SpringBoot is pretty much standard now, except for older projects or more conservative enterprises.

Before SpringBoot, using SSH or SSM required a lot of XML configuration files, and most of these configurations were the same in every project.

Although they are all the same, projects need to be configured, and it can take hours to configure and minutes to write code, which slows the project down. SpringBoot is designed to solve this problem and improve development efficiency.

Anyone who has used SpringBoot knows that using SpringBoot Initializer in IDEA allows you to quickly configure projects and write a Controller to quickly set up Web projects.

SpringBoot provides us with a number of starter servers that already configure common configurations for us. If we need to change them, we can configure them in application.yml.

SpringBoot can do this because of its design policy, out-of-the-box and convention over configuration.

Let’s take a look at what SpringBoot does for us.

Automatically.

To use SpringBoot, we need to specify the parent project

Basic configuration

The POM file specifies the parent parent project

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> The < version > 2.2.0. RELEASE < / version > < relativePath / > <! -- lookup parent from repository --> </parent>Copy the code

Spring-boot-starter-parent dependencies: Spring-boot-starter-parent dependencies

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> < version > 2.2.0. RELEASE < / version > < relativePath >). /.. /spring-boot-dependencies</relativePath> </parent>Copy the code

Spring-boot-dependencies manage dependencies and version numbers. Spring-boot-dependencies manage dependencies and version numbers

< properties > < activemq. Version > 5.15.10 < / activemq version > < antlr2. Version > 2.7.7 < / antlr2 version > < appengine - SDK version > 1.9.76 < / appengine - SDK version > < Artemis. Version > 2.10.1 < / Artemis. Version > < aspectj version > 1.9.4 < / aspectj version > < assertj. Version > 3.13.2 < / assertj version > < atomikos version > 4.0.6 < / atomikos version > < awaitility. Version > 4.0.1 < / awaitility version > < bitronix version > 2.1.4 < / bitronix version > < build helper - maven - plugin. Version > 3.0.0 < / build - helper - maven - plugin. Version > The < byte - buddy. Version > 1.10.1 < / byte - buddy. Version >... Too much, </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> < artifactId > spring - the boot < / artifactId > < version > 2.2.0. RELEASE < / version > < / dependency > < the dependency > < the groupId > org. Springframework. Boot < / groupId > < artifactId > spring - the boot - test < / artifactId > < version > 2.2.0. RELEASE < / version > </dependency> <dependency> <groupId>org.springframework.boot</groupId> < artifactId > spring - the boot - test - autoconfigure < / artifactId > < version > 2.2.0. RELEASE < / version > < / dependency > < the dependency > <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-actuator</artifactId> The < version > 2.2.0. RELEASE < / version > < / dependency > < dependencies >... <dependencyManagement>Copy the code

Our parent project, Spring-boot-starter -parent, also helped us specify the configuration file format

<build> <resources> <resource> <filtering>true</filtering> <directory>${basedir}/src/main/resources</directory> <! -- Specifies the format of the configuration file, Yaml => properties --> <includes> <include>**/application*. Yml </include> <include>**/application*.yaml</include> <include>**/application*.properties</include> </includes> </resource> <resource>  <directory>${basedir}/src/main/resources</directory> <excludes> <exclude>**/application*.yml</exclude> <exclude>**/application*.yaml</exclude> <exclude>**/application*.properties</exclude> </excludes> </resource> </resources> <build>Copy the code

Starter starter

SpringBoot encapsulates the dependencies and dependencies required by each usage scenario into an initiator starter. When we need to introduce functions of a certain domain, we can directly rely on the corresponding Starer.

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> < version > 2.2.0. RELEASE < / version > < / dependency >Copy the code

For example, we commonly use Web development, need to rely on SpringMVC, SpringBoot provides spring-boot-starter-Web initiator

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
Copy the code

We clicked on the starter, which defined the following dependencies for us:

  1. Spring-boot-starter SpringBoot indicates a basic SpringBoot initiator
  2. Spring-boot-starter-json Indicates the initiator of JSON serialization or deserialization
  3. Spring – the boot – starter – tomcat embedded tomcat
  4. Spring-web and Spring-Web MVC, that’s our SpringMVC
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> < version > 2.2.0. RELEASE < / version > < scope > compile < / scope > < / dependency > < the dependency > <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> < version > 2.2.0. RELEASE < / version > < scope > compile < / scope > < / dependency > < the dependency > <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> < version > 2.2.0. RELEASE < / version > < scope > compile < / scope > < / dependency > < the dependency > <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> The < version > 2.2.0. RELEASE < / version > < scope > compile < / scope > < exclusions > < exclusion > <artifactId>tomcat-embed-el</artifactId> <groupId>org.apache.tomcat.embed</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> < version > 5.2.0. RELEASE < / version > < scope > compile < / scope > < / dependency > < the dependency > < the groupId > org. Springframework < / groupId > < artifactId > spring - webmvc < / artifactId > < version > 5.2.0. RELEASE < / version > <scope>compile</scope> </dependency> </dependencies>Copy the code

