1. Warning: this is a very long article and I suggest you read it first. It may be the last time I write this long article
  2. Note: There are four sections on the basics of Spring, respectively: The IOC container, JavaConfig, event listeners, and SpringFactoriesLoader take up most of this article, and while they may not have much to do with each other, they are essential to understanding the core principles of Spring Boot. If you know the Spring framework by heart, you can definitely skip these four sections. It is because this series of articles is made up of these seemingly unrelated points of knowledge that it takes its name.

One of the most exciting things about the Spring ecosystem over the past two or three years has been the Spring Boot framework. Perhaps the name suggests what the framework is designed to do: quickly launch Spring applications. Therefore, Spring Boot applications are essentially applications based on the Spring framework. It is the product of Spring’s best practice of “convention over configuration”, which can help developers build applications based on the Spring ecosystem more quickly and efficiently.

So what’s the magic of Spring Boot? Automatic configuration, startup dependence, Actuator, and CLI are the four most important core features of Spring Boot. Among them, CLI is an optional feature of Spring Boot. Although it is powerful, it also introduces a set of unconventional development model, so this series of articles only focus on the other three features. As the title suggests, this article is the first in a series that opens the door to Spring Boot, focusing on the startup process and how automatic configuration works. To master this core, understanding some of the basics of the Spring framework will help you a lot.

Explore the Spring IoC container

If you have seen the source code for the SpringApplication.run() method, Spring Boot’s lengthy startup process will drive you crazy. At its core, SpringApplication simply extends the startup process of a typical Spring application. A thorough understanding of the Spring container is the key that opens the door to Spring Boot.

1.1. Spring IoC container

The Spring IoC container can be likened to a restaurant. When you go to the restaurant, you usually ask the waiter: Order! As for the ingredients? How to make a dish with ingredients? Maybe you don’t even care. The same goes for the IoC container. You just tell it that you need a bean, and it throws you an instance. You don’t care how the bean is initialized or dependent on other components.

In the same way that restaurants want to make dishes and know the ingredients and recipes for their dishes, IoC containers want to manage individual business objects and their dependencies, and need some way to record and manage this information. The BeanDefinition object takes on this responsibility: Each bean in the container has a corresponding BeanDefinition instance that holds all the necessary information about the bean object, including its class type, whether it is an abstract class, constructors and parameters, other properties, and so on. When a client requests an object from the container, the container returns a full, usable bean instance to the client using this information.

So the ingredients are ready (look at the BeanDefinition of ingredients), start cooking, etc., you also need a recipe, BeanDefinitionRegistry and BeanFactory are this recipe, BeanDefinitionRegistry abstracts the bean registration logic, while BeanFactory abstracts the bean management logic, and the implementation classes of each BeanFactory specifically take on the bean registration and management. The relationship between them is shown below:


BeanFactory, BeanDefinitionRegistry diagram (from: Spring Revelations)

DefaultListableBeanFactory as a more general the BeanFactory implementation, it also realizes BeanDefinitionRegistry interface, so it will take the registration management of Bean. As can be seen from the figure, the BeanFactory interface mainly contains getBean, containBean, getType, getAliases and other bean management methods. The BeanDefinitionRegistry interface contains registerBeanDefinition, removeBeanDefinition, getBeanDefinition and other methods for registering and managing BeanDefinitions.

Here is a simple code to simulate how the bottom layer of the BeanFactory works:

/ / the default container implementation DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory (); AbstractBeanDefinition definition = new RootBeanDefinition(business.class,true); // AbstractBeanDefinition definition = new RootBeanDefinition(business.class,true); / / defines the bean registered to container beanRegistry. RegisterBeanDefinition (" beanName "definition); // If there are multiple beans, you can also specify the dependencies between the individual beans //........ // You can then get an instance of the bean from the container // Note: Here, the beanRegistry actually implements the BeanFactory interface, so you can force it, // Pure BeanDefinitionRegistry is BeanFactory container = (BeanFactory)beanRegistry; Business business = (Business)container.getBean("beanName");Copy the code

While this code is intended to illustrate the underlying workflow of the BeanFactory, it can be more complex, such as bean dependencies defined in external configuration files (XML/Properties) or annotated. The overall workflow of the Spring IoC container can be roughly divided into two phases:

(1) Container startup stage

When the container is started, Configuration MetaData is loaded in some way. In addition to being fairly straightforward in code, in most cases containers need to rely on some utility classes, such as: BeanDefinitionReader, which will parse and analyze the loaded Configuration MetaData and assemble the analyzed information into the corresponding BeanDefinition. Finally, these BeanDefinitions, which hold bean definitions, are registered with the appropriate BeanDefinitionRegistry, and container startup is complete. This stage mainly completes some preparatory work, focuses more on the collection of bean object management information, of course, some verification or auxiliary work is also completed in this stage.

As a simple example, in the past, all beans were defined in an XML configuration file. The following code emulates how BeanFactory loads the bean definition and dependencies from the configuration file:

// Usually BeanDefinitionRegistry's implementation class, Here take DeFaultListabeBeanFactory BeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory (); XmlBeanDefinitionReader implements the BeanDefinitionReader interface, Used to parse the XML file XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReaderImpl (beanRegistry); / / load the configuration file beanDefinitionReader. LoadBeanDefinitions (" classpath: spring - bean. XML "); BeanFactory Container = (BeanFactory)beanRegistry; Business business = (Business)container.getBean("beanName");Copy the code

(2) Bean instantiation stage

