Pepper Metrics是我與同事開發的一個開源工具(github.com/zrbcool/pep…),其經過收集jedis/mybatis/httpservlet/dubbo/motan的運行性能統計,並暴露成prometheus等主流時序數據庫兼容數據,經過grafana展現趨勢。其插件化的架構也很是方便使用者擴展並集成其餘開源組件。
請你們給個star,同時歡迎你們成爲開發者提交PR一塊兒完善項目。html
不用說你們都知道Spring Boot很是的方便,快捷,讓開發的同窗簡單的幾行代碼加上幾行配置甚至零配置就能啓動並使用一個項目,項目當中咱們也可能常常使用 @ConfigurationProperties將某個Bean與properties配置當中的prefix相綁定,使配置值與定義配置的Bean分離,方便管理。
那麼,這個@ConfigurationProperties是什麼機制,如何實現的呢?咱們今天來聊聊這個話題java
爲何從EnableConfigurationProperties講? Spring Boot項目自身當中大量autoconfigure都是使用EnableConfigurationProperties註解啓用XXXProperties功能,例如spring-data-redis的 這個RedisAutoConfigurationgit
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class) //看這裏
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
// ...
}
複製代碼
而RedisProperties中又帶有註解@ConfigurationProperties(prefix = "spring.redis"),這樣就將spring.redis這個前綴的配置項與RedisProperties 這個實體類進行了綁定。github
說完來由,咱們就來講說內部實現,先來看看redis
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {
Class<?>[] value() default {};
}
複製代碼
@Import(EnableConfigurationPropertiesImportSelector.class)指明瞭這個註解的處理類EnableConfigurationPropertiesImportSelector, 查看EnableConfigurationPropertiesImportSelector源碼spring
class EnableConfigurationPropertiesImportSelector implements ImportSelector {
private static final String[] IMPORTS = { ConfigurationPropertiesBeanRegistrar.class.getName(),
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
@Override
public String[] selectImports(AnnotationMetadata metadata) {
return IMPORTS;
}
// 省略部分其餘方法
}
複製代碼
咱們先看這塊關鍵部分返回了一個IMPORTS數組,數組中包含ConfigurationPropertiesBeanRegistrar.class,ConfigurationPropertiesBindingPostProcessorRegistrar.class兩個元素
根據@Import及ImportSelector接口的原理(其原理能夠參考同事寫的一篇文章:相親相愛的@Import和@EnableXXX),咱們得知spring會初始化上面兩個Registrar到spring容器當中,而兩個Registrar均實現了ImportBeanDefinitionRegistrar接口, 而ImportBeanDefinitionRegistrar會在處理Configuration時觸發調用(其原理能夠參考文章:這塊找一篇文章),下面咱們分別深刻兩個Registrar的源碼:數據庫
直接看代碼數組
public class ConfigurationPropertiesBindingPostProcessorRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) {
registerConfigurationPropertiesBindingPostProcessor(registry);
registerConfigurationBeanFactoryMetadata(registry);
}
}
private void registerConfigurationPropertiesBindingPostProcessor(BeanDefinitionRegistry registry) {
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME, definition);
}
private void registerConfigurationBeanFactoryMetadata(BeanDefinitionRegistry registry) {
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(ConfigurationBeanFactoryMetadata.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(ConfigurationBeanFactoryMetadata.BEAN_NAME, definition);
}
}
複製代碼
能夠看到註冊了兩個Bean到spring容器微信
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class);
if (annotation != null) {
bind(bean, beanName, annotation);
}
return bean;
}
private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
ResolvableType type = getBeanType(bean, beanName);
Validated validated = getAnnotation(bean, beanName, Validated.class);
Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated }
: new Annotation[] { annotation };
Bindable<?> target = Bindable.of(type).withExistingValue(bean).withAnnotations(annotations);
try {
// 在這裏完成了,關鍵的prefix到PropertyBean的值綁定部分,因此各類@ConfigurationProperties註解最終生效就靠這部分代碼了
this.configurationPropertiesBinder.bind(target);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(beanName, bean, annotation, ex);
}
}
複製代碼
其實ConfigurationPropertiesBeanRegistrar是EnableConfigurationPropertiesImportSelector的靜態內部類,在前面貼代碼時被省略的部分,上代碼mybatis
public static class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
getTypes(metadata).forEach((type) -> register(registry, (ConfigurableListableBeanFactory) registry, type));
}
private List<Class<?>> getTypes(AnnotationMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);
return collectClasses((attributes != null) ? attributes.get("value") : Collections.emptyList());
}
private List<Class<?>> collectClasses(List<?> values) {
return values.stream().flatMap((value) -> Arrays.stream((Object[]) value)).map((o) -> (Class<?>) o)
.filter((type) -> void.class != type).collect(Collectors.toList());
}
private void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory, Class<?> type) {
String name = getName(type);
if (!containsBeanDefinition(beanFactory, name)) {
registerBeanDefinition(registry, name, type);
}
}
private String getName(Class<?> type) {
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type, ConfigurationProperties.class);
String prefix = (annotation != null) ? annotation.prefix() : "";
return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
}
private void registerBeanDefinition(BeanDefinitionRegistry registry, String name, Class<?> type) {
assertHasAnnotation(type);
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(type);
registry.registerBeanDefinition(name, definition);
}
private void assertHasAnnotation(Class<?> type) {...}
private boolean containsBeanDefinition(ConfigurableListableBeanFactory beanFactory, String name) {...}
}
複製代碼
邏輯解讀:
主邏輯入口(registerBeanDefinitions) 1 -> getTypes(metadata)拿到標註EnableConfigurationProperties註解的配置值,整理成List<Class<?>>而後逐個處理 2 -> 對每一個元素(Class<?>)調用register方法處理 3 -> register方法經過類當中的ConfigurationProperties註解的prefix值加類名字做爲beanName經過registry.registerBeanDefinition調用將Class<?>註冊到registry當中 當標註註解@ConfigurationProperties的XXXProperties的BeanDefinition加入到registry中後,Bean的初始化就交給spring容器, 而這個過程當中前面提到的ConfigurationPropertiesBindingPostProcessorRegistrar就完成一系列的後置操做幫助咱們完成最終的值綁定
@ConfigurationProperties的總體處理過程,本文已經基本講述完畢,如今大致總結一下: EnableConfigurationProperties完成ConfigurationPropertiesBindingPostProcessorRegistrar及ConfigurationPropertiesBeanRegistrar的引入 其中: