preface

Do you remember the fear of being dominated by SSM integration? I believe many partners have had such experience, a lot of configuration problems, all kinds of exclusion scan, import a new dependency and have to add a new configuration. Since SpringBoot, we’ve taken off! All kinds of zero configuration out of the box, and we can develop so cool, automatic configuration credit, today we will discuss the principle of SpringBoot automatic configuration.

This paper is mainly divided into three parts:

  1. SpringBoot source code commonly annotated pickups

  2. SpringBoot Boot process

  3. Principle of SpringBoot automatic configuration

1. Common annotations of SpringBoot source code

This part is mainly about SpringBoot source code often used to annotate, to clear the back of reading the source code when obstacles.

Combination of annotation

When it is possible to use several annotations on the same class in large numbers at the same time, consider using these annotations on other annotations. The annotated annotations are called composite annotations.

  • Meta-note: a note that can be added to other notes.

  • Composite annotations: The annotated annotations are called composite annotations.

@value [Spring]

@value is equivalent to the Value field in a traditional XML configuration file.

Suppose there is code:

@Component 
public class Person { 

@Value("i am name") 
private String name; 

} 
Copy the code

The above code is equivalent to the configuration file:

<bean class="Person"> 
<property name ="name" value="i am name"></property>
</bean> 
Copy the code

We know that the value in the configuration file can be:

  • literal

  • Get the value from the environment variable by ${key}

  • Get the value from the global configuration file as ${key}

  • #{SpEL}

So, we can get the specified configuration item in the global configuration file by @value (${key}).

@configurationProperties [provided by SpringBoot]

If we need to take N configuration items, and we’re going to have to take them one by one via @value, that’s a little low. We can use @ConfigurationProperties.

All properties of the class marked @ConfigurationProperties are bound to the relevant configuration items in the configuration file. After binding, we can use this class to access property values in the global configuration file.

Here’s an example:

  1. Add the following configuration to the master configuration file
person.name=kundy 
person.age=13 
person.sex=male 
Copy the code
  1. To create a configuration class, the setter and getter methods are omitted for space reasons, but they are required in real development, otherwise the injection will not succeed. Also, the @Component annotation needs to be added.
@Component 
@ConfigurationProperties(prefix = "person") 
public class Person { 

private String name; 
private Integer age; 
private String sex; 

} 
Copy the code

Here @ConfigurationProperties has a prefix parameter, which is used to specify the prefix of the configuration item in the configuration file.

  1. Test, in the SpringBoot environment, write a test method, inject the Person class, you can get the value of the configuration file through the Person object.

@import [Spring]

The @import annotation enables you to Import a normal Java class and declare it as a bean. It is primarily used to merge multiple discrete Java Config configuration classes into one larger Config class.

  • Before 4.2, the @import annotation only supported importing configuration classes.

  • Since 4.2, the @import annotation supports importing a normal Java class and declaring it as a bean.

** @import three ways to use **

  • Import plain Java classes directly.

  • Used with custom ImportSelector.

  • Cooperate with ImportBeanDefinitionRegistrar use.

1. Import common Java classes directly

  1. Create a normal Java class.
public class Circle { 

public void sayHi(a) { 
System.out.println("Circle sayHi()"); }}Copy the code
  1. Create a configuration class that does not explicitly declare any beans, and import the Circle you just created.
@Import({Circle.class}) 
@Configuration 
public class MainConfig {}Copy the code
  1. Create the test class.
public static void main(String[] args) { 

ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class); 
Circle circle = context.getBean(Circle.class); 
circle.sayHi(); 

} 
Copy the code
  1. Running results:

Circle sayHi()

You can see that we successfully retrieved the Circle object from the IOC container, proving that the Circle class we imported in the configuration class was indeed declared as a Bean.

2. Use it with a customized ImportSelector

ImportSelector is an interface with a single selectImports method that returns an array of full class names. So we can use this feature to dynamically import N beans into the container.

  1. Create the plain Java class Triangle.