After the first stage, all bean definitions are registered with BeanDefinitionRegistry as BeanDefinitions. When a request requests an object through the container’s getBean method, or because the dependency container needs to implicitly call getBean, The second-phase activity is triggered: the container first checks to see if the requested object has been instantiated before. If not, the requested object is instantiated and its dependencies are injected based on the information provided by the registered BeanDefinition. When the object is assembled, the container immediately returns it to the requesting method for use.

BeanFactory is simply an implementation of the Spring IoC container that, if not specified, uses a lazy initialization strategy: an object in the container is initialized and dependency injected only when it is accessed. In practical scenarios, we use another type of container more often: ApplicationContext, which is built on top of the BeanFactory. It is a more advanced container, and has all the capabilities of the BeanFactory, as well as support for event listening and internationalization. It manages beans that are all initialized and dependency injected when the container is started.

1.2. Spring container extension mechanism

The IoC container is responsible for managing the life cycle of all beans in the container, and Spring provides different extension points to change the fate of beans at different stages of the bean life cycle. At the start of the container, the BeanFactoryPostProcessor allows you to perform additional operations on the information held by the BeanDefinition registered with the container, such as modifying some properties of the bean definition or adding other information, before the container instantiates the object.

If you want to custom extensions class, usually need to implement org. Springframework. Beans. Factory. Config. The spring BeanFactoryPostProcessor interface, at the same time, Because the container may have more than one spring BeanFactoryPostProcessor, may also need to implement org. Springframework. Core. Ordered interface, to ensure that the spring BeanFactoryPostProcessor in accordance with the order. Spring provides a few Spring BeanFactoryPostProcessor implementation, we accomplished to illustrate the general workflow.

In the XML configuration files of Spring projects, it is common to see many configuration items with placeholder values, and the value represented by the placeholder is configured separately to a separate properties file. This allows centralized management of the configuration scattered in different XML files, and also facilitates operation and maintenance to configure different values for different environments. This very practical function is the accomplished is responsible for the implementation.

When BeanFactory loads all the configuration information in the first phase, the properties of the objects stored in the BeanFactory are still placeholders, such as ${jdbc.mysql.url}. When is accomplished as spring BeanFactoryPostProcessor being applied, it will use the properties in the values in the configuration file to replace the corresponding BeanDefinition placeholder attribute values. When the bean needs to be instantiated, the property values in the bean definition are replaced with the values we configured. Of course, its implementation is more complex than the above description, here only to explain its general working principle, more detailed implementation can refer to its source code.

Similarly, there is the BeanPostProcessor, which exists in the object instantiation phase. Like BeanFactoryPostProcessor, it handles all the qualified and instantiated objects in the container. In a simple comparison, BeanFactoryPostProcessor handles the definition of a bean, while BeanPostProcessor handles the object after the bean has been instantiated. BeanPostProcessor defines two interfaces:

Public interface BeanPostProcessor {/ / preposition Object postProcessBeforeInitialization (Object bean, String beanName) throws BeansException; / / rear handle Object postProcessAfterInitialization (Object beans, String beanName) throws BeansException; }Copy the code

To understand when these two methods execute, take a quick look at the bean lifecycle:


Bean instantiation process (from: Spring Reveal)

PostProcessBeforeInitialization () method with postProcessAfterInitialization () corresponding to figure in the pre processing and post processing method of two steps will be executed. Both methods pass in a reference to the bean object instance, which greatly facilitates the extension of the container’s object instantiation process, where you can do almost anything on the passed instance. Annotations, AOP, and other implementations make extensive use of BeanPostProcessor. For example, if you have a custom annotation, you can implement the BeanPostProcessor interface and determine whether the bean object has the annotation on its head. If so, you can perform any operation on the bean instance. Isn’t that easy to think about?

As a more common example, it is common to see various Aware interfaces in Spring that inject dependencies specified in the Aware interface definition into the current instance once the object is instantiated. For the most common ApplicationContextAware interface, classes that implement this interface can get an ApplicationContext object. When a container of each object instantiation went down to the BeanPostProcessor pre-processing step, containers will be detected before ApplicationContextAwareProcessor registered to the container, Then call its postProcessBeforeInitialization () method, check and set up the Aware related dependency. Take a look at the code, isn’t it very simple:

// Code from: org.springframework.context.support.ApplicationContextAwareProcessor // Its postProcessBeforeInitialization method calls the invokeAwareInterfaces private void invokeAwareInterfaces (Object bean) {if (bean instanceof EnvironmentAware) { ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); } if (bean instanceof ApplicationContextAware) { ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); } / /... }Copy the code

In conclusion, this section reviews some of the core contents of the Spring container with you. There is no space to write more, but this section is enough to make it easy for you to understand the principle of The Spring Boot. If you encounter some difficult knowledge in the subsequent learning process, you can easily understand the Spring Boot. Going back to the core knowledge of Spring, it may have unexpected effects. There may be very few Spring Boot Chinese materials, but there are so many Spring Chinese materials and books that there is always something to inspire you.

2. Solid foundation: JavaConfig and common Annotations

2.1, JavaConfig

We know that beans are a very core concept in Spring IOC, and the Spring container is responsible for managing the life cycle of beans. At first, Spring used XML configuration files to describe the definition of beans and their dependencies, but as Spring grew, more and more people complained about this approach because all the business classes of Spring projects were configured in XML files as beans, resulting in a large number of XML files. Making projects complex and difficult to manage.

Later, Guice, a dependency injection framework based on pure Java Annotations, was born. Its performance is significantly better than That of XmL-based Spring, and some people even believe that Guice can completely replace Spring (Guice is only a lightweight IOC framework, which is far from replacing Spring). It was this sense of crisis that prompted Spring and the community to launch and continue to refine the JavaConfig subproject, which describes dependent binding relationships between beans based on Java code and Annotation annotations. For example, here is the definition of a bean using XML configuration:

