Explain the principle of Springboot automatic assembly in simple terms

Springboot automatic assembly principle

To put it simply, Springboot automatic assembly is to automatically assemble beans into Ioc containers. Before understanding Springboot automatic assembly, let's talk about how Spring injects and configures beans.

Spring Ioc/DI

The full names of loC (Inversion of Control) and DI (Dependency Injection) are Inversion of Control and Dependency Injection respectively. How to understand these two concepts?

loC

loC (Inversion of Control) actually hosts the life cycle of the object in the Spring container, and inversion means that the object acquisition method is reversed. This concept may not be well understood. Let's understand it through two pictures The role of loC, the figure below shows the traditional way of creating objects. If the client class needs to use User and UserDetail, it needs to be built through new. This way will make the coupling between codes very high.

image-20220502090416402

After using the Spring loC container, the client class does not need to create these objects through new. Figure 2 adds the loC container on the basis of Figure 1. When the client class obtains User and UserDetail object instances, it no longer uses new to create build, but directly from the loC container. So when were the objects in the Spring loC container built? In the early Spring, beans were defined mainly through XML, and Spring would parse the XML file and load the defined beans into the loC container.

figure 2

FROM

DI (Dependency Inject), that is, dependency injection, simply understood that the loC container dynamically injects certain dependencies into components during runtime. In order to fully understand the concept of DI, let's continue to look at Figure 2 above. Two beans are described in the Spring configuration file, namely User and UserDetail. These two objects have no relationship from the configuration file, but in fact, from the relationship diagram of the class, there is an aggregation relationship between them. If we want this aggregation relationship to be automatically injected in the loC container, that is, like this code, get the UserDetail instance through user.getUserDetail, what should we do?

ApplicationContext context = new FileSystemXmlApplicationContext("...");
User user = context.getBean(User.class);
UserDetail userdetail = user.getUserDetail();

​ In fact, we only need to describe the dependencies between beans in the Spring configuration file. When the Ioc container parses the configuration file, it will inject according to the dependencies of the beans. This process is dependency injection

<bean id="user" class="User">
	<property name="userDetail" ref="userDetail"/>
</bean>
<bean id="userDetail" class="UserDetail"/>

There are three ways to implement dependency injection, namely interface injection, constructor injection and setter method injection. But now it is basically based on annotations to describe the dependencies between beans, such as @Autowired, @Inject and @Resource.

Upgrade of Bean assembly method

​ The XML-based configuration method completes the description and management of the object declaration cycle well, but as the project scale continues to expand, the XML configuration gradually increases, making configuration files difficult to manage. On the other hand, the dependencies in the project become more and more complex, and the configuration files become difficult to understand. At this time, there is an urgent need for a way to solve this kind of problem.
​ With the annotation support brought by JDK 1.5, starting from Spring 2.x, you can use annotations to declare and inject beans, which greatly reduces the amount of XML configuration.
After Spring is upgraded to 3.x, it provides the capability of JavaConfig, which can completely replace XML, and complete Bean injection through Java code. Therefore, the Spring Framework or Spring Boot we use now does not see the existence of XML configuration.

XML configuration method

<?xm1 version="1.0" encoding="UTF-8"?>
<beans xmIns="http:/ /www . springframework.org/schema/beans"
	   xmIns:xsi="http: / / www. w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">
<! --bean的描述-->
</beans>

After using the javaConfig form, you only need to use the @Configuration annotation, which is equivalent to the XML configuration form.

@Configuration
public class Springconfigclass{
    
    
// Bean 的描述
}

Changes in how beans are loaded

XML-based loading method

<bean id="beanDefine" class="com.gupaoedu.spring.BeanDefine"/>

Based on the configuration form of JavaConfig, an object can be injected into the loC container through the @Bean annotation. By default, the method name is used as the id of the bean.

@Configuration
public class SpringConfigclass{
    
    
    @Bean
	public BeanDefine beanDefine(){
    
    
    	return new BeanDefine();
    }
}

Dependency Injection Changes

In XML form, dependency injection can be done in three ways, such as setter injection

<bean id="beanDefine" class="com.scmpe.spring.BeanDefine">
	<property name="dependencyBean" ref="dependencyBean"/>
</bean>
<bean id="dependencyBean" class="com.scmpe.spring.dependencyBean"/>

In JavaConfig, it can be expressed like this

public class SpringConfigClass{
    
    
    @Bean
    public BeanDefine beanDefine() {
    
    
        public BeanDefine=new BeanDefine();
        beanDefine.setDependencyBean(dependencyBean());
        return beanDefine;
    }
	@Bean
    public DependencyBean dependencyBean() {
    
    
        return new DependencyBean();
    }
}