public class Triangle { 

public void sayHi(a){ 
System.out.println("Triangle sayHi()"); }}Copy the code
  1. Create the ImportSelector implementation class and selectImports returns the full class name of Triangle.
public class MyImportSelector implements ImportSelector { 

@Override 
public String[] selectImports(AnnotationMetadata annotationMetadata) { 
return new String[]{"annotation.importannotation.waytwo.Triangle"}; }}Copy the code
  1. Create a configuration class and import MyImportSelector in addition to the original.
@Import({Circle.class,MyImportSelector.class}) 
@Configuration 
public class MainConfigTwo {}Copy the code
  1. Creating a test class
public static void main(String[] args) { 

ApplicationContext context = new AnnotationConfigApplicationContext(MainConfigTwo.class); 
Circle circle = context.getBean(Circle.class); 
Triangle triangle = context.getBean(Triangle.class); 
circle.sayHi(); 
triangle.sayHi(); 

} 
Copy the code
  1. Running results:

Circle sayHi()

Triangle sayHi()

You can see that the Triangle object is also successfully instantiated by the IOC container.

3. Cooperate with ImportBeanDefinitionRegistrar use

ImportBeanDefinitionRegistrar is an interface, it can be manually registered bean into the container, so we can be personalized customization to the class. (You need to use @import and @Configuration together.)

  1. Create a normal Java Rectangle class.
public class Rectangle { 

public void sayHi(a) { 
System.out.println("Rectangle sayHi()"); }}Copy the code
  1. Create ImportBeanDefinitionRegistrar implementation class, realization method manually register a Bean named rectangle directly to the IOC container.
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { 

@Override 
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { 

RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Rectangle.class); 
// Register a bean named Rectangle
beanDefinitionRegistry.registerBeanDefinition("rectangle", rootBeanDefinition); }}Copy the code
  1. Create a configuration class, import MyImportBeanDefinitionRegistrar class.
@Import({Circle.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class}) 
@Configuration 
public class MainConfigThree {}Copy the code
  1. Create the test class.
public static void main(String[] args) { 

ApplicationContext context = new AnnotationConfigApplicationContext(MainConfigThree.class); 
Circle circle = context.getBean(Circle.class); 
Triangle triangle = context.getBean(Triangle.class); 
Rectangle rectangle = context.getBean(Rectangle.class); 
circle.sayHi(); 
triangle.sayHi(); 
rectangle.sayHi(); 

} 
Copy the code
  1. The results

Circle sayHi()

Triangle sayHi()

Rectangle sayHi()

Well, the Rectangle object is registered as well.

@Conditional [provided by Spring]

The > @conditional annotation enables some configurations to be enabled only if certain conditions are met.

Here’s a simple example:

  1. ConditionBean creates the normal Java class ConditionBean, which validates that the Bean loaded successfully.
public class ConditionBean { 

public void sayHi(a) { 
System.out.println("ConditionBean sayHi()"); }}Copy the code
  1. Condition is an interface that has only a matches() method that returns a Boolean. If this method returns true, the Condition is true and the configuration class takes effect. Otherwise, it does not take effect. In this case we simply return true.
public class MyCondition implements Condition { 

@Override 
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { 
return true; }}Copy the code
  1. Create the configuration class, and you can see that the @Conditionality of the configuration passes in the Condition implementation class we just created for the Condition judgment.
@Configuration 
@Conditional(MyCondition.class) 
public class ConditionConfig { 

@Bean 
public ConditionBean conditionBean(a){ 
return newConditionBean(); }}Copy the code
  1. Write test methods.
public static void main(String[] args) { 

ApplicationContext context = new AnnotationConfigApplicationContext(ConditionConfig.class); 
ConditionBean conditionBean = context.getBean(ConditionBean.class); 
conditionBean.sayHi(); 

} 
Copy the code
  1. Results analysis

