- 先看我的demo实现
第一步: 配置ConfigurationProperties属性
@ConfigurationProperties(prefix = "redis.proxy")
@Getter
@Setter
public class RedisProperties {
/** 服务器列表*/
private List<String> configServerList;
/** 可选配置,Redis运行模式,默认值singleton模式,枚举值Singleton,Sentinel,Sharded,从1.1.8-snapshot开始支持*/
private RedisCacheMode mode;
/** 可选配置(仅当模式为Sentinel时需要配置),表示master的名称*/
private String masterName;
private String password;
}
第二步: 配置bean
@Configuration
@EnableConfigurationProperties({RedisProperties.class})
public class RedisConfig {
这里注意要将RedisProperties 作为入参传进来,并将属性set进行
@Bean(initMethod = "init")
@ConditionalOnMissingBean
public RedisCacheManager redisCacheManager(RedisProperties redisProperties){
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setConfigServerList(redisProperties.getConfigServerList());
redisCacheManager.setMasterName(redisProperties.getMasterName());
redisCacheManager.setMode(redisProperties.getMode());
redisCacheManager.setPassword(redisProperties.getPassword());
return redisCacheManager;
}
- 源码分析
先看@EnableConfigurationProperties源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
这里导入了EnableConfigurationPropertiesImportSelector
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {
/**
* Convenient way to quickly register {@link ConfigurationProperties} annotated beans
* with Spring. Standard Spring Beans will also be scanned regardless of this value.
* @return {@link ConfigurationProperties} annotated beans to register
*/
Class<?>[] value() default {};
}
再看EnableConfigurationPropertiesImportSelector ,关于EnableConfigurationPropertiesImportSelector 是怎么被调用到的,请查看我的博客 https://blog.youkuaiyun.com/tszxlzc/article/details/88113819 (可以跳到博客最下面查看时序图了解全流程)
class EnableConfigurationPropertiesImportSelector implements ImportSelector {
//下面的selectImports方法返回的类
private static final String[] IMPORTS = {
ConfigurationPropertiesBeanRegistrar.class.getName(),
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
// selectImports方法会在org.springframework.context.annotation.ConfigurationClassParser#processImports方法里被调用
@Override
public String[] selectImports(AnnotationMetadata metadata) {
return IMPORTS;
}
下图是通过debug查看org.springframework.context.annotation.ConfigurationClassParser#processImports方法调用EnableConfigurationPropertiesImportSelector 选择器的返回结果,正好是org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector#IMPORTS数组中的类
说说这两个类吧
ConfigurationPropertiesBeanRegistrar 和ConfigurationPropertiesBindingPostProcessorRegistrar这两个类 是处理
@ EnableConfigurationProperties和 @ConfigurationProperties注解的
ConfigurationPropertiesBeanRegistrar类核心源码如下
registerBeanDefinitions方法会在
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars方法中调用,而loadBeanDefinitionsFromRegistrars方法又会在org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass方法中调用,loadBeanDefinitionsForConfigurationClass方法又会在org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions方法中被调用,loadBeanDefinitions方法又会在org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中调用,processConfigBeanDefinitions方法又会在org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法中调用,
最后就倒退到了ConfigurationClassPostProcessor 这个bean后处理器(配置的入口)
ConfigurationClassPostProcessor 是怎么调到loadBeanDefinitions方法的看时序图,是在配置解析之后
public static class ConfigurationPropertiesBeanRegistrar
implements ImportBeanDefinitionRegistrar {
//注册bean定义(ConfigurationProperties注解的bean)
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
getTypes(metadata).forEach((type) -> register(registry,
(ConfigurableListableBeanFactory) registry, type));
}
// 获取EnableConfigurationProperties注解的属性值,即ConfigurationProperties注解配置的属性文件
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());
}
// 将ConfigurationProperties注解的属性文件注册到容器中
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() + "'.");
}
}
总结: 通过以上解析注册后,被ConfigurationProperties注册的属性bean最终被注册到spring容器中,属性bean就可以在被EnableConfigurationProperties注册的bean或者被@bean配置的bean实例化时被依赖注入(set注入或构造方法注入),其实最终发现EnableConfigurationProperties ConfigurationProperties都是配置了一个bean被使用,spring可真是面向bean编程啊