Start the class SpringBootApplication

SpringBoot requires us to provide a boot class with the @SpringBootApplication annotation in its header, which is the core of SpringBoot boot.

@SpringBootApplication public class SpringbootEsApplication { public static void main(String[] args) { SpringApplication.run(SpringbootEsApplication.class, args); Log.info (" Project started successfully, access address: http://localhost:8081/"); }}Copy the code

So let’s click on the @SpringBootApplication 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) }) @ConfigurationPropertiesScan public @interface SpringBootApplication {// omit attribute... }Copy the code

We’ll see that SpringBootApplication is a composite annotation, the most important of which are @springbootconfiguration and @enableautoconfiguration. The @ComponentScan annotation is a package scan. Because no package scan is configured, the default is to scan the package of the class that identifies the annotation, and the subpackages below it, so the startup class is usually under the root package.

  • @ SpringBootConfiguration annotations

We found the @SpringBootConfiguration annotation, and the main thing is to add the @Configuration annotation. We know that the @Configuration annotation represents a Javaconfig-like Spring container, so our launcher class is also a container.

The SpringBootConfiguration annotation is done, let’s go to the next annotation, okay

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(
    proxyBeanMethods = false
)
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
Copy the code
  • @ EnableAutoConfiguration annotations

@ EnableAutoConfiguration annotations, the annotations are mainly @ Import (AutoConfigurationImportSelector. Class). @ Import annotations, help us to Import the AutoConfigurationImportSelector this class

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<? >[] exclude() default {}; String[] excludeName() default {}; }Copy the code
  • AutoConfigurationImportSelector class

AutoConfigurationImportSelector class implements the DeferredImportSelector interface, the interface inheritance ImportSelector interface, can ask autotype selectImports () method.

The ImportSelector interface is used to import @Configuration, and DeferredImportSelector is a deferred import. When all @Configuration is processed, Call DeferredImportSelector.

So AutoConfigurationImportSelector class is delay the imported, all @ Configuration after the treatment, then call its selectImports () method.

SelectImports () method, which invokes the getAutoConfigurationEntry () method, and getAutoConfigurationEntry () call again getCandidateConfigurations () method. While getCandidateConfigurations () method is the key!

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { @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()); } protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, 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.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }}Copy the code
  • GetCandidateConfigurations () method

Method, called SpringFactoriesLoader. LoadFactoryNames (), two parameters, the incoming EnableAutoConfiguration this Class and beans.

The loadFactoryNames() method, which returns a collection and throws an exception if the collection is empty after entering the Assert assertion in the next sentence.

This configuration set is returned.

//AutoConfigurationImportSelector protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; } protected Class<? > getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; } protected ClassLoader getBeanClassLoader() { return this.beanClassLoader; }Copy the code
  • SpringFactoriesLoader

The loadFactoryNames() method, which gets the Class of the EnableAutoConfiguration annotation passed in, calls the loadSpringFactories() method. The loadSpringFactories() method reads the Spring.factories configuration file from the meta-INF directory in the JAR package.

If it cannot be read, an empty collection is returned.

Public final class SpringFactoriesLoader {public final class SpringFactoriesLoader { Public static final String FACTORIES_RESOURCE_LOCATION = "meta-INF /spring.factories"; public static List<String> loadFactoryNames(Class<? > factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result ! = null) { return result; } try { Enumeration<URL> urls = (classLoader ! = null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<? ,? > entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); }}}Copy the code
  • The spring.factories configuration file

Take a starter, such as spring-boot-autoconfigure, go to its meta-INF directory, find the Spring. factories file, and open it.

We found that the file has a lot of autoconfiguration properties configured (there are too many deletions!). . It is in the form of key-value. For example, a Key is the full class name of EnableAutoConfiguration, and its Value is several classes whose names end in AutoConfiguration, separated by commas.

The loadFactoryNames() method we just traced, passed in the Class of EnableAutoConfiguration, is the set of values to which it corresponds from the Spring.Factories configuration file.

We ServletWebServerFactoryAutoConfiguration, for example, in check

# omit other configuration... # Auto Configure !!!!!!!! The point here is!!!!!!!! org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\ Org. Springframework. Boot. Autoconfigure. Webservices. Client. WebServiceTemplateAutoConfiguration # omit other configuration...Copy the code
  • ServletWebServerFactoryAutoConfiguration class

We see the class class head, have @ EnableConfigurationProperties annotations, the said loading configuration attribute, specifies a ServerProperties class here.

Let’s go to the ServerProperties class and see