Other configuration changes

In addition to the previous configurations, there are common configurations, such as:

  • @ComponentScan corresponds to <context:component-scan base-package=""> in XML form, it will scan the classes annotated with @Service, @Repository, @Controller, @Component, etc. under the specified package path, and load these classes into IoC container.

  • @Import corresponds to XML form, importing other configuration files.
    Although assembling beans through annotations can reduce the problems caused by XML configuration to a certain extent, the essential problems are still not resolved, such as:

  • Too much dependence. Spring can integrate almost all commonly used technical frameworks, such as JSON, MyBatis, Redis, Log, etc. The versions of different dependent packages can easily lead to version compatibility issues.

  • Too many configurations. Taking Spring as an example to integrate MyBatis using JavaConfig, you need to configure annotation drivers, configure data sources, configure MyBatis, configure transaction managers, etc. These are just the basic configurations required to integrate a technical component. There are many such configurations in a project. Need to do a lot of similar repetitive work.

  • Running and deploying is cumbersome. The project needs to be packaged first, and then deployed to the container.

How to make developers no longer need to pay attention to these issues and focus on business? Fortunately, Spring Boot was born.

SpringBoot

Spring Boot is not a new technical framework. Its main function is to simplify the development of Spring applications. Developers only need to use a small amount of code to create a production-level Spring application. The core idea to achieve this goal is "agreement Better than Configuration (Convention over Configuration)".

In Spring Boot, the idea of ​​convention over configuration is mainly reflected in the following aspects (including but not limited to)

  • Conventions for Maven directory structures.
  • Spring Boot's default configuration file and the conventions of configuration properties in the configuration file.
  • For Spring MVC dependencies, it automatically depends on the built-in Tomcat container.
  • Wiring is done automatically for Starter components.

The core of Spring Boot

Spring Boot is built based on the Spring Framework system, so there is nothing new about it, but if you want to learn Spring Boot well, you must know its core:

  • The Starter component provides out-of-the-box components.

  • Automatic assembly, automatically completes the assembly of beans according to the context.

  • Actuator, monitoring of Spring Boot applications.

  • Spring Boot CLI, quickly build Spring Boot applications based on command line tools.

Among them, the core part should be automatic assembly, and the core part of the Starter component is also realized based on automatic assembly.

SpringBoot automatic assembly principle

A brief overview of Springboot automatic assembly is to automatically assemble beans into Ioc containers. Next, let's learn about automatic assembly through an example of Spring boot integrating Redis.

  • Add Stater dependency

    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  • Configure the data source of Redis in application.properties

spring.redis.host=localhost
spring.redis.port=6379
  • Use RedisTemplate in TestController to implement Redis operations
@Restcontroller
public class HelloController {
    
    
	@Autowired
	RedisTemplate<String, string> redisTemplate;
    @GetMapping( "/hello")
    public String hello(){
    
    
    	redisTemplate.opsForValue().set("key" , "value");return "He1lo world";
    }
}

In this example, we did not inject the RedisTemplate into the Ioc container, but the HelloController can directly use @Autowired to inject the redisTemplate instance, which means that there is already a RedisTemplate in the Ioc container. This is the automatic assembly of Spring boot. You only need to add a Starter dependency to complete the automatic injection of beans related to the dependent component. It is not difficult to guess that the implementation of this mechanism must be based on a certain agreement or specification. You only need the Starter component to conform to Springboot Automatic assembly can be realized by specifying the specification of the automatic assembly agreement in

Implementation of automatic assembly

Automatic assembly is enabled in Springboot through the @EnableAutoConfiguration annotation, which is declared in the startup class annotation @SpringbootApplication.

