In the previous analysis, the Spring Framework has been committed to solving a problem, that is, how to make bean management easier, and how to make developers pay less attention to the configuration of some basic beans, so as to realize automatic assembly. Therefore, the so-called automatic assembly is actually how to automatically load beans into the Ioc container.
In fact, in spring 3.x, the emergence of Enable module driven annotation has a certain prototype of automatic assembly, and the real realization of this mechanism is the emergence of conditional conditional annotation in spirng 4.x. ok, let's take a look at the automatic assembly of spring boot.
Demonstration of automatic assembly
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
spring: redis: host: 127.0.0.1 port: 6379
@Autowired private RedisTemplate<String,String>redisTemplate;
Add the starter in the following order, and then add the configuration. You can use RedisTemplate? Have you thought about a question, why can RedisTemplate be injected directly? When was it added to the Ioc container? This is automatic assembly. Automatic assembly enables the dependent package related bean s under the classpath to be automatically loaded into the Spring Ioc container. How do you do this?
In depth analysis of EnableAutoConfiguration
The main function of EnableAutoConfiguration is to help springboot applications load all qualified @ Configuration configurations into the IoC container created and used by the current springboot.
Returning to the enable autoconfiguration annotation, we find that its import is like this
@Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {
However, judging from the import annotation above EnableAutoCOnfiguration, this does not introduce another Configuration. It's an ImportSelector. What is this?
What is AutoConfigurationImportSelector?
The Enable annotation can not only realize the integration of multiple configurations as the case demonstrated above, but also realize some complex scenarios. For example, different types of bean s can be activated according to the context, and the @ Import annotation can configure three different class es
- The first is based on ordinary beans or beans with @ Configuration, as demonstrated earlier
- Implement the ImportSelector interface for dynamic injection
Implement the ImportBeanDefinitionRegistrar interface for dynamic injection
CacheService
public class CacheService {
}
LoggerService
public class LoggerService {
}
EnableDefineService
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited --Allow to be inherited @Import({GpDefineImportSelector.class}) public @interface EnableDefineService { String[] packages() default ""; }
GpDefineImportSelector
public class GpDefineImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { //Gets the details of the specified annotation. We can return different class es according to the attributes configured in the annotation, //Thus, the purpose of dynamically opening different functions can be achieved annotationMetadata.getAllAnnotationAttributes(EnableDefineService.class.getName(),true) .forEach((k,v) -> { log.info(annotationMetadata.getClassName()); log.info("k:{},v:{}",k,String.valueOf(v)); }); return new String[]{CacheService.class.getName()}; } }
EnableDemoTest
@SpringBootApplication @EnableDefineService(name = "gupao",value = "gupao") public class EnableDemoTest { public static void main(String[] args) { ConfigurableApplicationContext ca=SpringApplication.run(EnableDemoTest.class,args); System.out.println(ca.getBean(CacheService.class)); System.out.println(ca.getBean(LoggerService.class)); } }
After understanding the basic principle of the selector, it is very simple to analyze the principle of AutoConfigurationImportSelector later. It is also the dynamic loading of bean s in essence.
@Implementation principle of EnableAutoConfiguration annotation
Understanding EnableAutoConfiguration is easier after understanding ImportSelector and ImportBeanDefinitionRegistrar
It imports the configuration class of the bean provided by the third party through import: AutoConfigurationImportSelector
@Import(AutoConfigurationImportSelector.class)
From the name, you can guess that it is based on ImportSelector to realize the loading function based on dynamic bean s. We talked about the working principle of Springboot @Enable * annotation before. The array (full class name of the class) returned by selectImports of the ImportSelector interface will be included in the spring container.
You can guess that the implementation principle here must be the same. Locate the selectImports method in the AutoConfigurationImportSelector class
selectImports
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } // Load AutoConfigurationMetadata from the configuration file (spring-autoconfigure-metadata.properties) AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); // Get all candidate configuration classes EnableAutoConfiguration AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry( autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } //Gets the attribute in the meta annotation AnnotationAttributes attributes = getAttributes(annotationMetadata); //Use springfactoryesloader to load META-INF\spring.factories under the classpath path, //Key = value corresponding to org.springframework.boot.autoconfigure.enableautoconfiguration List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); //duplicate removal configurations = removeDuplicates(configurations); //Apply exclusion property Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); //Filter: check the annotation @ ConditionalOnClass on the candidate configuration class. If the required class does not exist, the candidate class will be filtered and not loaded configurations = filter(configurations, autoConfigurationMetadata); //Broadcast event fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
In essence, EnableAutoConfiguration will help springboot applications load all @ Configuration compliant configurations into the IoC container created by the current springboot, which is supported by a tool class springfactoryesloader provided by the Spring framework. The @ Conditional annotation provided by Spring is used to selectively filter the bean s to be loaded
SpringFactoriesLoader
In order to supplement the foundation for you, I'll briefly analyze the use of the tool class SpringFactoriesLoader. In fact, its principle is the same as that of SPI mechanism in java, but it is better than SPI in that it does not load all classes at one time, but according to the key.
First, the role of springfactoryesloader is to load the corresponding classes into the spring IoC container according to the key from the classpath/META-INF/spring.factories file. Next, let's practice it
Create external project jar
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.13.RELEASE</version> </dependency>
Create bean and config
public class GuPaoCore { public String study(){ System.out.println("good good study, day day up"); return "GuPaoEdu.com"; } } @Configuration public class GuPaoConfig { @Bean public GuPaoCore guPaoCore(){ return new GuPaoCore(); } }
Create another project (spring boot)
Package the previous project into a jar, and the current project depends on the jar package
<dependency> <groupId>com.gupaoedu.practice</groupId> <artifactId>Gupao-Core</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
Get the properties in the dependent package through the following code
The running result will report an error because GuPaoCore is not loaded by Spring's IoC container, that is, it is not imported by EnableAutoConfiguration
@SpringBootApplication public class SpringBootStudyApplication { public static void main(String[] args) throws IOException { ConfigurableApplicationContext ac=SpringApplication.run(SpringBootStudyApplication.class, args); GuPaoCore gpc=ac.getBean(GuPaoCore.class); System.out.println(gpc.study()); } }
Solution
Create a new folder META-INF under Gupao core project resources, and a new spring.factories file under the folder. In the file, key is the full path of the custom configuration class EnableAutoConfiguration, and value is the full path of the configuration class
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gupaoedu.practice.GuPaoConfig
Repackage and rerun the SpringBootStudyApplication class.
It can be found that the class we write is loaded in.
@Implementation principle of EnableAutoConfiguration annotation
Understanding EnableAutoConfiguration is easier after understanding ImportSelector and ImportBeanDefinitionRegistrar
It imports the configuration class of the bean provided by the third party through import: AutoConfigurationImportSelector
@Import(AutoConfigurationImportSelector.class)
From the name, you can guess that it is based on ImportSelector to realize the loading function based on dynamic bean s. We talked about the working principle of Springboot @Enable * annotation before. The array (full class name of the class) returned by selectImports of the ImportSelector interface will be included in the spring container.
You can guess that the implementation principle here must be the same. Locate the selectImports method in the AutoConfigurationImportSelector class
selectImports
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } // Load AutoConfigurationMetadata from the configuration file (spring-autoconfigure-metadata.properties) AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); // Get all candidate configuration classes EnableAutoConfiguration AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry( autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } //Gets the attribute in the meta annotation AnnotationAttributes attributes = getAttributes(annotationMetadata); //Use springfactoryesloader to load META-INF\spring.factories under the classpath path, //Key = value corresponding to org.springframework.boot.autoconfigure.enableautoconfiguration List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); //duplicate removal configurations = removeDuplicates(configurations); //Apply exclusion property Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); //Filter: check the annotation @ ConditionalOnClass on the candidate configuration class. If the required class does not exist, the candidate class will be filtered and not loaded configurations = filter(configurations, autoConfigurationMetadata); //Broadcast event fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
In essence, EnableAutoConfiguration will help springboot applications load all @ Configuration compliant configurations into the IoC container created by the current springboot, which is supported by a tool class springfactoryesloader provided by the Spring framework. The @ Conditional annotation provided by Spring is used to selectively filter the bean s to be loaded
Copyright notice: unless otherwise stated, all articles on this blog adopt CC BY-NC-SA 4.0 license agreement. Reprint please indicate from Mic to take you to learn architecture!
If this article is helpful to you, please pay attention and praise. Your persistence is the driving force of my continuous creation. Welcome to WeChat public official account for more dry cargo.