@EnableConfigurationProperties @ConfigurationProperties @ConfigurationPropertiesScan

Prefacio

En el proyecto SpringBoot, a menudo necesitamos vincular algunos elementos de configuración de prefijo específicos a una clase de configuración. En este momento, podemos usar @EnableConfigurationPropertiesy @ConfigurationPropertiesanotar para lograr. @ConfigurationPropertiesScanLas anotaciones también se agregan en SpringBoot 2.2.0 para ayudarnos a simplificar el registro de la clase de configuración como un Bean. A continuación, se explica principalmente el uso y la implementación del código fuente de estas tres anotaciones.

Ejemplo de uso: vincular elementos de configuración a una clase de configuración

Existen los siguientes elementos de configuración, que usamos respectivamente @ConfigurationPropertiesy @EnableConfigurationPropertiesdos métodos de anotación para vincularlos a la clase de configuración, y estas clases de configuración se registrarán como Beans

#绑定到配置类 com.example.demo.config.MyBatisProperties
mybatis.basePackage= com.example.web.mapper
mybatis.mapperLocations= classpath*:mapper/*.xml
mybatis.typeAliasesPackage= com.example.web.model
mybatis.defaultStatementTimeoutInSecond= 5
mybatis.mapUnderscoreToCamelCase= false

#绑定到配置项类 com.example.demo.config.ShardingProperties
sharding.defaultDSIndex= 0
sharding.dataSources[0].driverClassName= com.mysql.jdbc.Driver
sharding.dataSources[0].jdbcUrl= jdbc:mysql://localhost:3306/lwl_db0?useSSL=false&characterEncoding=utf8
sharding.dataSources[0].username= root
sharding.dataSources[0].password= 123456
sharding.dataSources[0].readOnly= false

Método 1. Utilice @ConfigurationProperties

@ConfigurationPropertiesLa anotación en realidad solo especifica el prefijo correspondiente al atributo en la clase de configuración. Cuando una clase de configuración solo está @ConfigurationPropertiesmarcada, el valor del elemento de configuración no estará vinculado a su atributo, ni se registrará como un Bean. utilice la @Componentanotación al mismo tiempo O @Component子类注解(por ejemplo @Configuration).
Ejemplo: clase de configuración com.example.demo.config.ShardingProperties

@Component
@ConfigurationProperties(prefix = "sharding")
public class ShardingProperties {
    
    
    private Integer defaultDSIndex;
    private String column;
    private List<MyDataSourceProperties> dataSources;
    //忽略其他字段和getter/setter方法
}

public class MyDataSourceProperties {
    
    
    private String name;
    private String driverClassName;
    private String jdbcUrl;
    private String username;
    private String password;
    private Long connectionTimeout;
}

Método 2, use @EnableConfigurationProperties

Además de usar el método 1, también puede especificar una clase de configuración específica para vincular valores de propiedad a través de @EnableConfigurationProperties (value = {xxx.calss}).

Ejemplo: clase de configuración com.example.demo.config.MyBatisProperties

@ConfigurationProperties(prefix = "mybatis")
public class MyBatisProperties {
    
    
    private String basePackage;
    private String mapperLocations;
    private String typeAliasesPackage;
    private String markerInterface;
    //忽略其他字段和getter/setter方法
}

@EnableConfigurationProperties({
    
    MyBatisProperties.class})
@Configuration
public class EnableMyBatisConfig {
    
    

}

Usa el valor en la clase de configuración

/** Created by bruce on 2019/6/15 00:20 */
@Component
public class BinderConfig {
    
    
    private static final Logger logger = LoggerFactory.getLogger(BinderConfig.class);
   
    @Autowired
    private ShardingProperties shardingProperties;

    @Autowired
    private MyBatisProperties myBatisProperties;

    @PostConstruct
    public void binderTest() {
    
    
        //打印配置类中从配置文件中映射的值
        System.out.println(JsonUtil.toJson(shardingProperties));
        System.out.println(JsonUtil.toJson(myBatisProperties));
    }
}

Rol de @ConfigurationProperties

@ConfigurationPropertiesLas clases de procesamiento relevantes no se inyectan en el contenedor Spring y solo sirven como marcadores relevantes. La lógica de procesamiento relevante es @EnableConfigurationPropertiescompletada por las clases de procesamiento importadas. @ConfigurationPropertiesLas clases que solo están marcadas y anotadas no se registrarán como Beans de forma predeterminada

public @interface ConfigurationProperties {
    //等同于prefix,指定属性绑定的前缀
	@AliasFor("prefix")
	String value() default "";