<bean id="bookService" class="cn.moondev.service.BookServiceImpl"></bean>
Copy the code

The Javaconfig.based configuration looks like this:

@configuration public class MoonBookConfiguration {// Any method that marks @bean, The return value will be registered as a bean in Spring's IOC container // The method name defaults to the id of the bean definition @bean public BookService BookService () {return new BookServiceImpl(); }}Copy the code

If there is a dependency between two beans, it would look like this in the XML configuration:

<bean id="bookService" class="cn.moondev.service.BookServiceImpl">
    <property name="dependencyService" ref="dependencyService"/>
</bean>

<bean id="otherService" class="cn.moondev.service.OtherServiceImpl">
    <property name="dependencyService" ref="dependencyService"/>
</bean>

<bean id="dependencyService" class="DependencyServiceImpl"/>
Copy the code

In JavaConfig it looks like this:

@configuration public class MoonBookConfiguration {// If a bean depends on another bean, DependencyService () @bean public BookService BookService () {return new BookServiceImpl(dependencyService()); } @Bean public OtherService otherService() { return new OtherServiceImpl(dependencyService()); } @Bean public DependencyService dependencyService() { return new DependencyServiceImpl(); }}Copy the code

You may have noticed that in this example, both beans depend on dependencyService, that is, when bookService is initialized, dependencyService() is called, DependencyService () is also called when initializing otherService. Is there one dependencyService instance or two in the IOC container? I’ll leave that for you to think about and I won’t repeat it here.

2.2, @ ComponentScan

The @ComponentScan annotation corresponds to the < Context: Component-scan > element in the XML configuration form, indicating that component scanning is enabled. Spring automatically scans all beans configured by the annotation and registers them with the IOC container. We can specify the scope that @ComponentScan automatically scans by using properties such as basePackages. If not, the default is to scan from the package of the class that declares @ComponentScan. Because of this, SpringBoot’s boot classes are all in SRC /main/ Java by default.

2.3, @ Import

The @import annotation is used to Import configuration classes, as a simple example:

@Configuration public class MoonBookConfiguration { @Bean public BookService bookService() { return new BookServiceImpl(); }}Copy the code

Now you have another configuration class, for example: MoonUserConfiguration. This configuration class has a bean that depends on the bookService in MoonBookConfiguration. How do you combine the two beans? Use @import:

@configuration // You can import multiple Configuration classes at the same time, for example: @Import({A.class,B.class}) @Import(MoonBookConfiguration.class) public class MoonUserConfiguration { @Bean public UserService userService(BookService bookService) { return new BookServiceImpl(bookService); }}Copy the code

Note that before 4.2, the @import annotation only supported importing configuration classes, but after 4.2, it supported importing normal classes and registering this class as the definition of a bean in the IOC container.

2.4, @ Conditional

The @Conditional annotation indicates that a bean is not initialized or some configuration is enabled until certain conditions are met. It is typically used on classes identified by @Component, @Service, @Configuration, etc., or on methods marked by @Beans. If an @Configuration class marks @Conditional, then all methods in that class that identify @Bean and related classes imported by the @import annotation will comply with these conditions.

It’s easy to write your own Condition class in Spring. All you have to do is implement the Condition interface and override its matches() method. For example, the following simple conditional class indicates that the JdbcTemplate class is valid only if it exists in the Classpath:

public class JdbcTemplateCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { try { conditionContext.getClassLoader().loadClass("org.springframework.jdbc.core.JdbcTemplate"); return true; } catch (ClassNotFoundException e) { e.printStackTrace(); } return false; }}Copy the code

When declaring beans in Java, you can use this custom condition class:

@Conditional(JdbcTemplateCondition.class)
@Service
public MyService service() {
    ......
}
Copy the code

In this example, the MyService bean is created only if the JdbcTemplateCondition class condition is true. That is, MyService is created only if the JdbcTemplate is in the classpath, otherwise the declaration of the bean is ignored.

Spring Boot defines a number of interesting conditions and applies them to the configuration classes that form the basis of Spring Boot’s automatic configuration. Spring Boot uses conditional configuration by defining multiple special conditional annotations and applying them to configuration classes. Some conditional annotations provided by Spring Boot are listed below:

Conditional annotation Setting Effective Conditions
@ConditionalOnBean A specific bean is configured
@ConditionalOnMissingBean No specific bean is configured
@ConditionalOnClass The Classpath has specified classes
@ConditionalOnMissingClass There are no specified classes in the Classpath
@ConditionalOnExpression The given Spring Expression Language Expression evaluates to true
@ConditionalOnJava The Java version matches a specific reference or a range value
@ConditionalOnProperty Specify configuration properties that have an explicit value
@ConditionalOnResource The Classpath has specified resources
@ConditionalOnWebApplication This is a Web application
@ConditionalOnNotWebApplication This is not a Web application

2.5, @ ConfigurationProperties and @ EnableConfigurationProperties

When the Value of some property needs to be configured, we typically create a new configuration item in the application.properties file and use the @Value annotation in the bean to obtain the configured Value, as shown in the following code for configuring the data source.

// jdbc config jdbc.mysql.url=jdbc:mysql://localhost:3306/sampledb jdbc.mysql.username=root jdbc.mysql.password=123456 . / / Configuration data source @ Configuration public class HikariDataSourceConfiguration {@ Value (" JDBC. Mysql. Url ") public String url; @Value("jdbc.mysql.username") public String user; @Value("jdbc.mysql.password") public String password; @Bean public HikariDataSource dataSource() { HikariConfig hikariConfig = new HikariConfig(); hikariConfig.setJdbcUrl(url); hikariConfig.setUsername(user); hikariConfig.setPassword(password); Return new HikariDataSource(hikariConfig); }}Copy the code

Properties injected with the @Value annotation are generally simple, and can be difficult to maintain if the same configuration is used in multiple places (think: if a configuration is used in dozens of places and you want to change the name, how do you do it?). . For more complex configurations, Spring Boot provides a more elegant implementation with the @ConfigurationProperties annotation. We can rewrite the above code in the following way:

@ Component / / you can @ PropertySource (" classpath: JDBC. Properties ") to specify the configuration file @ ConfigurationProperties (" JDBC. Mysql ") / / Jdbc.mysql.* pulic class JdbcConfig {public String URL; public String username; public String password; } @Configuration public class HikariDataSourceConfiguration { @AutoWired public JdbcConfig config; @Bean public HikariDataSource dataSource() { HikariConfig hikariConfig = new HikariConfig(); hikariConfig.setJdbcUrl(config.url); hikariConfig.setUsername(config.username); hikariConfig.setPassword(config.password); Return new HikariDataSource(hikariConfig); }}Copy the code

ConfigurationProperties is also handy for more complex configurations, such as the following configuration files:

#App app.menus[0].title=Home app.menus[0].name=Home app.menus[0].path=/ app.menus[1].title=Login app.menus[1].name=Login  app.menus[1].path=/login app.compiler.timeout=5 app.compiler.output-folder=/temp/ app.error=/error/Copy the code

You can define the following configuration classes to receive these attributes

@Component @ConfigurationProperties("app") public class AppProperties { public String error; public List<Menu> menus = new ArrayList<>(); public Compiler compiler = new Compiler(); public static class Menu { public String name; public String path; public String title; } public static class Compiler { public String timeout; public String outputFolder; }}Copy the code

@ EnableConfigurationProperties annotation built-in support for @ ConfigurationProperties said, the default will correspond to the Properties of the Class as a bean into the IOC container, That is, there is no @Component annotation on the corresponding Properties class.

Three, cut iron like mud: SpringFactoriesLoader details

The JVM provides three types of loaders: BootstrapClassLoader, ExtClassLoader, and AppClassLoader, which load Java core libraries, extension libraries, and libraries in the application’s CLASSPATH respectively. The JVM loads classes through the parent delegate model, and we can implement our own classloader by inheriting java.lang.classloader.

What is the parental delegation model? When a class loader receives a class loading task, it will first hand it to its parent loader to complete it. Therefore, the final loading task will be passed to the top-level BootstrapClassLoader. Only when the parent loader fails to complete the loading task, it will try to load itself.

One of the benefits of using the parent delegate model is to ensure that different class loaders end up with the same Object, thus ensuring the type safety of the Java core library. For example, loading the java.lang.Object class in the Rt.jar package, regardless of which loader loads the class, The load is ultimately delegated to the top-level BootstrapClassLoader, which ensures that any class loader will end up with the same Object. See the ClassLoader source code for a more intuitive understanding of the parent delegate model:

protected Class<? > loadClass(String name, Boolean resolve) {synchronized (getClassLoadingLock(name)) {// First, check whether the class has been loaded. Class<? > c = findLoadedClass(name); If (c == null) {try {// Following the parent delegate model, the first step is to recursively find the parent loader, // until the parent loader is BootstrapClassLoader if (parent! = null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); }} catch (ClassNotFoundException e) {} if (c == null) {if (c == null) { C = findClass(name); c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }}Copy the code

However, the parent delegation model does not solve all classloader problems. For example, Java provides many Service Provider interfaces (SPIs) that allow third parties to provide implementations for these interfaces. Common SPIs include JDBC, JNDI, JAXP, etc. The interfaces of these SPIs are provided by the core class library, but implemented by a third party. Thus, there is a problem: the interfaces of SPI are part of the Java core library and loaded by BootstrapClassLoader; Java classes implemented by SPI are typically loaded by AppClassLoader. The BootstrapClassLoader cannot find the SPI implementation class because it only loads Java’s core libraries. It also cannot be proxied to AppClassLoader because it is the top-level classloader. In other words, the parental delegation model does not solve this problem.

The ContextClassLoader solves this problem. The name might be mistaken for a new class loader, but it’s actually just a variable of Thread, This object can be set and obtained by setContextClassLoader(ClassLoader CL) and getContextClassLoader(). The default context classloader for Java application threads is AppClassLoader without any Settings. When the core library uses the SPI interface, the class loader passed through uses the thread-context class loader, which can be successfully loaded into the CLASS implemented by SPI. Thread context classloaders are used in many IMPLEMENTATIONS of SPI. In JDBC, however, you might see a more straightforward implementation, such as the loadInitialDrivers() method in JDBC Driver management java.sql.driver, where you can directly see how the JDK loads the Driver:

for (String aDriver : DriversList) {try {/ / direct use AppClassLoader Class. The class.forname (aDriver, true, this getSystemClassLoader ()); } catch (Exception ex) { println("DriverManager.Initialize: load failed: " + ex); }}Copy the code

Thread.currentthread ().getClassLoader() and Thread.currentThread().getContextClassLoader(). The two are the same in most business scenarios, as long as you know what problem it is there to solve, except that the ClassLoader may be different in many of the underlying frameworks.

GetResources (String name) is used to read resources from jar files. The following code is used to read resources from jar files:

public Enumeration<URL> getResources(String name) throws IOException { Enumeration<URL>[] tmp = (Enumeration<URL>[]) new  Enumeration<? > [2]. if (parent ! = null) { tmp[0] = parent.getResources(name); } else { tmp[0] = getBootstrapResources(name); } tmp[1] = findResources(name); return new CompoundEnumeration<>(tmp); }Copy the code

BootstrapClassLoader: BootstrapClassLoader: BootstrapClassLoader: BootstrapClassLoader: BootstrapClassLoader: BootstrapClassLoader: BootstrapClassLoader: BootstrapClassLoader Different class loaders scan jar packages in different paths, just like loading classes, and eventually scan all jar packages to find the resource files that match the conditions.

The class loader’s findResources(name) method iterates through all the JARS it is responsible for loading to find the resource file named name in the JAR, where the resource can be any file, even a.class file, as in the following example, to find array.class:

Public static void main(String[] args) throws Exception{// The complete path of array. class String name = "java/sql/Array.class"; Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(name); while (urls.hasMoreElements()) { URL url = urls.nextElement(); System.out.println(url.toString()); }}Copy the code

After running, the following results can be obtained:

$JAVA_HOME/jre/lib/rt.jar! /java/sql/Array.classCopy the code

Based on the URL of the resource file, the corresponding file can be constructed to read the content of the resource.

You might be surprised to learn that you want to explain the SpringFactoriesLoader. A bunch of classloaders. Take a look at its source code and you will know:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; // Spring. factories files are in the following format: Key =value1,value2,value3 // Find the meta-INF /spring.factories file from all the jar packages and then parse the file to find all the values of the key=factoryClass class name public static List<String> loadFactoryNames(Class<? > factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); Enumeration<URL> urls = (classLoader! = null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List<String> result = new ArrayList<String>(); While (urls.hasmoreElements ()) {URL URL = urls.nextelement (); / / according to the resource URL parsing the properties file properties properties. = PropertiesLoaderUtils loadProperties (new UrlResource (URL)); String factoryClassNames = properties.getProperty(factoryClassName); / / assembly data, and returns the result. The addAll (arrays.aslist (StringUtils.com maDelimitedListToStringArray (factoryClassNames))); } return result; }Copy the code

Now that you know about classLoaders, it’s easy to understand this code: Search for all meta-INF/Spring.Factories configuration files from each Jar package in your CLASSPATH, and then parse the properties file to find the configuration with the specified name and return. Note that it’s not just going to the ClassPath path, it’s going to scan all the jars in the ClassPath path, but only in the Jar in the ClassPath path. Take a quick look at the contents of the Spring.Factories file:

. / / from org. Springframework. Boot autoconfigure under the meta-inf/spring. The factories / / EnableAutoConfiguration later talk about, It is used to open the Spring Boot automatically configure function org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration = \ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\Copy the code

Perform loadFactoryNames (EnableAutoConfiguration. Class, after this), get the corresponding to a set of @ the Configuration class, we can through the reflection to instantiate the class and then injected into the IOC container, Finally, the container has a set of Configuration classes in the form of JavaConfig labeled @Configuration.

This is the SpringFactoriesLoader, which is essentially a proprietary extension solution of the Spring framework, similar to SPI. Many of Spring Boot’s core functions based on Spring are based on this.

Another weapon: the Spring container’s event listening mechanism

In the past, the event monitoring mechanism for the graphical interface programming, such as: click on the button, in the text box input operation is called the event, and when the event is triggered when the application to a certain response is applied to monitor the events, and on the server side, event surveillance mechanism more for asynchronous notification and monitoring and exception handling. Java provides two basic classes that implement the event listening mechanism: custom event types extended from java.util.eventobject and listeners of events extended from java.util.EventListener. Consider a simple example: simply monitoring the time spent on a method.

You define the event type first, which is usually done by extending EventObject. As the event occurs, the corresponding state is usually encapsulated in this class:

Public class MethodMonitorEvent extends EventObject {public long timestamp; public MethodMonitorEvent(Object source) { super(source); }}Copy the code

We can issue a BEGIN event before the method starts executing and an End event after the method ends. Accordingly, the event listener needs to provide methods to handle events received in either case:

/ / 1, the custom event listener interfaces public interface MethodMonitorEventListener extends EventListener {/ / processing method performs before publishing event public void onMethodBegin(MethodMonitorEvent event); Public void onMethodEnd(MethodMonitorEvent Event); } // 2, event listener interface implementation: How to deal with public class AbstractMethodMonitorEventListener implements MethodMonitorEventListener {@ Override public void OnMethodBegin (MethodMonitorEvent Event) {event.timestamp = system.currentTimemillis (); } @override public void onMonitoreVent event {// Long duration = system.currentTimemillis () - event.timestamp; System.out.println(" time: "+ duration); }}Copy the code

The event listener interface actually provides the corresponding handler definition for different event publications. Most importantly, its methods only receive the MethodMonitorEvent parameter, indicating that the listener class is only responsible for listening to and handling the corresponding event. With events and listeners, all that is left is to publish events and let the appropriate listeners listen and process them. Typically, we have an event publisher that acts as an event source and publishes the corresponding event to the corresponding event listener when appropriate:

public class MethodMonitorEventPublisher { private List<MethodMonitorEventListener> listeners = new ArrayList<MethodMonitorEventListener>(); public void methodMonitor() { MethodMonitorEvent eventObject = new MethodMonitorEvent(this); publishEvent("begin",eventObject); Timeunit.seconds.sleep (5); publishEvent("end",eventObject); } private void publishEvent (String status, MethodMonitorEvent event) {/ / avoid during event processing, the listener is removed, Here in order to make a copy operation safety List < MethodMonitorEventListener > copyListeners = ➥ new ArrayList < MethodMonitorEventListener > (listeners); for (MethodMonitorEventListener listener : copyListeners) { if ("begin".equals(status)) { listener.onMethodBegin(event); } else { listener.onMethodEnd(event); } } } public static void main(String[] args) { MethodMonitorEventPublisher publisher = new MethodMonitorEventPublisher(); publisher.addEventListener(new AbstractMethodMonitorEventListener()); publisher.methodMonitor(); } / / omit implementation public void addEventListener (MethodMonitorEventListener listener) {} public void removeEventListener(MethodMonitorEventListener listener) {} public void removeAllListeners() {}Copy the code

There are two common concerns for event publishers (event sources) :

  1. Post events when appropriate. The methodMonitor() method in this example is the source of event publication, publishing MethodMonitorEvent events at two points in time before and after the method is executed, and each point in time the published event is passed to the appropriate listener for processing. In the implementation, it is important to note that event publishing is executed sequentially, so that the event listener processing logic should be as simple as possible in order not to affect processing performance.
  2. Management of event listeners. The Publisher class provides methods to register and remove event listeners so that clients can decide if they need to register new listeners or remove one. If there are no provide the remove method, then the registered listener sample will always be MethodMonitorEventPublisher references, even if has been abandoned, is still in the publisher of the list of listeners, which leads to implicit memory leaks.

Event listening mechanism in the Spring container

Spring ApplicationContext container inside of all event types are inherited from org. Springframework. Context. AppliationEvent, In a container all listeners org. Springframework. Context. ApplicationListener interface, and registered in the form of beans in the container. Once applicationEvents and events of their subtypes are published within the container, the ApplicationListener registered with the container processes these events.

You can probably guess what’s going on.

ApplicationEvent inherits from EventObject, and Spring provides some default implementations such as: ContextClosedEvent represents the event type published when the container is about to close, and ContextRefreshedEvent represents the event type published when the container is initialized or refreshed……

The container internally uses ApplicationListener as the EventListener interface definition, which inherits from EventListener. The ApplicationContext container automatically recognizes and loads beans of type EventListener when it is started, and notifies those registered with the container of EventListener once an event is published in the container.

ApplicationContext interface inherits the ApplicationEventPublisher interface, this interface provides a void publishEvent (ApplicationEvent event) method is defined, it is not hard to see, The ApplicationContext container acts as the event publisher. If you are interested in can check AbstractApplicationContext. PublishEvent (ApplicationEvent event) method source code: ApplicationContext event publishing and management of the listener entrusted to ApplicationEventMulticaster interface implementation class. The container starts, will check the container whether there is called applicationEventMulticaster applicationEventMulticaster object instance. If there is used with the implementation of, without the default initialization of an SimpleApplicationEventMulticaster as implementation.

Finally, if the business we need to publish events within the container, you just need to inject ApplicationEventPublisher rely for it: Implement ApplicationEventPublisherAware interface or ApplicationContextAware interface (Aware interface related content, please review above).

Five, brilliant: reveal the principle of automatic configuration

Typical Spring Boot applications have Boot classes in the SRC /main/ Java root, such as the MoonApplication class:

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

Where @SpringBootApplication enables component scanning and automatic configuration, springApplication.run is responsible for starting the boot application. @SpringBootApplication is a composite Annotation that combines three useful annotations:

@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 {
    // ......
}
Copy the code

@SpringBootConfiguration is @Configuration, which is a Spring framework annotation that indicates that this class is a JavaConfig Configuration class. While @ComponentScan enables component scanning, which has been explained in detail in the previous section, this section focuses on @EnableAutoConfiguration.

The @enableAutoConfiguration annotation enables Spring Boot to automatically configure the beans you need based on application dependencies, custom beans, whether or not you have a class in your classpath, and so on. Then register with the IOC container. So how does @enableAutoConfiguration figure out your requirements? Let’s start with its definition:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    // ......
}
Copy the code

You should focus on @ Import (EnableAutoConfigurationImportSelector. Class), remember, @ Import annotations used to Import the classes, and the definition of the class as a bean registered into the container, Here it will EnableAutoConfigurationImportSelector as the beans into the container, and this class will all eligible @ Configuration Configuration is loaded into the container, see the code:

AnnotationMetadata public String[] selectImports(AnnotationMetadata AnnotationMetadata) { In the most recent version of SpringBoot, List<String> Factories = new ArrayList<String>(new) LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader))); }Copy the code

The “META INF/ Spring. factories” file looks like this:

. / / from org. Springframework. Boot autoconfigure under the meta-inf/spring. The factories / / configuration key = EnableAutoConfiguration, In accordance with the code org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration = \ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\ .....Copy the code

DataSourceAutoConfiguration, for example, look at how Spring Boot automatically configure:

@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
public class DataSourceAutoConfiguration {
}
Copy the code

Say it separately:

  • @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }): This configuration is only enabled when the DataSource or EmbeddedDatabaseType class exists in the Classpath, otherwise it will be ignored.
  • @EnableConfigurationProperties(DataSourceProperties.class)The DataSource default configuration class is injected into the IOC container. DataSourceproperties is defined as:
// The datasource configuration is supported with the following prefixes: spring.datasource @ConfigurationProperties(prefix = "spring.datasource") public class DataSourceProperties { private ClassLoader classLoader; private Environment environment; private String name = "testdb"; . }Copy the code
  • @Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class }): Import other additional configurations as followsDataSourcePoolMetadataProvidersConfigurationFor example.
@Configuration public class DataSourcePoolMetadataProvidersConfiguration { @Configuration @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class) static class TomcatDataSourcePoolMetadataProviderConfiguration { @Bean public DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider() { ..... }}... }Copy the code

DataSourcePoolMetadataProvidersConfiguration is a configuration, database connection pool providers that exist in the Classpath org.. Apache tomcat. JDBC. Pool. The DataSource. The class, Tomcat-jdbc connection pools are used, or HikariDataSource. Class is used if you have HikariDataSource in the Classpath.

Here only describes DataSourceAutoConfiguration the tip of the iceberg, but enough to explain the Spring Boot how to make use of condition configuration to realize the automatic configuration. In retrospect, @ EnableAutoConfiguration import EnableAutoConfigurationImportSelector class, The selectImports() of this class gets a large number of configuration classes through the SpringFactoriesLoader, and each configuration class makes decisions based on conditional configuration to implement automatic configuration.

