0. 开源项目推荐
Pepper Metrics https://github.com/zrbcool/pepper-metrics 是我与同事开发的一个开源工具(https://github.com/zrbcool/pepper-metrics), 其通过收集 jedis/mybatis/httpservlet/dubbo/motan 的运行性能统计, 并暴露成 prometheus 等主流时序数据库兼容数据, 通过 grafana 展示趋势. 其插件化的架构也非常方便使用者扩展并集成其他开源组件.
请大家给个 star, 同时欢迎大家成为开发者提交 PR 一起完善项目.
1. 概述
不用说大家都知道 Spring Boot 非常的方便, 快捷, 让开发的同学简单的几行代码加上几行配置甚至零配置就能启动并使用一个项目, 项目当中我们也可能经常使用
@ConfigurationProperties 将某个 Bean 与 properties 配置当中的 prefix 相绑定, 使配置值与定义配置的 Bean 分离, 方便管理.
那么, 这个 @ConfigurationProperties 是什么机制, 如何实现的呢? 我们今天来聊聊这个话题
2. 正文
2.1 从 EnableConfigurationProperties 说起
为什么从 EnableConfigurationProperties 讲?
Spring Boot 项目自身当中大量 autoconfigure 都是使用 EnableConfigurationProperties 注解启用 XXXProperties 功能, 例如 spring-data-Redis 的
这个 RedisAutoConfiguration
- @Configuration
- @ConditionalOnClass(RedisOperations.class)
- @EnableConfigurationProperties(RedisProperties.class) // 看这里
- @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
- public class RedisAutoConfiguration {
- // ...
- }
而 RedisProperties 中又带有注解 @ConfigurationProperties(prefix = "spring.redis"), 这样就将 spring.Redis 这个前缀的配置项与 RedisProperties
这个实体类进行了绑定.
2.2 EnableConfigurationProperties 内部实现解析
说完来由, 我们就来说说内部实现, 先来看看
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Import(EnableConfigurationPropertiesImportSelector.class)
- public @interface EnableConfigurationProperties {
- Class<?>[] value() default {};
- }
@Import(EnableConfigurationPropertiesImportSelector.class)指明了这个注解的处理类 EnableConfigurationPropertiesImportSelector,
查看 EnableConfigurationPropertiesImportSelector 源码
- 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 https://zhuanlan.zhihu.com/p/83295224 ), 我们得知 spring 会初始化上面两个 Registrar 到 spring 容器当中, 而两个 Registrar 均实现了 ImportBeanDefinitionRegistrar 接口,
而 ImportBeanDefinitionRegistrar 会在处理 Configuration 时触发调用(其原理可以参考文章: 这块找一篇文章), 下面我们分别深入两个 Registrar 的源码:
- ConfigurationPropertiesBeanRegistrar
- ConfigurationPropertiesBindingPostProcessorRegistrar
- 2.2.1 ConfigurationPropertiesBindingPostProcessorRegistrar
直接看代码
- 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 容器
ConfigurationPropertiesBindingPostProcessor
其实现如下接口:
BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean
PriorityOrdered
Ordered.HIGHEST_PRECEDENCE + 1 保证前期执行, 且非最先
ApplicationContextAware
获取到 ApplicationContext 设置到内部变量
InitializingBean
afterPropertiesSet 方法在 Bean 创建时被调用, 保证内部变量 configurationPropertiesBinder 被初始化, 这个 binder 类就是使 prefix 与 propertyBean 进行值绑定的关键工具类
BeanPostProcessor
postProcessBeforeInitialization 方法处理具体的 bind 逻辑如下:
- @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);
- }
- }
- ConfigurationBeanFactoryMetadata
如果某些 Bean 是通过 FactoryBean 创建, 则该类用于保存 FactoryBean 的各种原信息, 用于 ConfigurationPropertiesBindingPostProcessor 当中的元数据查询, 这里就不做展开
2.2.2 ConfigurationPropertiesBeanRegistrar
其实 ConfigurationPropertiesBeanRegistrar 是 EnableConfigurationPropertiesImportSelector 的静态内部类, 在前面贴代码时被省略的部分, 上代码
- 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 就完成一系列的后置操作帮助我们完成最终的值绑定
3. 总结
@ConfigurationProperties 的整体处理过程, 本文已经基本讲述完毕, 现在大体总结一下:
EnableConfigurationProperties 完成 ConfigurationPropertiesBindingPostProcessorRegistrar 及 ConfigurationPropertiesBeanRegistrar 的引入
其中:
ConfigurationPropertiesBeanRegistrar 完成标注 @ConfigurationProperties 的类的查找并组装成 BeanDefinition 加入 registry
ConfigurationPropertiesBindingPostProcessorRegistrar 完成 ConfigurationPropertiesBindingPostProcessor 及 ConfigurationBeanFactoryMetadata
ConfigurationPropertiesBindingPostProcessor 完成所有标注 @ConfigurationProperties 的 Bean 到 prefix 的 properties 值绑定
ConfigurationBeanFactoryMetadata 仅用于提供上面处理中需要的一些元数据信息
4. 作者其他文章
https://github.com/zrbcool/blog-public
5. 微信订阅号
来源: https://www.cnblogs.com/zrbcool/p/11589417.html