preface
In the SpringBoot project, we often need to bind some configuration items with specific prefixes to a configuration class. At this time, we can use @ EnableConfigurationProperties and @ ConfigurationProperties annotations to implement. In SpringBoot 2 The @ ConfigurationPropertiesScan annotation is also added in 2.0 to help us simplify the registration of configuration classes as a Bean. The following mainly explains the use and source code implementation of these three annotations.
Usage example: bind configuration item to configuration class
There are the following configuration items. We use @ ConfigurationProperties and @ EnableConfigurationProperties annotation methods to bind them to the configuration class, and these configuration classes will actually be registered as beans
#Bind to configuration class 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 #Bind to configuration item class 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
Method 1: use @ ConfigurationProperties
@In fact, the ConfigurationProperties annotation only specifies the prefix corresponding to the property in the Configuration class. When a Configuration class is only marked by @ ConfigurationProperties, the value of the Configuration item will not be bound to its property or registered as a Bean. You need to use @ Component annotation or @ component subclass annotation (for example, @ Configuration).
Example: configuration class com example. demo. config. ShardingProperties
@Component @ConfigurationProperties(prefix = "sharding") public class ShardingProperties { private Integer defaultDSIndex; private String column; private List<MyDataSourceProperties> dataSources; //Ignore other fields and getter/setter methods } public class MyDataSourceProperties { private String name; private String driverClassName; private String jdbcUrl; private String username; private String password; private Long connectionTimeout; }
Method 2: use @ EnableConfigurationProperties
In addition to using mode 1, you can also bind property values by specifying specific configuration classes through @ EnableConfigurationProperties(value={xxx.calss}).
Example: configuration class com example. demo. config. MyBatisProperties
@ConfigurationProperties(prefix = "mybatis") public class MyBatisProperties { private String basePackage; private String mapperLocations; private String typeAliasesPackage; private String markerInterface; //Ignore other fields and getter/setter methods } @EnableConfigurationProperties({MyBatisProperties.class}) @Configuration public class EnableMyBatisConfig { }
Use the values in the configuration class
/** 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() { //Print the values mapped from the configuration file in the configuration class System.out.println(JsonUtil.toJson(shardingProperties)); System.out.println(JsonUtil.toJson(myBatisProperties)); } }
@ConfigurationProperties function
@ConfigurationProperties will not inject relevant processing classes into the Spring container, but only play the role of relevant tags. The relevant processing logic is completed by the processing class imported by @ EnableConfigurationProperties. Classes that are only annotated with @ ConfigurationProperties will not be registered as beans by default
public @interface ConfigurationProperties { //Equivalent to prefix, specifies the prefix of attribute binding @AliasFor("prefix") String value() default ""; @AliasFor("value") String prefix() default ""; //Whether to ignore the exception when an error occurs when the attribute value is bound to a field. It is not ignored by default, and an exception will be thrown boolean ignoreInvalidFields() default false; //When the configuration item is bound to the attribute in the entity class, the corresponding field is not found. Whether to ignore it. It is ignored by default and no exception is thrown. //If ignoreInvalidFields = true, ignoreUnknownFields = false will no longer take effect. It may be a bug in SpringBoot boolean ignoreUnknownFields() default true; }
@Implementation principle of EnableConfigurationProperties
@EnableConfigurationProperties have two main functions
- Register the post processor ConfigurationPropertiesBindingPostProcessor, which is used to bind property values to the properties in the Bean when the Bean is initialized. This is why the first way to use @ ConfigurationProperties requires @ Component annotation. Otherwise, it is not a Bean and cannot be processed by the post processor of Spring, so the property value cannot be bound.
- Register a configuration class marked with @ ConfigurationProperties as a Bean of Spring. Classes not marked with @ ConfigurationProperties annotation cannot be used as parameters of @ EnableConfigurationProperties, otherwise an exception will be thrown. Using @ ConfigurationProperties alone will not register this class as a Bean
class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerInfrastructureBeans(registry); ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry); //Get the configuration class specified by the @ EnableConfigurationProperties annotation parameter and register it as a Bean //beanName is "prefix + configuration class full class name". 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()); } //Register the relevant post processor and Bean for binding static void registerInfrastructureBeans(BeanDefinitionRegistry registry) { ConfigurationPropertiesBindingPostProcessor.register(registry); BoundConfigurationProperties.register(registry); ConfigurationPropertiesBeanDefinitionValidator.register(registry); ConfigurationBeanFactoryMetadata.register(registry); } }
Which beans are registered for property binding
ConfigurationPropertiesBinder.Factory
It is mainly used to create an instance of ConfigurationPropertiesBinder object
ConfigurationPropertiesBinder
ConfigurationPropertiesBinder is equivalent to a tool class for property binding between configuration items and configuration classes
ConfigurationPropertiesBindingPostProcessor
When the bean is initialized, it will go through the post processor to find out whether the class or Menthd in the class is marked with @ ConfigurationProperties. If it exists, it will call ConfigurationPropertiesBinder to bind the properties of the bean.
@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); }
Why can property binding method 1 in the example succeed without enabling @ EnableConfigurationProperties?
If you want to use the (annotation) property binding function in SpringBoot, you must turn on the @ EnableConfigurationProperties annotation. However, the annotation function has been turned on by default in SpringBoot, and many configuration classes turn on the annotation function. Therefore, developers do not need to display the code on themselves.
Will the use of @ EnableConfigurationProperties in multiple places in the project lead to repeated registration of imported bean s?
Open this annotation. When registering the post-processing of attribute binding in Spring, you will first judge whether it has been registered to avoid registering the same Bean repeatedly
Avoid duplicate registration of configuration classes springframework. boot. context. properties. EnableConfigurationPropertiesImportSelector. ConfigurationPropertiesBeanRegistrar
public static class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //Register configuration class getTypes(metadata).forEach((type) -> register(registry, (ConfigurableListableBeanFactory) registry, type)); } //Find configuration class on annotation 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()); } //Register configuration class private void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory, Class<?> type) { String name = getName(type); //Avoid duplicate annotations for configuration classes if (!containsBeanDefinition(beanFactory, name)) { registerBeanDefinition(registry, name, type); } } //...... }
Avoid duplicate registration of post processor
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#register
public static void register(BeanDefinitionRegistry registry) { Assert.notNull(registry, "Registry must not be null"); //Determine whether the ConfigurationPropertiesBindingPostProcessor has been registered 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); }
Avoid duplicate registration of binding tool classes
org.springframework.boot.context.properties.ConfigurationPropertiesBinder#register
static void register(BeanDefinitionRegistry registry) { //Judge configurationpropertiesbinder Whether the factory has been registered, 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); } //Judge whether the ConfigurationPropertiesBinder has been registered, 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); } }
@Implementation principle of ConfigurationPropertiesScan
In springboot 2 After 2, if you want a configuration class with only @ ConfigurationProperties annotation to be registered as a bean, you can open it through @ ConfigurationProperties scan annotation. You no longer need to use it with @ Component.
Implementation principle
- This annotation uses the @ Import annotation to Import org. Org into the Spring container springframework. boot. context. properties. ConfigurationPropertiesScanRegistrar
- This class implements the ImportBeanDefinitionRegistrar interface. Spring will call back the method of this interface during startup.
- ConfigurationPropertiesScanRegistrar will scan the classes marked by @ ConfigurationProperties through package scanning
- Traverse the scanned class marked with @ ConfigurationProperties and exclude the class marked with @ Component to avoid repeated registration of the configuration class. Then register it as Bean and beanName as the full class name of prefix + configuration class.
- After the configuration class is registered as a bean, the post processor of @ EnableConfigurationProperties can bind its properties
class ConfigurationPropertiesScanRegistrar implements ImportBeanDefinitionRegistrar { //Some code ignored @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //Get the package scanning range, and scan the packages and sub packages of the class where @ ConfigurationPropertiesScan is located by default Set<String> packagesToScan = getPackagesToScan(importingClassMetadata); //Perform package scanning and scan only the classes marked by @ ConfigurationProperties scan(registry, packagesToScan); } private void register(ConfigurationPropertiesBeanRegistrar registrar, Class<?> type) { //If the scanned class is marked with @ Component annotation, it will not be registered. Otherwise, the registration will be repeated. However, due to the inaccessibility of beanName, the registration will be repeated if (!isComponent(type)) { //Register the bean. The name of the bean is the full class name of prefix + configuration class registrar.register(type); } } private boolean isComponent(Class<?> type) { return MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY).isPresent(Component.class); } }
Reprinted from: @EnableConfigurationProperties @ConfigurationProperties @ConfigurationPropertiesScan_Brucelwl's blog - CSDN blog