The whole process is very clear, but missed a big problem: EnableAutoConfigurationImportSelector. SelectImports when execution is ()? Actually this method can be carried in the process of container startup: AbstractApplicationContext. Refresh (), more details in the next section.

Vi. Boot Boot: The secret of Spring Boot

6.1 SpringApplication Initialization

The whole SpringBoot startup process is divided into two steps: initializing a SpringApplication object and executing the run method of the object. Initialize (Object[] sources); initialize(Object[] sources);

private void initialize(Object[] sources) { if (sources ! = null && sources.length > 0) { this.sources.addAll(Arrays.asList(sources)); } // deduceWebEnvironment = deduceWebEnvironment(); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); / / find the entrance to the class this. MainApplicationClass = deduceMainApplicationClass (); }Copy the code

The initialization process. The most important thing is to find the spring through SpringFactoriesLoader factories file configuration ApplicationContextInitializer and ApplicationListener two interface implementation class name, In order to construct the corresponding example later. The main purpose of ApplicationContextInitializer is before ConfigurableApplicationContext do refresh, Do further Settings or with ConfigurableApplicationContext instance. ConfigurableApplicationContext inherited from ApplicationContext, its main offer the ability to set the ApplicationContext.

Implement a ApplicationContextInitializer is very simple, because it is only one way, but in most cases we don’t need to customize a ApplicationContextInitializer, even Spring Boot framework, It only registers two implementations by default, since the Spring container is so mature and stable that you don’t have to change it.

The purpose of ApplicationListener is less clear. It is a framework implementation of the Spring framework’s Java event listener mechanism, explained in detail in the Spring Event Listener section above. If you want to add a listener to your Spring Boot application, how do you do it?

Spring Boot provides two ways to add custom listeners:

  • throughSpringApplication.addListeners(ApplicationListener<? >... listeners)orSpringApplication.setListeners(Collection<? extends ApplicationListener<? >> listeners)Two methods to add one or more custom listeners
  • Now that the SpringApplication initialization process has been removed fromspring.factoriesGet to theApplicationListenerThe implementation class, then we directly in their own JAR packageMETA-INF/spring.factoriesAdd the configuration to the file:
org.springframework.context.ApplicationListener=\
cn.moondev.listeners.xxxxListener\
Copy the code

So much for the initialization of SpringApplication.

6.2 Spring Boot Startup Process

The whole process of Spring Boot application is wrapped in the springApplication. run method. The whole process is really too long, but essentially it is based on the Spring container startup to do a lot of extensions, according to this idea to look at the source code:

public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; configureHeadlessProperty(); / / (1) SpringApplicationRunListeners listeners = getRunListeners (args); listeners.starting(); Try {/ / (2) ApplicationArguments ApplicationArguments = new DefaultApplicationArguments (args); ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments); // ③ Banner printedBanner = printBanner(environment); // ④ context = createApplicationContext(); // ⑤ analyzers = new FailureAnalyzers(context); / / 6 prepareContext (the context, the environment, listeners, applicationArguments, printedBanner); / / all landowners refreshContext (context); / / end afterRefresh (context, applicationArguments); / / pet-name ruby listeners. Finished (context, null); stopWatch.stop(); return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); }}Copy the code

(1) through SpringFactoriesLoader find and load all SpringApplicationRunListeners, by calling the starting () method to notify all SpringApplicationRunListeners: The application starts. SpringApplicationRunListeners it is essentially a event publishers, it released at the start of application of different SpringBoot time points different application event type (ApplicationEvent), If any event listeners are interested in these events, they can be received and processed. Remember that during the initialization process, SpringApplication loads a set of ApplicationListeners? The startup process have not found a publish event code, actually have been here in SpringApplicationRunListeners implements.

Simply analyze the implementation process, first look at the source of the SpringApplicationRunListener:

Public interface SpringApplicationRunListener {/ / run run method this method is called immediately, users can work very early initialization void starting (); // Call void environmentPrepared(ConfigurableEnvironment Environment) after the Environment is ready and before ApplicationContext is created; / / ApplicationContext created immediately after calling void contextPrepared (ConfigurableApplicationContext context). / / ApplicationContext loaded, before the refresh calling void contextLoaded (ConfigurableApplicationContext context). / / when void finished before the end of the run method call (ConfigurableApplicationContext context, Throwable exception). }Copy the code

Only one SpringApplicationRunListener implementation class: EventPublishingRunListener. (1) the code can only access to a EventPublishingRunListener instance, let’s take a look at starting () method:

Public void starting () {/ / release a ApplicationStartedEvent enclosing initialMulticaster. MulticastEvent (new ApplicationStartedEvent(this.application, this.args)); }Copy the code

Follow this logic, you can be in (2) the prepareEnvironment () method found in the source of listeners. The environmentPrepared (environment); Namely SpringApplicationRunListener interface of the second method, it is not out you might expect, environmentPrepared () and ApplicationEnvironmentPreparedEvent issued another event. I don’t need to tell you what will happen next.

Create and configure the Environment to be used by the current application. The Environment is used to describe the current operating Environment of the application. It abstractions two aspects of the content: profile and properties. Different environments (eg: production environment, pre-release environment) can use different configuration files, and attributes can be obtained from configuration files, environment variables, command line parameters, and so on. Therefore, when the Environment is ready, resources can be retrieved from the Environment at any time throughout the application.

To sum up, the two lines of code in ② mainly accomplish the following things:

  • Determine if the Environment exists and create it if it does not (create it if it is a Web project)StandardServletEnvironment, otherwise createStandardEnvironment)
  • Configure Environment: Configure profile and properties
  • Calling SpringApplicationRunListenerenvironmentPrepared()Method to inform event listeners that the application’s Environment is ready

③ The SpringBoot application will output something like this when it starts:

. ____ _ __ _ _ / \ \ / ___ '_ __ _ _) (_ _ __ __ _ \ \ \ \ (\ ___ () |' _ | '_ | |' _ \ / _ ` | \ \ \ \ \ \ / ___) | | _) | | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.5.6. RELEASE)Copy the code

If you want to make this your own doodle, you can explore the implementation of the following Banner. I’ll leave that to you.

Create different ApplicationContext containers depending on whether they are Web projects.

5. Create a series of FailureAnalyzer. The process of creating FailureAnalyzer is to obtain all the classes that implement the FailureAnalyzer interface through the SpringFactoriesLoader, and then create the corresponding instances. FailureAnalyzer analyzes faults and provides diagnosis information.

(6) Initialize ApplicationContext.

  • Set the prepared Environment to ApplicationContext
  • Call all ApplicationContextInitializer traversalinitialize()Method to further process the already created ApplicationContext
  • Calling SpringApplicationRunListenercontextPrepared()Method to inform all listeners that ApplicationContext is ready
  • Load all the beans into the container
  • Calling SpringApplicationRunListenercontextLoaded()Method to inform all listeners that ApplicationContext has been loaded

Call the Refresh () method of the ApplicationContext to complete the final process available to the IoC container. What is a refresh container? To start the container, refer to section 1. So how do you refresh? Look at the following code:

/ / from the refresh () method of a code invokeBeanFactoryPostProcessors (the beanFactory);Copy the code

Look at the implementation of this method:

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); . }Copy the code

Get all the BeanFactoryPostProcessors to do some extra operations on the container. The BeanFactoryPostProcessor allows us to do some additional operations on the information held by the BeanDefinition registered to the container before the container instantiates the corresponding object. The getBeanFactoryPostProcessors here () method can get 3 Processor:

ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor
SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
ConfigFileApplicationListener$PropertySourceOrderingPostProcessor
Copy the code

There aren’t so many BeanFactoryPostProcessor implementation classes. Why are there only three? Because the initialization process access to various ApplicationContextInitializer and ApplicationListener, only the above three made similar to the following:

public void initialize(ConfigurableApplicationContext context) {
    context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
}
Copy the code

And then you can enter to PostProcessorRegistrationDelegate. InvokeBeanFactoryPostProcessors () method. This method in addition to traverse the above three spring BeanFactoryPostProcessor processing, can also obtain type for BeanDefinitionRegistryPostProcessor bean: Org. Springframework. Context. The annotation. InternalConfigurationAnnotationProcessor, The corresponding Class for ConfigurationClassPostProcessor. ConfigurationClassPostProcessor for analytical processing various annotations, including: @Configuration, @ComponentScan, @import, @propertysource, @importResource, @Bean. When dealing with @ import annotations, is called “automatic configuration > EnableAutoConfigurationImportSelector. In this section selectImports () to complete the function of automatic configuration. I won’t go into more details here, but if you’re interested, you can refer to Reference 6.

Find out if CommandLineRunner and ApplicationRunner are registered in the current context. If so, run through them.

Pet-name ruby, perform all SpringApplicationRunListener finished () method.

This is the whole startup process of Spring Boot. The core is to add various extension points on the basis of the initialization and startup of the Spring container. These extension points include: ApplicationContextInitializer, ApplicationListener and various spring BeanFactoryPostProcessor and so on. You don’t have to pay too much attention to the details of the process, or even understand them, as long as you understand when and how these extension points work and make them work for you.

The whole startup process is really very complicated, you can check some of the chapters and content in resources, compare the source code, have a look, I think you can figure it out eventually. In short, Spring is the core, understand the Spring container startup process, then the Spring Boot startup process is not a problem.

The resources

[1] Wang Fuqiang; SpringBoot revealed: rapid construction of microservice system; China Machine Press, 2016 [2] Wang Fuqiang; The Spring revelation; People’s Mail Press, 2009 [3] Craig Walls; Translated by Ding Xuefeng; Spring Boot; China Industry and Information Technology Publishing Group, Posts and Telecommunications Press, 2016 [4] In-depth Discussion of Java Class loader: www.ibm.com/developerwo… [5] spring boot of actual combat: automatic configuration principle analysis: blog.csdn.net/liaokailin/… [6] spring boot of actual combat: spring boot load Bean source code analysis: blog.csdn.net/liaokailin/…