@SpringBootApplication
public class SpringBootDemoApplication {
    
    
	public static void main(String[] args){
    
    
		SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}

Enter @SpringBootApplication to see the @EnableAutoConfiguration annotation

@Target(ElementType. TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
    
    @Filter(type = FilterType.CUSTOM,classes =TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public interface SpringBootApplication {
    
    
...
}

Here we first introduce the @Enable annotation. The Spring 3.1 version began to support the @Enable annotation, and its main function is to assemble the beans of related components into the Ioc container. The @Enable annotation further improves JavaConfig, which reduces the amount of configuration and the difficulty of use for developers who use Spring Framework. For example, common @Enable annotations include @EnableWebMvc, @EnableSheduling, etc.

We said before that if you complete the Bean loading based on the form of javaConfig, you must use the @Configuration annotation and the @Bean annotation. And @Enable is essentially the encapsulation of these two annotations. If you pay close attention to these annotations, you will find that they all carry an @Import annotation, such as @EnableScheduling.

@Target({
    
    ElementType. TYPE})
@Retention(RetentionPolicy. RUNTIME)
@Import({
    
    SchedulingConfiguration.class})Documented
public @interface EnableScheduling {
    
    }

After using the @Enable annotation, Spring will parse to the configuration class imported by @Import, so as to realize the Bean assembly according to the description of this configuration class. Then you can think about whether the implementation principle of @EnableAutoConfiguration is the same.

EnableAutoConfiguration

Entering the @EnableAutoConfiguration annotation, you can see that in addition to the @Import annotation, there is an additional @AutoConfigurationPackage annotation (the function of this annotation is to scan all components under the package and its subpackages of the class that uses the annotation to Spring Ioc in the container).

Moreover, what is imported in the @Import annotation is not a Configuration configuration class, but an AutoConfigurationImportSelector class.

@Target(ElementType. TYPE)
@Retention( RetentionPolicy.RUNTIME)@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration

However, no matter what AutoConfigurationImportSelector is, it will definitely implement the import of configuration classes. As for the specific implementation method, we need to continue to analyze it.

AutoConfigurationImportSelector

AutoConfigurationImportSelector implements ImportSelector. It has only one selectorImports abstract method and returns a String array. In this array, you can specify the class that needs to be assembled into the Ioc container. When an ImportSelector implementation class is imported in @Import, the implementation class will be The returned Class names are all loaded into the Ioc container.

public interface ImportSelector {
    
    
    String[] selectImports(AnnotationMetadata var1);
}

Unlike @Configuration, ImportSelector can implement batch assembly, and can also implement selective assembly of beans through logical processing, that is, it can be determined by the context to be initialized by the Ioc container. Next, let's take you through a simple example to understand the use of ImportSelector.

  • First create two classes, we need to assemble these two classes into the Ioc container.
public class Firstclass {
    
    
    
}
public class Secondclass {
    
    
    
}
  • Create an implementation class of ImportSelector, and add the defined two beans into the String array in the implementation class, which means that these two beans will be assembled into the Ioc container.
public class GpImportSelector implements ImportSelector {
    
    
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    
    
		return new String[]{
    
    FirstClass.class.getName() ,SecondClass.class.getName()};
    }
}
  • In order to simulate EnableAutoConfiguration, we can customize a similar annotation and import GpImportSelecto through @Import.
@Target(ElementType. TYPE)
@Retention(RetentionPolicy. RUNTIME)@Documented
@Inherited
@AutoConfigurationPackage
@Import(GpImportSelector.class)
public @interface EnableAutoImport {
    
    
}
  • Create a startup class and use @EnableAutoImport annotation on the startup class to get the First object instance from the Ioc container through ca.getBean.
@SpringBootApplication@EnableAutoImport
public class ImportSelectorMain {
    
    
public static void main(String[ ] args){
    
    
	ConfigurableApplicationcontext ca=SpringApplication.run(ImportSelectorMain.class, args);
	FirstClass fc=ca.getBean(Firstclass.class);
}

The advantage of this implementation compared to @Import(*Configuration.class) lies in the flexibility of assembly, and it can also realize batch assembly. For example, GpImportSelector can also directly set multiple Configuration classes in String. Since a configuration class represents a batch of Bean declarations of a certain technical component, it is only necessary to scan to the corresponding configuration class under the specified path in the process of automatic assembly. That's it.

Analysis of automatic assembly principle

Based on the previous analysis, it can be guessed that the core of automatic assembly is to scan the files in the agreed directory for analysis. After the analysis is completed, import the obtained Configuration configuration class through ImportSelector to complete the automatic assembly of beans. Then we prove this conjecture by analyzing the implementation of AutoConfigurationImportSelector.

Locate the selectImport method in AutoConfigurationImportSelector, which is the implementation of the ImportSelector interface. There are two main functions in this method:

  • AutoConfigurationMetadataLoader.loadMetadata loads auto-assembled conditional metadata from META-INF/spring-autoconfigure-metadata.properties. Simply put, only beans that meet the conditions can be assembled.

  • Collect all qualified configuration classes autoConfigurationEntry.getConfigurations() to complete automatic assembly.

@Override
public String[ ] selectImports(AnnotationMetadata annotationMetadata){
    
    
    if ( !isEnabled(annotationMetadata)){
    
    
    return NO_IMPORTS;
    }
    AutoConfigurationMetadata autoConfigurationMetadata = 	AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);

    AutoConfigurationEntry autoconfigurationEntry =
    getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);

    return Stringutils.toStringArray(autoConfigurationEntry.getConfigurations());
}

There is a problem here SpringApplication.run(...). How to transfer selectImports()the method to the loading process of the method is probably like this:

SpringApplication.run(…) ->

AbstractApplicationContext.refresh() ->

invokeBeanFactoryPostProcessors(…) ->

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(…) ->

ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(…) ->

AutoConfigurationImportSelector.selectImports

​ Let's focus on the analysis of the configuration class collection method getAutoConfigurationEntry. Combining the previous functions of Starter, it is not difficult to guess that this method should scan the files under the specified path and analyze to get the configuration class that needs to be assembled, and SpringFactoriesLoader is used here. This content I will explain it later as the code expands. A brief analysis of this code, it mainly does several things:

