@ConfigurationProperties
和 @EnableConfigurationProperties
是 Spring Boot
中常用的注解,提供了方便和强大的外部化配置支持。尽管它们常常一起出现,但是它们真的必须一起使用吗?Spring Boot
的灵活性常常让我们忽略配置背后产生的作用究竟是什么?本文将从源码角度出发分析两个注解的作用时机和工作原理。
注解
ConfigurationProperties
是用于外部化配置的注解。如果你想绑定和验证某些外部属性(例如来自 .properties
文件),就将其添加到类定义或 @Configuration
类中的 @Bean
方法。请注意,和 @Value
相反,SpEL
表达式不会被求值,因为属性值是外部化的。查看 ConfigurationProperties
注解的源码可知,该注解主要起到标记和存储一些信息的作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ConfigurationProperties {
@AliasFor("prefix") String value() default "";
@AliasFor("value") String prefix() default "";
boolean ignoreInvalidFields() default false;
boolean ignoreUnknownFields() default true;
}
|
查看 EnableConfigurationProperties
的源码,我们注意到它通过 @Import
导入了 EnableConfigurationPropertiesImportSelector
。
1 2 3 4 5 6 7 8 9 10
| @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(EnableConfigurationPropertiesImportSelector.class) public @interface EnableConfigurationProperties {
Class<?>[] value() default {};
}
|
注解的作用
查看 EnableConfigurationPropertiesImportSelector
的源码,关注 selectImports
方法。该方法返回了 ConfigurationPropertiesBeanRegistrar
和 ConfigurationPropertiesBindingPostProcessorRegistrar
的全限定类名,Spring
将注册它们。
1 2 3 4 5 6 7 8 9 10 11
| class EnableConfigurationPropertiesImportSelector implements ImportSelector {
private static final String[] IMPORTS = { ConfigurationPropertiesBeanRegistrar.class.getName(), ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
@Override public String[] selectImports(AnnotationMetadata metadata) { return IMPORTS; } }
|
注册目标类
ConfigurationPropertiesBeanRegistrar
是一个内部类,查看 ConfigurationPropertiesBeanRegistrar
的源码,关注 registerBeanDefinitions
方法。注册的目标来自于:
@EnableConfigurationProperties
的 value
所指定的类中
- 且标注了
@ConfigurationProperties
的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| 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 ? Collections.emptyList() : attributes.get("value")); }
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 boolean containsBeanDefinition( ConfigurableListableBeanFactory beanFactory, String name) { if (beanFactory.containsBeanDefinition(name)) { return true; } BeanFactory parent = beanFactory.getParentBeanFactory(); if (parent instanceof ConfigurableListableBeanFactory) { return containsBeanDefinition((ConfigurableListableBeanFactory) parent, name); } return false; }
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) { Assert.notNull( AnnotationUtils.findAnnotation(type, ConfigurationProperties.class), "No " + ConfigurationProperties.class.getSimpleName() + " annotation found on '" + type.getName() + "'."); }
}
|
注册后处理器
查看 ConfigurationPropertiesBindingPostProcessorRegistrar
的源码,关注 registerBeanDefinitions
方法。该方法注册了 ConfigurationPropertiesBindingPostProcessor
和 ConfigurationBeanFactoryMetadata
。
- 前者顾名思义,用于处理
ConfigurationProperties
的绑定
- 后者是用于在
Bean
工厂初始化期间记住 @Bean
定义元数据的实用程序类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 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); }
}
|
绑定
ConfigurationPropertiesBindingPostProcessor
是用于 ConfigurationProperties
绑定的后处理器,关注 afterPropertiesSet
方法还有核心方法 postProcessBeforeInitialization
。
- 在
afterPropertiesSet
方法中,它获取到了和自己一起注册的 ConfigurationBeanFactoryMetadata
。
- 在
postProcessBeforeInitialization
方法中,先获取 @ConfigurationProperties
,再进行绑定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {
public static final String BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class .getName();
public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
private ConfigurationBeanFactoryMetadata beanFactoryMetadata;
private ApplicationContext applicationContext;
private ConfigurationPropertiesBinder configurationPropertiesBinder;
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }
@Override public void afterPropertiesSet() throws Exception { this.beanFactoryMetadata = this.applicationContext.getBean( ConfigurationBeanFactoryMetadata.BEAN_NAME, ConfigurationBeanFactoryMetadata.class); this.configurationPropertiesBinder = new ConfigurationPropertiesBinder( this.applicationContext, VALIDATOR_BEAN_NAME); }
@Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE + 1; }
@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 } : new Annotation[] { annotation, validated }); Bindable<?> target = Bindable.of(type).withExistingValue(bean) .withAnnotations(annotations); try { this.configurationPropertiesBinder.bind(target); } catch (Exception ex) { throw new ConfigurationPropertiesBindException(beanName, bean, annotation, ex); } }
private ResolvableType getBeanType(Object bean, String beanName) { Method factoryMethod = this.beanFactoryMetadata.findFactoryMethod(beanName); if (factoryMethod != null) { return ResolvableType.forMethodReturnType(factoryMethod); } return ResolvableType.forClass(bean.getClass()); }
private <A extends Annotation> A getAnnotation(Object bean, String beanName, Class<A> type) { A annotation = this.beanFactoryMetadata.findFactoryAnnotation(beanName, type); if (annotation == null) { annotation = AnnotationUtils.findAnnotation(bean.getClass(), type); } return annotation; }
}
|
ConfigurationBeanFactoryMetadata
是用于在 Bean
工厂初始化期间记住 @Bean
定义元数据的实用程序类。在前面我们介绍过 @ConfigurationProperties
不仅可以添加到类定义,还可以用于标注 @Bean
方法,ConfigurationBeanFactoryMetadata
正是应用于在后者这类情况下获取 @ConfigurationProperties
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class ConfigurationBeanFactoryMetadata implements BeanFactoryPostProcessor {
private final Map<String, FactoryMetadata> beansFactoryMetadata = new HashMap<>();
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; for (String name : beanFactory.getBeanDefinitionNames()) { BeanDefinition definition = beanFactory.getBeanDefinition(name); String method = definition.getFactoryMethodName(); String bean = definition.getFactoryBeanName();、 if (method != null && bean != null) { this.beansFactoryMetadata.put(name, new FactoryMetadata(bean, method)); } } }
public <A extends Annotation> A findFactoryAnnotation(String beanName, Class<A> type) { Method method = findFactoryMethod(beanName); return (method == null ? null : AnnotationUtils.findAnnotation(method, type)); }
public Method findFactoryMethod(String beanName) { if (!this.beansFactoryMetadata.containsKey(beanName)) { return null; } AtomicReference<Method> found = new AtomicReference<>(null); FactoryMetadata metadata = this.beansFactoryMetadata.get(beanName); Class<?> factoryType = this.beanFactory.getType(metadata.getBean()); String factoryMethod = metadata.getMethod(); if (ClassUtils.isCglibProxyClass(factoryType)) { factoryType = factoryType.getSuperclass(); } ReflectionUtils.doWithMethods(factoryType, (method) -> { if (method.getName().equals(factoryMethod)) { found.compareAndSet(null, method); } }); return found.get(); } }
|
总结
@EnableConfigurationProperties
的目的有两个:
- 注册目标
- 注册后处理器用于在目标进行
Bean
初始化工作时,介入进行绑定
尽管注册目标时的操作有些巧妙,但是还是要明白 ConfigurationProperties
类只是单纯的被注册了而已。对于后处理器而言,无论一个 ConfigurationProperties
类是不是通过注解注册,后处理器都会一视同仁地进行绑定。但同时,你又要知道后处理器也是通过 @EnableConfigurationProperties
注册的,因此你需要保证至少有一个 @EnableConfigurationProperties
标注的类被注册(并被处理了 @Import
)。
在 Spring Boot
中,@SpringBootApplication
通过 @EnableAutoConfiguration
启用了自动配置,从而注册了 ConfigurationPropertiesAutoConfiguration
,ConfigurationPropertiesAutoConfiguration
标注了 @EnableConfigurationProperties
。因此,对于 Spring Boot
而言,扫描范围内的所有 ConfigurationProperties
类,其实都不需要 @EnableAutoConfiguration
。事实上,由于默认生成的 beanName
不同,多余的配置还会重复注册两个 Bean
定义。
1 2 3 4 5
| @Configuration @EnableConfigurationProperties public class ConfigurationPropertiesAutoConfiguration {
}
|