	@AliasFor("value")
	String prefix() default "";

	//当属性值绑定到字段,发生错误时,是否忽略异常。默认不忽略,会抛出异常
	boolean ignoreInvalidFields() default false;

	//当配置项向实体类中的属性绑定时,没有找到对应的字段,是否忽略。默认忽略,不抛出异常。
	//如果ignoreInvalidFields = true 则 ignoreUnknownFields = false不再生效,可能是SpringBoot的bug
	boolean ignoreUnknownFields() default true;
}

Principio de implementación de @EnableConfigurationProperties

@EnableConfigurationPropertiesHay dos funciones principales

  1. Registre el postprocesador ConfigurationPropertiesBindingPostProcessor, que se utiliza para vincular valores de propiedad a las propiedades en el Bean cuando se inicializa el Bean. Esta es la razón por la que el primer método @ConfigurationPropertiesrequiere el uso de @Componentanotaciones; de lo contrario, no es un Bean y no puede ser procesado por el postprocesador procesado por Spring, y el valor del atributo no se puede vincular.

  2. @ConfigurationPropertiesRegistre una clase de configuración marcada como Bean of Spring. Las clases que no están marcadas y @ConfigurationPropertiesanotadas no se pueden usar como @EnableConfigurationPropertiesparámetros, de lo contrario, se lanzará una excepción. El solo uso de @ConfigurationProperties no registrará esta clase como un Bean

class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {
    
    

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    
    
		registerInfrastructureBeans(registry);
		ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
		//获取@EnableConfigurationProperties注解参数指定的配置类,并将其注册成Bean
		//beanName为 " prefix+配置类全类名"。
		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());
	}

	//注册相关后置处理器和Bean用于注定绑定
	static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
    
    
		ConfigurationPropertiesBindingPostProcessor.register(registry);
		BoundConfigurationProperties.register(registry);
		ConfigurationPropertiesBeanDefinitionValidator.register(registry);
		ConfigurationBeanFactoryMetadata.register(registry);
	}
}

Qué beans están registrados para el enlace de atributos

ConfigurationPropertiesBinder.Factory se
utiliza principalmente para crear instancias de objetos ConfigurationPropertiesBinder

ConfigurationPropertiesBinder
ConfigurationPropertiesBinder es equivalente a una clase de herramienta para el enlace de propiedades entre elementos de configuración y clases de configuración

ConfigurationPropertiesBindingPostProcessor
Cuando se inicializa el bean, pasará por el postprocesador y descubrirá Menthdsi la clase o la marca en la clase está marcada @ConfigurationProperties, y si existe, llamará ConfigurationPropertiesBinderal bean para la vinculación de propiedades.

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    
    
	bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
	return bean;
}

org.springframework.boot.context.properties.ConfigurationPropertiesBean # get (applicationContext, bean, beanName)

public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) {
    
    
		Method factoryMethod = findFactoryMethod(applicationContext, beanName);
		return create(beanName, bean, bean.getClass(), factoryMethod);
}

private static ConfigurationPropertiesBean create(String name, Object instance, Class<?> type, Method factory) {
    
    
		ConfigurationProperties annotation = findAnnotation(instance, type, factory, ConfigurationProperties.class);
		if (annotation == null) {
    
    
			return null;
		}
		Validated validated = findAnnotation(instance, type, factory, Validated.class);
		Annotation[] annotations = (validated != null) ? new Annotation[] {
    
     annotation, validated }
				: new Annotation[] {
    
     annotation };
		ResolvableType bindType = (factory != null) ? ResolvableType.forMethodReturnType(factory)
				: ResolvableType.forClass(type);
		Bindable<Object> bindTarget = Bindable.of(bindType).withAnnotations(annotations);
		if (instance != null) {
    
    
			bindTarget = bindTarget.withExistingValue(instance);
		}
		return new ConfigurationPropertiesBean(name, instance, annotation, bindTarget);
	}

¿Por qué el método de vinculación de propiedades 1 en el ejemplo tiene éxito sin abrir @EnableConfigurationProperties?

Si desea utilizar la función de enlace de propiedad (anotación) en SpringBoot, debe activar la anotación @EnableConfigurationProperties, pero la función de anotación se ha habilitado de forma predeterminada en SpringBoot, y muchas clases de configuración han habilitado la función de anotación, por lo que no debe ' t necesita al desarrollador mismo. El código de visualización está activado.
Inserte la descripción de la imagen aquí

¿El uso de @EnableConfigurationProperties en varios lugares del proyecto provocará el registro repetido de beans importados?

