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 @EnableConfigurationProperties
y @ConfigurationProperties
anotar para lograr. @ConfigurationPropertiesScan
Las 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 @ConfigurationProperties
y @EnableConfigurationProperties
dos 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
@ConfigurationProperties
La 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á @ConfigurationProperties
marcada, el valor del elemento de configuración no estará vinculado a su atributo, ni se registrará como un Bean. utilice la @Component
anotació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
@ConfigurationProperties
Las clases de procesamiento relevantes no se inyectan en el contenedor Spring y solo sirven como marcadores relevantes. La lógica de procesamiento relevante es @EnableConfigurationProperties
completada por las clases de procesamiento importadas. @ConfigurationProperties
Las 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
@EnableConfigurationProperties
Hay dos funciones principales
-
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@ConfigurationProperties
requiere el uso de@Component
anotaciones; 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. -
@ConfigurationProperties
Registre una clase de configuración marcada como Bean of Spring. Las clases que no están marcadas y@ConfigurationProperties
anotadas no se pueden usar como@EnableConfigurationProperties
pará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á Menthd
si la clase o la marca en la clase está marcada @ConfigurationProperties
, y si existe, llamará ConfigurationPropertiesBinder
al 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.
¿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 @ConfigurationPropertiesScan
habilitarla a través de anotaciones. Ya no es necesario @Component
utilizarlos juntos.
Principio de realización
- Esta anotación se importa al contenedor Spring usando la anotación @Import
org.springframework.boot.context.properties.ConfigurationPropertiesScanRegistrar
- Esta clase implementa la
ImportBeanDefinitionRegistrar
interfaz y Spring volverá a llamar al método de la interfaz durante el proceso de inicio. ConfigurationPropertiesScanRegistrar
Escaneará la@ConfigurationProperties
clase marcada a través del escaneo de paquetes- Atravesando la
@ConfigurationProperties
clase etiquetada escaneada , las@Component
clases etiquetadas de exclusión , la configuración de la clase para evitar que se vuelva a registrar, se registra como Bean, beanName esprefix+配置类全类名
. - Cuando la clase de configuración se registra como un bean, el
@EnableConfigurationProperties
postprocesador 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);
}
}