@Configuration(proxyBeanMethods = false) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @ConditionalOnClass(ServletRequest.class) @ConditionalOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties(ServerProperties.class) @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) public class ServletWebServerFactoryAutoConfiguration { / /... Omit}Copy the code
  • ServerProperties class

This is a class that corresponds to the configuration information. It has the @ConfigurationProperties annotation on its head, which maps the contents of the configuration items in the configuration file to the variables of our class.

On the annotations, the configured prefix attribute represents the server.xxx family configuration. For example, we configure port: server.port, which maps our configuration to ServerProperties.

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
	/**
	 * Server HTTP port.
	 */
	private Integer port;

	/**
	 * Network address to which the server should bind.
	 */
	private InetAddress address;
}
Copy the code

So far, the automatic configuration process is basically through, to sum up:

When the main method of the SpringBoot boot class starts, it looks for the @EnableAutoConfiguration annotation, which is on the @SpringBootApplication. The @ EnableAutoConfiguration annotations, use the @ Import annotations, imported the AutoConfigurationImportSelector class. The class goes to the meta-INF/Spring. factories configuration file, which configures a series of AutoConfiguration classes that end in AutoConfiguration. Each configuration class has a configuration class at the end of the Properties, which corresponds to the configuration item in YML one by one, which is equivalent to binding the configuration into the object.

If you just want to know about the interview, you can stop here, but if you want more in-depth, you should continue to follow.

If you want to continue with, there is a doubt, automatic assembly is when to start, is actually AutoConfigurationImportSelector selectImports on class () method, also don’t know what it is called.

When to start automatic assembly

We back to the Spring, the Spring application to start, in AbstractApplicationContext class, call the refresh () method.

Refresh () method calls the invokeBeanFactoryPostProcessors () method, this method is used to deal with spring BeanFactoryPostProcessor interface, A subinterface and spring BeanFactoryPostProcessor BeanDefinitionRegistryPostProcessor.

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {@override public void refresh() throws BeansException, IllegalStateException {// omit irrelevant code... invokeBeanFactoryPostProcessors(beanFactory); // omit irrelevant code... } } //BeanDefinitionRegistryPostProcessor public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException; }Copy the code
  • ConfigurationClassPostProcessor class

A subinterface BeanDefinitionRegistryPostProcessor, have an implementation class ConfigurationClassPostProcessor, it is designed to handle @ the Configuration notes.

ProcessConfigBeanDefinitions () method, is the class processing @ the Configuration notes. We use the Parse () method of the ConfigurationClassParser class.

Let’s go to the parse() method and take a look

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {public void processConfigBeanDefinitions (BeanDefinitionRegistry registry) {/ / omit part of the code / / Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); Do {// Parse the class that handles @Configuration annotations parser.parse(candidates); parser.validate(); Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); candidates.clear(); if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames)); Set<String> alreadyParsedClasses = new HashSet<>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } for (String candidateName : newCandidateNames) { if (! oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && ! alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (! candidates.isEmpty()); }}Copy the code
  • The Parse () method of the ConfigurationClassParser class

First class have an inner class DeferredImportSelectorHandler, constructor ConfigurationClassParser instance, create the inner class instance.

The parse () method call, the last line calls the processDeferredImportSelectors () method.

class ConfigurationClassParser { public void parse(Set<BeanDefinitionHolder> configCandidates) { this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>(); for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); }} / / key processDeferredImportSelectors (); }}Copy the code
  • ProcessDeferredImportSelectors () method

Focus on String [] imports = deferredImport. GetImportSelector () selectImports (configClass. For getMetadata ()); .

Call is DeferredImportSelectorHolder class, it preserved the DeferredImportSelector reference, in the for loop, call the DeferredImportSelector selectImports () method, In the class to call to our previous analysis AutoConfigurationImportSelector selectImports () method.

private void processDeferredImportSelectors() { List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors; this.deferredImportSelectors = null; Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR); for (DeferredImportSelectorHolder deferredImport : deferredImports) { ConfigurationClass configClass = deferredImport.getConfigurationClass(); try { String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata()); processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", ex); }}} // Class, Save the configuration class and DeferredImportSelector reference private static class DeferredImportSelectorHolder {private final ConfigurationClass configurationClass; private final DeferredImportSelector importSelector; public DeferredImportSelectorHolder(ConfigurationClass configClass, DeferredImportSelector selector) { this.configurationClass = configClass; this.importSelector = selector; } public ConfigurationClass getConfigurationClass() { return this.configurationClass; } public DeferredImportSelector getImportSelector() { return this.importSelector; }}Copy the code

The resources

SpringBoot: Take a serious look at the principle of automatic assembly

Spring Boot interview killer – automatic configuration principle

In-depth understanding of SpringBoot autoassembly