Since Condition’s matches method directly returns true, the configuration class will take effect. If we change matches to return false, the configuration class will not take effect.

In addition to custom conditions, Spring extends some of the commonly used conditions for us.

Extended annotations role
ConditionalOnBean It takes effect when the specified Bean exists in the container.
ConditionalOnMissingBean The specified Bean does not exist in the container.
ConditionalOnClass If the specified class exists in the system, it takes effect.
ConditionalOnMissingClass If no specified class exists in the system, it takes effect.
ConditionalOnProperty Whether a specified property in the system has a specified value.
ConditionalOnWebApplication This parameter takes effect if the system is in the Web environment.

2. SpringBoot startup process

In the process of looking at the source code, we will see the following four classes of methods are often called, we need to get a sense of the following classes:

  • ApplicationContextInitializer

  • ApplicationRunner

  • CommandLineRunner

  • SpringApplicationRunListener

Start with the run() method of the SpringBoot boot class. Here is the call chain: Springapplication.run () -> run(new Class[]{primarySource}, args) -> New SpringApplication(primarySources)).run(args).

New SpringApplication(primarySources).run(args)

The above method mainly consists of two steps:

  • Create the SpringApplication object.

  • Run the run() method.

Create the SpringApplication object

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { 

this.sources = new LinkedHashSet(); 
this.bannerMode = Mode.CONSOLE; 
this.logStartupInfo = true; 
this.addCommandLineProperties = true; 
this.addConversionService = true; 
this.headless = true; 
this.registerShutdownHook = true; 
this.additionalProfiles = new HashSet(); 
this.isCustomEnvironment = false; 
this.resourceLoader = resourceLoader; 
Assert.notNull(primarySources, "PrimarySources must not be null"); 
// Save the primary configuration class (this is an array, indicating that there can be multiple primary configuration classes)
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); 
// Check whether the current application is a Web application
this.webApplicationType = WebApplicationType.deduceFromClasspath(); 
/ / from the classpath find META/INF/Spring. All the ApplicationContextInitializer factories configuration, and then save up
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); 
/ / from the classpath find META/INF/Spring. All the ApplicationListener factories configuration, and then save up
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); 
// Find the main configuration class with main method from multiple configuration classes (only one)
this.mainApplicationClass = this.deduceMainApplicationClass(); 

} 
Copy the code

Run the run() method

