概述
@ConfigurationProperties注解
@ConfigurationProperties为Springboot中的一个注解,用于绑定实体类与配置文件,用来把properties或者yml配置文件转化为bean来使用的。
基本使用
其基本使用方式如下:
@Component
@ConfigurationProperties(prefix = "person") //使用yaml,springboot配置,将实体类与配置绑定
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> list;
private Dog dog;
}
@ConfigurationProperties绑定的yaml配置文件。使用yaml配置文件相较于properties配置文件修改赋值是比较方便的。
person:
name: java
age: 3
happy: true
birth: 1996/03/11
maps: {k1: v1,k2: v2}
list:
- code
- music
- girl
dog:
name: 旺财
age: 3
源码解析
@ConfigurationProperties
/**
*外部化配置的注释。如果要绑定和验证一些外部属性(例如,来自. Properties文件),请将其添加到类定
*义或{@code @Bean}类中的{@code @Bean}方法中。绑定可以通过在带注解的类上调用setter来执行,或
*者,如果{@link ConstructorBinding @ConstructorBinding}正在使用,则通过绑定到构造函数参数来
* 执行。注意,与{@code @Value}相反,SpEL表达式不计算,因为属性值是外部化的
*
*
* @author Dave Syer
* @since 1.0.0
* @see ConfigurationPropertiesScan
* @see ConstructorBinding
* @see ConfigurationPropertiesBindingPostProcessor
* @see EnableConfigurationProperties
*/
@Target({ ElementType.TYPE, ElementType.METHOD }) //Target注解决定该注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等
@Retention(RetentionPolicy.RUNTIME) //Retention注解决定MyAnnotation注解的生命周期,这个注解的生命周期一直程序运行时都存在
@Documented
public @interface ConfigurationProperties {
/*
*有效绑定到此对象的属性的前缀。是{@link #prefix()}的同义词。一个有效的前缀由一个或多个用
*点分隔的单词定义(例如{@code"acme.system.feature"})。
*@return要绑定属性的前缀
*/
@AliasFor("prefix")
String value() default "";
@AliasFor("value")
String prefix() default "";
/**
* 指示绑定到此对象时应忽略无效字段。根据所使用的绑定器,Invalid意味着无效,通常意味
* 着字段的类型错误(或者不能强制转换为正确的类型)。
* 返回标志值(默认为false)
*/
boolean ignoreInvalidFields() default false;
/**
* 指示在绑定到此对象时应忽略未知字段。未知字段可能是属性中出现错误的标志。
* 返回标志值(默认为true)
*/
boolean ignoreUnknownFields() default true;
}
下面分析该注解中@see引用的其他注解
@ConfigurationPropertiesScan
/**
* 配置扫描{@link ConfigurationProperties @ConfigurationProperties}类时使用的基本包。可以指
* 定{@link #basePackageClasses()}、{@link #basePackages()}或它的别名{@link #value()}来定*
* 义要扫描的特定包。如果没有定义特定的包,将对带有此注释的类的包进行扫描。
*
* 注意:用{@link Component @Component}注释或元注释的类不会被这个注释选中。
*
* @author Madhura Bhave
* @since 2.2.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ConfigurationPropertiesScanRegistrar.class)
@EnableConfigurationProperties
public @interface ConfigurationPropertiesScan {
/**
* 属性{@link #basePackages()}的别名。允许更简洁的注释声明,例如:{@code
* @ConfigurationPropertiesScan("org.my.pkg")}而不是{@code
* @ConfigurationPropertiesScan(basePackages="org.my.pkg")}。
* @return要扫描的基本包
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* 扫描配置属性的基本包。{@link #value()}是此属性的别名(与此属性互斥)。
* 使用{@link # basepackagclasses()}作为基于字符串的包名的类型安全替代。
* @return要扫描的基本包
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* {@link #basePackages()}的类型安全替代,用于指定要扫描配置属性的包。每个指定类别的包裹
* 将被扫描。
* 考虑在每个包中创建一个特殊的no-op标记类或接口,这些类或接口除了被此属性引用之外没有任何
* 用途。
* @return要扫描的基包中的类
*/
Class<?>[] basePackageClasses() default {};
}
该注解中引入了ConfigurationPropertiesScanRegistrar类,用来在指定packages中发现@ConfigurationProperties并注册。下面分析该类的源码:
/**
* 通过扫描来注册{@link ConfigurationProperties @ConfigurationProperties} bean定义。
*
* @author Madhura Bhave
* @author Phillip Webb
*/
class ConfigurationPropertiesScanRegistrar implements ImportBeanDefinitionRegistrar {
private final Environment environment;
private final ResourceLoader resourceLoader;
ConfigurationPropertiesScanRegistrar(Environment environment, ResourceLoader resourceLoader) {
this.environment = environment;
this.resourceLoader = resourceLoader;
}
//BeanDefinitionRegistry:往BeanFactory中添加BeanDefinition
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
scan(registry, packagesToScan);
}
/**
* 获取要扫描的包
*/
private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(ConfigurationPropertiesScan.class.getName()));
String[] basePackages = attributes.getStringArray("basePackages");
Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages));
for (Class<?> basePackageClass : basePackageClasses) {
packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
}
// 如果要扫描的包是空的,添加元数据的类
if (packagesToScan.isEmpty()) {
packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName()));
}
//过滤名字为空,“”,“ ”的包
packagesToScan.removeIf((candidate) -> !StringUtils.hasText(candidate));
return packagesToScan;
}
private void scan(BeanDefinitionRegistry registry, Set<String> packages) {
ConfigurationPropertiesBeanRegistrar registrar = new ConfigurationPropertiesBeanRegistrar(registry);
//ClassPathScanningCandidateComponentProvider是Spring提供的工具,可以按自定义的类型,查找classpath下符合要求的class文件
ClassPathScanningCandidateComponentProvider scanner = getScanner(registry);
for (String basePackage : packages) {
for (BeanDefinition candidate : scanner.findCandidateComponents(basePackage)) {
register(registrar, candidate.getBeanClassName());
}
}
}
private ClassPathScanningCandidateComponentProvider getScanner(BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.setEnvironment(this.environment);
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(ConfigurationProperties.class));
//TypeExcludeFilter让某个使用了spring 注解的类不被spring扫描注入到spring bean池中
TypeExcludeFilter typeExcludeFilter = new TypeExcludeFilter();
typeExcludeFilter.setBeanFactory((BeanFactory) registry);
scanner.addExcludeFilter(typeExcludeFilter);
return scanner;
}
private void register(ConfigurationPropertiesBeanRegistrar registrar, String className) throws LinkageError {
try {
register(registrar, ClassUtils.forName(className, null));
}
catch (ClassNotFoundException ex) {
// Ignore
}
}
private void register(ConfigurationPropertiesBeanRegistrar registrar, Class<?> type) {
if (!isComponent(type)) {
registrar.register(type);
}
}
/**
*判断type类中是否包含@Component注解,若没有该注解,会查找继承关系
*(SearchStrategy.TYPE_HIERARCHY)中是否包含这个注解
*/
private boolean isComponent(Class<?> type) {
return MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY).isPresent(Component.class);
}
}
@EnableConfigurationProperties
/**
* 使使用 @ConfigurationProperties 注解的类生效。
*
* 如果一个配置类只配置@ConfigurationProperties注解,而没有使用@Component,那么在IOC容器中是
* 获取不到properties 配置文件转化的bean。说白了 @EnableConfigurationProperties 相当于把使
* 用 @ConfigurationProperties 的类进行了一次注入。
*
* @author Dave Syer
* @since 1.0.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties {
/**
* 配置属性验证器的bean名
*/
String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
/**
* Convenient way to quickly register
* {@link ConfigurationProperties @ConfigurationProperties} annotated beans with
* Spring. Standard Spring Beans will also be scanned regardless of this value.
* @return {@code @ConfigurationProperties} annotated beans to register
*/
Class<?>[] value() default {};
}
/**
* 注册属性配置、绑定等所需要bean
* 1、 初始化好BeanDefinition注册器
* 2、取得 @EnableConfigurationProperties value属性表示的所有 Class 对象
* 3、调用注册器的 register(Class class) 方法,将这些 Class表示的对象生成 BeanDefinition 注
* 册到 Spring 上下文里
*/
class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {
//getQualifiedAttributeName返回一个由给定的封装限定的属性名
private static final String METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME = Conventions
.getQualifiedAttributeName(EnableConfigurationPropertiesRegistrar.class, "methodValidationExcludeFilter");
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//注册基础设施bean
registerInfrastructureBeans(registry);
registerMethodValidationExcludeFilter(registry);
//创建注册器代理类。ConfigurationPropertiesBeanRegistrar是BeanDefinitionRegistry注册器的代理实现类,
//代理类被EnableConfigurationPropertiesRegistrar和ConfigurationPropertiesScanRegistrar注册器类使用,用于将@ConfigurationProperties标注的bean注册为一个bean定义
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
//获取注解@EnableConfigurationProperties指定的被@ConfigurationProperties注解标注的bean实例对象
//forEach循环调用注册器方法将@ConfigurationProperties标注的bean注册到IOC容器之中
getTypes(metadata).forEach(beanRegistrar::register);
}
private Set<Class<?>> getTypes(AnnotationMetadata metadata) {
return metadata.getAnnotations().stream(EnableConfigurationProperties.class)
.flatMap((annotation) -> Arrays.stream(annotation.getClassArray(MergedAnnotation.VALUE)))
.filter((type) -> void.class != type).collect(Collectors.toSet());
}
static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
//将BeanPostProcessor bean后置处理器ConfigurationPropertiesBindingPostProcessor注册到IOC容器.
//ConfigurationPropertiesBindingPostProcessor是BeanPostProcessor接口的实现类,用来绑定被注解@ConfigurationProperties标注的bean及配置文件中的PropertySources属性配置
ConfigurationPropertiesBindingPostProcessor.register(registry);
//将提供属性绑定的BoundConfigurationProperties类注册到IOC容器之中
BoundConfigurationProperties.register(registry);
}
static void registerMethodValidationExcludeFilter(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME)) {
BeanDefinition definition = BeanDefinitionBuilder
.genericBeanDefinition(MethodValidationExcludeFilter.class,
() -> MethodValidationExcludeFilter.byAnnotation(ConfigurationProperties.class))
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition();
registry.registerBeanDefinition(METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME, definition);
}
}
}
@ConstructorBinding
/**
* 可以用来指示配置属性应使用构造函数参数而不是通过调用setter进行绑定的注释。 可以在类型级别(如
* 果有明确的构造函数)或实际使用的构造函数上添加。
*
* @author Phillip Webb
* @since 2.2.0
* @see ConfigurationProperties
*/
@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConstructorBinding {
}
@ConfigurationPropertiesBindingPostProcessor
/**
* 绑定PropertySources到@ConfigurationProperties注解的bean实例。这是一个框架内部工具,在实例
* 化每一个bean时,框架会使用它将@ConfigurationProperties注解中指定前缀的外部配置属性项加载进
* 来设置到bean相应的属性上。这里的PropertySources能够从上下文中的Environment对象获取外部配置
* 属性项。
*
* @author Dave Syer
* @author Phillip Webb
* @author Christian Dupuis
* @author Stephane Nicoll
* @author Madhura Bhave
* @since 1.0.0
*/
public class ConfigurationPropertiesBindingPostProcessor
implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {
/**
* 该后处理器向其注册的Bean名称
*/
public static final String BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class.getName();
private ApplicationContext applicationContext;
private BeanDefinitionRegistry registry;
private ConfigurationPropertiesBinder binder;
// ApplicationContextAware 接口定义的方法
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
//我们不能对应用程序上下文使用构造函数注入,因为它会引起工厂Bean急切的初始化
this.registry = (BeanDefinitionRegistry) this.applicationContext.getAutowireCapableBeanFactory();
this.binder = ConfigurationPropertiesBinder.get(this.applicationContext);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
// BeanPostProcessor 接口定义的方法
// 在每个bean实例化后,初始化前执行
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
return bean;
}
//将注解 @ConfigurationProperties 指定的外部配置属性项设置到目标对象 bean
private void bind(ConfigurationPropertiesBean bean) {
if (bean == null || hasBoundValueObject(bean.getName())) {
return;
}
Assert.state(bean.getBindMethod() == BindMethod.JAVA_BEAN, "Cannot bind @ConfigurationProperties for bean '"
+ bean.getName() + "'. Ensure that @ConstructorBinding has not been applied to regular bean");
try {
this.binder.bind(bean);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(bean, ex);
}
}
private boolean hasBoundValueObject(String beanName) {
return this.registry.containsBeanDefinition(beanName) && this.registry
.getBeanDefinition(beanName) instanceof ConfigurationPropertiesValueObjectBeanDefinition;
}
/**
* Register a {@link ConfigurationPropertiesBindingPostProcessor} bean if one is not
* already registered.
* @param registry the bean definition registry
* @since 2.2.0
*/
public static void register(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "Registry must not be null");
if (!registry.containsBeanDefinition(BEAN_NAME)) {
BeanDefinition definition = BeanDefinitionBuilder
.genericBeanDefinition(ConfigurationPropertiesBindingPostProcessor.class,
ConfigurationPropertiesBindingPostProcessor::new)
.getBeanDefinition();
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN_NAME, definition);
}
ConfigurationPropertiesBinder.register(registry);
}
}
总结
@ConfigurationProperties用于绑定实体类与配置文件,即将配置文件中属性的值映射到对象。其引用参考了 ConfigurationPropertiesScan, ConstructorBinding, ConfigurationPropertiesBindingPostProcessor和EnableConfigurationProperties注解。ConfigurationPropertiesScan引入了ConfigurationPropertiesScanRegistrar类,用来在指定packages中发现ConfigurationProperties注解的类并将其注册;EnableConfigurationProperties 使用 @ConfigurationProperties 的类注入容器;ConfigurationPropertiesBindingPostProcessor为后置处理器,在实例化bean时,将配置文件中的属性值加载设置到bean的属性中。
不禁想起了Spring中的ContextConfiguration注解
最后
以上就是等待心锁为你收集整理的@ConfigurationProperties注解@ConfigurationProperties注解的全部内容,希望文章能够帮你解决@ConfigurationProperties注解@ConfigurationProperties注解所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复