我是靠谱客的博主 等待心锁,最近开发中收集的这篇文章主要介绍@ConfigurationProperties注解@ConfigurationProperties注解,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

@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注解所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(39)

评论列表共有 0 条评论

立即
投稿
返回
顶部