Cuando esta anotación está activada, al registrar el posprocesamiento del enlace de propiedad en Spring, primero determinará si se ha registrado para evitar el registro repetido del mismo Bean
para evitar el registro repetido de las clases de configuración
org.springframework.boot. context.properties.EnableConfigurationPropertiesImportSelector. ConfigurationPropertiesBeanRegistrar

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 void register(BeanDefinitionRegistry registry,
				ConfigurableListableBeanFactory beanFactory, Class<?> type) {
    
    
			String name = getName(type);
			//避免配置类被重复注解
			if (!containsBeanDefinition(beanFactory, name)) {
    
    
				registerBeanDefinition(registry, name, type);
			}
		}
		//......
	}

Evite el registro repetido de postprocesadores
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor # register

public static void register(BeanDefinitionRegistry registry) {
    
    
		Assert.notNull(registry, "Registry must not be null");
		//判断ConfigurationPropertiesBindingPostProcessor是否已经注册
		if (!registry.containsBeanDefinition(BEAN_NAME)) {
    
    
			GenericBeanDefinition definition = new GenericBeanDefinition();
			definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
			definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN_NAME, definition);
		}
		ConfigurationPropertiesBinder.register(registry);
	}

Evite el registro repetido de clases de herramientas vinculantes
org.springframework.boot.context.properties.ConfigurationPropertiesBinder # register

static void register(BeanDefinitionRegistry registry) {
    
    
        //判断ConfigurationPropertiesBinder.Factory是否已经注册,
		if (!registry.containsBeanDefinition(FACTORY_BEAN_NAME)) {
    
    
			GenericBeanDefinition definition = new GenericBeanDefinition();
			definition.setBeanClass(ConfigurationPropertiesBinder.Factory.class);
			definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(ConfigurationPropertiesBinder.FACTORY_BEAN_NAME, definition);
		}
		//判断ConfigurationPropertiesBinder是否已经注册,
		if (!registry.containsBeanDefinition(BEAN_NAME)) {
    
    
			GenericBeanDefinition definition = new GenericBeanDefinition();
			definition.setBeanClass(ConfigurationPropertiesBinder.class);
			definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			definition.setFactoryBeanName(FACTORY_BEAN_NAME);
			definition.setFactoryMethodName("create");
			registry.registerBeanDefinition(ConfigurationPropertiesBinder.BEAN_NAME, definition);
		}
	}

Principio de implementación de @ConfigurationPropertiesScan

Después de SpringBoot2.2, si desea que una clase de configuración con solo @ConfigurationProperties anotada se registre como un bean, puede @ConfigurationPropertiesScanhabilitarla a través de anotaciones. Ya no es necesario @Componentutilizarlos juntos.

Principio de realización

  1. Esta anotación se importa al contenedor Spring usando la anotación @Importorg.springframework.boot.context.properties.ConfigurationPropertiesScanRegistrar
  2. Esta clase implementa la ImportBeanDefinitionRegistrarinterfaz y Spring volverá a llamar al método de la interfaz durante el proceso de inicio.
  3. ConfigurationPropertiesScanRegistrarEscaneará la @ConfigurationPropertiesclase marcada a través del escaneo de paquetes
  4. Atravesando la @ConfigurationPropertiesclase etiquetada escaneada , las @Componentclases etiquetadas de exclusión , la configuración de la clase para evitar que se vuelva a registrar, se registra como Bean, beanName es prefix+配置类全类名.
  5. Cuando la clase de configuración se registra como un bean, el @EnableConfigurationPropertiespostprocesador registrado puede vincular sus propiedades.
class ConfigurationPropertiesScanRegistrar implements ImportBeanDefinitionRegistrar {
    
    
    //部分代码忽略...
	
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    
	    //获取包扫描范围,默认扫描@ConfigurationPropertiesScan所在类的包和子包
		Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
		//执行包扫描,只扫描被@ConfigurationProperties标记的类
		scan(registry, packagesToScan);
	}

	private void register(ConfigurationPropertiesBeanRegistrar registrar, Class<?> type) {
    
           
	    //如果被扫描到的类被标记了@Component注解,则不注册,否则会重复注册,但是由于beanName不通,会导致重复注册.
		if (!isComponent(type)) {
    
    
		    //注册bean,bean的名称为prefix+配置类全类名
			registrar.register(type);
		}
	}
    
	private boolean isComponent(Class<?> type) {
    
    
		return MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY).isPresent(Component.class);
	}

}

Supongo que te gusta

Origin blog.csdn.net/u013202238/article/details/107133200
Recomendado
Clasificación