public ConfigurableApplicationContext run(String... args) { 

// Create a timer
StopWatch stopWatch = new StopWatch(); 
stopWatch.start(); 
// Declare the IOC container
ConfigurableApplicationContext context = null; 
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList(); 
this.configureHeadlessProperty(); 
/ / from the classpath find META/INF/Spring SpringApplicationRunListeners factories
SpringApplicationRunListeners listeners = this.getRunListeners(args); 
/ / callback all SpringApplicationRunListeners starting () method
listeners.starting(); 
Collection exceptionReporters; 
try { 
// Encapsulate command line arguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); 
/ / to the environment, including the creation of the environment, create the environment after the completion of the callback SpringApplicationRunListeners# environmentPrepared () method, said the environment is prepared to do
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); 
this.configureIgnoreBeanInfo(environment); 
/ / print the Banner
Banner printedBanner = this.printBanner(environment); 
Create an IOC container (decide whether to create a Web IOC container or a plain IOC container)
context = this.createApplicationContext(); 
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); 
/* * Prepare the context, save the environment to the IOC container, And invoke applyInitializers * applyInitializers () method () method before the callback saved all ApplicationContextInitializer the initialize () method is * and then all of the callback SpringApplicationRunListener# contextPrepared () method * all the last callback SpringApplicationRunListener# contextLoaded * / () method
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); 
// The container is refreshed, the IOC container is initialized (and embedded Tomcat is created if it is a Web application), and all components are scanned, created and loaded
this.refreshContext(context); 
// Get all applicationRunners and CommandLineRunner from the IOC container for callbacks
this.afterRefresh(context, applicationArguments); 
stopWatch.stop(); 
if (this.logStartupInfo) { 
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); 
} 
/ / call all SpringApplicationRunListeners# started () method
listeners.started(context); 
this.callRunners(context, applicationArguments); 
} catch (Throwable var10) { 
this.handleRunFailure(context, var10, exceptionReporters, listeners); 
throw new IllegalStateException(var10); 
} 
try { 
listeners.running(context); 
return context; 
} catch (Throwable var9) { 
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null); 
throw newIllegalStateException(var9); }}Copy the code

Summary:

The run () phase is mainly the callback is mentioned at the beginning of this section 4 of the listener method and the load program components to the IOC container, and all need a callback listener from the classpath META/INF/Spring. The factories, so as to achieve before and after the start of a variety of customized operation.

3. Principle of SpringBoot automatic configuration

@ SpringBootApplication annotations

The SpringBoot project starts with the @SpringBootApplication annotation.

The @SpringBootApplication annotation states on a class:

  • This class is the main configuration class for SpringBoot.

  • SpringBoot should run the main method of the class to start the SpringBoot application.

This annotation is defined as follows:

@SpringBootConfiguration 
@EnableAutoConfiguration 
@ComponentScan( 
excludeFilters = {@Filter( 
type = FilterType.CUSTOM, 
classes = {TypeExcludeFilter.class} 
), @Filter( 
type = FilterType.CUSTOM, 
classes = {AutoConfigurationExcludeFilter.class} 
)} 
) 
public @interface SpringBootApplication { 
Copy the code

As you can see, the SpringBootApplication annotation is a composite annotation (discussed at the beginning of this article on composite annotations) that combines three main annotations:

  • @SpringBootConfiguration: This annotation indicates that this is a SpringBootConfiguration class, which is really just an @Configuration annotation.

  • @ComponentScan: Enables component scanning.

  • EnableAutoConfiguration: As the name suggests, this is the class that enables automatic configuration. Well, the secrets of auto-configuration are all in this note.

@ EnableAutoConfiguration annotations

Let’s look at how this annotation is defined:

@AutoConfigurationPackage 
@Import({AutoConfigurationImportSelector.class}) 
public @interface EnableAutoConfiguration { 
Copy the code

@AutoConfigurationPackage

Automatic configuration package literally. Click on the @import annotation to Import the Registrar component: @import ({registr.class}). The use of @import is also explained in the article.

Via a breakpoint on the registerBeanDefinitions method in the Registrar class, the package name is returned. The package name is the name of the main configuration class.

The @AutoConfigurationPackage annotation scans the package of the main configuration class (annotated by @SpringBootConfiguration) and all the components in the following subpackages into the Spring container. Therefore, components outside the main configuration class package and subpackages are not scanned by the Spring container by default.

@Import({AutoConfigurationImportSelector.class})

This annotation imports N additional auto-configuration classes into the current configuration class. (The detailed usage of this note is mentioned above).

Configure class import rules

So what are the import rules? Let’s look at the source code. Before I start looking at the source code, a couple of words. As Xiao Ma said, we do not have to look at all the source code, do not have to figure out what every line of code is meant, we just seize the key place.

We know AutoConfigurationImportSelector selectImports is used to return all you need to import the components of the class name of the array, so how do you get these arrays?

In selectImports method calling a getAutoConfigurationEntry () method.

Due to space problem I not capture one by one, I tell you directly call chain: in getAutoConfigurationEntry () – > getCandidateConfigurations () – > loadFactoryNames ().

Here loadFactoryNames () method is introduced into EnableAutoConfiguration. Class this parameter. Remember this parameter, we’ll use it later.

There are three key steps in loadFactoryNames() :

  1. Get all the information under the meta-INF/Spring.Factories file from the classpath of the current project.

  2. Encapsulate the information captured above into a Map and return it.

  3. From the returned Map by just incoming EnableAutoConfiguration. The class parameter, get all the value of this key.

The meta-inf/spring. Factories to explore

If you’re going to get a little confused, let’s take a look at what a meta-INF /spring.factories file is. Of course, there will be this file in many third-party dependencies, generally every import of a third-party dependency, in addition to its jar package, there will also be a XXx-spring-boot-autoconfigure, this is the third party dependencies written by their own automatic configuration class. Let’s start with the spring-boot-AutoCongigure dependency for now.

You can see that there are many classes under EnableAutoConfiguration, and these are the classes that our project automatically configures.

Add all the EnableAutoConfiguration values configured in meta-INF/Spring. factories under the classpath to the Spring container.

HttpEncodingAutoConfiguration

In this way, all automatic configuration classes are guided into the main configuration class. But there are so many configuration classes, there are obviously a lot of automatic configuration we usually do not use, there is no reason to all take effect.

Next we HttpEncodingAutoConfiguration, for example to see how an automatic configuration class work. Why this class? Mainly a simple example of this class comparison.

Take a look at the notes marked with this class:

@Configuration 
@EnableConfigurationProperties({HttpProperties.class}) 
@ConditionalOnWebApplication( 
type = Type.SERVLET 
) 
@ConditionalOnClass({CharacterEncodingFilter.class}) 
@ConditionalOnProperty( 
prefix = "spring.http.encoding", 
value = {"enabled"}, 
matchIfMissing = true 
) 
public class HttpEncodingAutoConfiguration { 
Copy the code
  • @configuration: Marks as a Configuration class.

  • To come into force next @ ConditionalOnWebApplication: web applications.

  • ConditionalOnClass: the specified class (dependency) exists.

  • ConditionalOnProperty: ConditionalOnProperty is valid only if the specified properties exist in the master profile.

  • @ EnableConfigurationProperties ({HttpProperties. Class}) : start the specified class ConfigurationProperties function; Bind the corresponding values in the configuration file to HttpProperties; Add HttpProperties to the IOC container.

Because the @ EnableConfigurationProperties ({HttpProperties. Class}) configuration items in the configuration file with the current HttpProperties class. And then in the HttpEncodingAutoConfiguration cited HttpProperties, so finally can in HttpEncodingAutoConfiguration values in the configuration file is used. Finally, add components to the container via @beans and some conditional judgments for automatic configuration. (Of course, the property values in this Bean are retrieved from HttpProperties)

HttpProperties

HttpProperties binds the configuration file to its own properties through the @ConfigurationProperties annotation.

All properties that can be configured in a configuration file are encapsulated in the xxxProperties class; What can be configured in a configuration file can refer to the property class corresponding to a particular function.

@ConfigurationProperties( 
prefix = "spring.http" 
)// Get the specified value from the configuration file and bind it to the bean's properties
public class HttpProperties { 
Copy the code

Summary:

  1. SpringBoot launches load a large number of auto-configuration classes.

  2. Let’s see if the required functionality has automatic configuration classes written by SpringBoot by default.

  3. Let’s take a look at which components are configured in the auto-configuration class (we don’t need to configure them as long as we have them).

  4. When you add a component to the auto-configuration class in the container, you get some properties from the Properties class. We can then specify the values of these properties in the configuration file.

  • XxxAutoConfiguration: Automatically configures the class to add components to the container.

  • XxxProperties: encapsulates related properties in the configuration file.

If you want to start a “starter” class, you can start a “starter” class using a meta-INF/spring. factories file in the classpath. Let SpringBoot load it. Now you see why SpringBoot can achieve zero configuration, right out of the box!

The last

The article is a bit long, thank you for reading! You can like it if you think it’s good!

Reference article:

– blog.51cto.com/4247649/211…

– www.cnblogs.com/duanxz/p/37…

– www.jianshu.com/p/e22b9fef3…

– blog.csdn.net/qq_26525215…