  • getAttributes Get the attributes exclude, excludeName, etc. in the @EnableAutoConfiguration annotation.
  • getCandidateConfigurations Gets all autowired configuration classes.
  • removeDuplicates Remove duplicate configuration items.
  • getExclusions removes configuration classes that do not need to be automatically assembled according to the exclude and other attributes configured in the @EnableAutoConfiguration annotation.
  • fireAutoConfigurationImportEvents Broadcast events.
  • Finally, return the configuration class collection after multi-layer judgment and filtering.
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadataautoConfigurationMetadata,AnnotationMetadata annotationMetadata){
    
    
    if(!isEnabled(annotationMetadata)){
    
    
    	return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getcandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions( annotationMetadata,attributes);
    checkExcludedclasses( configurations,exclusions);
    configurations. removeAl1(exclusions);
    configurations = filter(configurations,autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations,exclusions);
    return new AutoConfigurationEntry(configurations,exclusions);
}

In general, it obtains all configuration classes first, and obtains the final configuration classes that need to be automatically assembled through operations such as deduplication and exclude. What needs to be focused on here is getCandidateConfigurations, which is the core method for obtaining configuration classes.

protected list<String> getCandidateConfigurations(AnnotationMetadata metadata,AnnotationAttributes attributes) {
    
    
	List<String> configurations =
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryclass(),getBeanclassLoader());
	Assert.notEmpty(configurations,"No auto configuration classes found inMETA-INF/spring.factories. If you " + "are using a custom packaging,make sure that file is correct.");
return configurations;
}

SpringFactoriesLoader is used here, which is a conventional loading method provided by Spring, similar to SPI in Java. Simply put, it scans the META-INF/spring.factories file under the classpath, and the data in the spring.factories file is stored in the form of Key=Value, and SpringFactoriesLoader.loadFactoryNames will get the corresponding value according to the Key. Therefore, in this scenario, Key corresponds to EnableAutoConfiguration, and Value is multiple configuration classes, that is, the value returned by the getCandidateConfigurations method.

Open RabbitAutoConfiguration, you can see that it is a configuration class based on JavaConfig.

@Configuration(proxyBeanMethods = false)
@Conditional0nclass({
    
     RabbitTemplate.class,Channe1.class})
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration

In addition to the basic @Configuration annotation, there is also a @ConditionalOnClass annotation. The purpose of this conditional control mechanism here is to determine whether there are two classes, RabbitTemplate and Channel, under the classpath. If so, register the current configuration class to the IloC container. In addition, @EnableConfigurationProperties is a property configuration, which means that we can configure RabbitMQ parameters in application.properties according to the convention, and these configurations will be loaded into RabbitProperties. In fact, these things are all functions of Spring itself.

  • The import of configuration classes is realized through @Import(AutoConfigurationImportSelector), but this is not a single configuration class assembly in the traditional sense
  • The AutoConfigurationImportSelector class implements the ImportSelector interface and rewrites the method selectImports, which is used to realize the assembly of selective batch configuration classes.
  • Through the SpringFactoriesLoader mechanism provided by Spring, scan META-INF/spring.factories under the classpath path, and read the configuration classes that need to be automatically assembled.
  • Through conditional screening, configuration classes that do not meet the conditions are removed, and automatic assembly is finally completed. The import of configuration classes is realized through @Import(AutoConfigurationImportSelector), but this is not a single configuration class assembly in the traditional sense.
  • The AutoConfigurationImportSelector class implements the ImportSelector interface and rewrites the method selectImports, which is used to realize the assembly of selective batch configuration classes.
  • Through the SpringFactoriesLoader mechanism provided by Spring, scan META-INF/spring.factories under the classpath path, and read the configuration classes that need to be automatically assembled.
  • Through conditional screening, configuration classes that do not meet the conditions are removed, and automatic assembly is finally completed.

Guess you like

Origin blog.csdn.net/qq_45473439/article/details/124546390