How does SpringBoot automatically assemble components according to configuration?
Before analyzing the source code, let’s take a look at the theory of autoloader. Without the support of theory, we can hardly understand the essence of source code.
Start relying on
Spring Boot helps manage dependencies for projects by starting dependencies. Starting dependencies are simply special Maven dependencies and Gradle dependencies that take advantage of transitive dependency resolution to aggregate common libraries into several dependencies tailored for specific functionality.
Many starter dependencies are configured in spring-boot-starter-parent:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1. RELEASE</version>
<relativePath/> <! -- lookup parent from repository -->
</parent>
Copy the code
Spring Boot reduces the complexity of project dependencies by providing numerous start-up dependencies.
Automatically.
Automatic configuration of Spring Boot is an application startup process that takes many factors into account to determine which Spring configuration should and should not be used.
These are all things that Spring Boot’s automatic configuration takes into account.
- Spring’s
JdbcTemplate
Is it in the Classpath? If it is, and there isDataSource
Is automatically configuredJdbcTemplate
The Bean. Spring Security
Is it in the Classpath? If so, perform a very basic Web security setup.
Spring Boot’s automatic configuration makes nearly 200 such decisions every time an application starts. All this auto-configuration is designed to keep us from writing our own configuration.
JdbcTemplate and Spring Security auto-assembly
JdbcTemplate
Spring of the Boot DataSourceAutoConfiguration JdbcTemplate defined in the Bean is a very simple example, illustrates the @ ConditionalOnMissingBean how it works:
@Bean
@ConditionalOnMissingBean(JdbcOperations.class)
public JdbcTemplate jdbcTemplate(a) {
return new JdbcTemplate(this.dataSource);
}
Copy the code
The @Bean annotation is added to the jdbcTemplate() method so that a JdbcTemplateBean can be configured if needed.
However, it also adds the @conditionAlonmissingBean annotation, which requires that no JdbcOperations Bean currently exists.
If there is already a JdbcOperations Bean, the condition is not met and the jdbcTemplate() method is not executed.
Spring Security
Spring Boot automatically configure security configuration, one of the most important class is SpringBootWebSecurityConfiguration.
Here is a snippet of the code:
@Configuration
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
public class SpringBootWebSecurityConfiguration {
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER)
static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {}}Copy the code
Let’s first analyze these annotations.
@ConditionalOnClass
The Bean will only be built if security-related packages are introduced into the project.@ConditionalOnMissingBean
Note When there is no containerWebSecurityConfigurerAdapter
For instance, the default configuration is used.@ConditionalOnWebApplication
Note This must be a Web application and of typeservlet
。
Custom Configuration
Although Spring Boot provides some basic automatic configuration, many times you need to override some of the configuration yourself to meet your needs.
Security, for example, custom configuration, we need to inherit abstract class WebSecurityConfigurerAdapter, and can be injected into the container
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {}Copy the code
Let’s test to see if it’s already injected into the container
The test class:
public class MainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = newAnnotationConfigApplicationContext(SecurityConfig.class); System.out.println(applicationContext.getBean(WebSecurityConfigurerAdapter.class)); }}Copy the code
It turns out to have been injected into the container:
version
Springboot version: 2.3.1.release
Spring version: 5.2.7.release
Source code analysis
Through the description of the above theory, then we analyze Springboot related source code, see how to achieve automatic assembly Springboot?
An overview of the
Let’s start with a picture:
Comments:
@SpringBootApplication
This is the core annotation of SpringBoot, and of course the composite annotation@EnableAutoConfiguration
It is the master switch for automatic assembly. We’ll start with this annotation to enhance our understanding of automatic configuration.@Import(AutoConfigurationImportSelector.class)
Import automatic configurationImportSelectot
Class.
Core classes and methods:
AutoConfigurationImportSelector
Import classes or beans that need to be auto-assembled.getCandidateConfigurations
Gets the configuration classes for all components.AutoConfigurationMetadataLoader.loadMetadata
Conditions for loading all autowiring component configuration classes (@Conditional
Filter conditions).filter.match(candidates, this.autoConfigurationMetadata)
Configure all classes based on each component@Conditional
Conditional filtering is performed.
Configuration file:
Meta-inf /spring.factories stores the full pathnames of all the configuration classes for the component.
Meta-inf /spring-autoconfigure-metadata.properties stores all the filtering conditions when the component loads the configuration class.
Source details
SpringBootApplication
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration // Turn on automatic assembly switch
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
Copy the code
EnableAutoConfiguration
This note acts as a switch on automatic assembly.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
// Import selector, identify the AutoConfigutaion class in each component and load it into the container
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// Automatic assembly switch, default true, can be set in application.properties
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
// No loaded beans are requiredClass<? >[] exclude()default {};
// No loaded beans are required
String[] excludeName() default {};
}
Copy the code
Focus onAutoConfigurationImportSelector
This class implements the ImportSelector interface and, most importantly, the selectImports method, which injects beans that need to be injected into the container based on the configuration file (spring.Factories).
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// Check whether the automatic assembly switch is on
if(! isEnabled(annotationMetadata)) {return NO_IMPORTS;
}
// Get all beans that need to be assembled
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
Copy the code
First of all, let’s look at how to judge the automatic assembly switch:
protected boolean isEnabled(AnnotationMetadata metadata) {
// Determine the class of the current instance
if (getClass() == AutoConfigurationImportSelector.class) {
/ / return spring. The boot. Enableautoconfiguration values, if it is null, return true
/ / spring. The boot. Enableautoconfiguration can be configured in the configuration file, not the null
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}
Copy the code
Next, let’s look at how to get the bean we need to assemble:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// Check the automatic assembly switch
if(! isEnabled(annotationMetadata)) {return EMPTY_ENTRY;
}
Exclude ()/excludeName()
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// Get all the configuration classes that need to be auto-assembled and read meta-INF /spring.factories
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// go to Set and then go to List
configurations = removeDuplicates(configurations);
// Get excludeName from the exclude/excludeName attribute of EnableAutoConfiguration
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
Configurations Check if the classes you want to exclude are in configurations and do not report an error
checkExcludedClasses(configurations, exclusions);
// Remove exclusions from configurations
configurations.removeAll(exclusions);
Configurations are filtered out if the @conditional condition is not true
configurations = getConfigurationClassFilter().filter(configurations);
/ / the AutoConfigurationImportEvent binding on all AutoConfigurationImportListener subclass instance
fireAutoConfigurationImportEvents(configurations, exclusions);
Configurations, exclusions) group
return new AutoConfigurationEntry(configurations, exclusions);
}
Copy the code
Visible selectImports () is the core of AutoConfigurationImportSelector method
The functions of this method are as follows:
- To obtain
META-INF/spring.factories
In theEnableAutoConfiguration
The correspondingConfiguration
The class list - by
@EnableAutoConfiguration
In the annotationsexclude/excludeName
Filter the parameters once - By private inner class
ConfigurationClassFilter
Filter once, that is, not satisfied@Conditional
The configuration of the class
Reading configuration Files
The meta-inf/spring – autoconfigure – metadata. The properties:
Initialize meta-INF/Spring-autoconfigure-metadata. properties in the constructor of the private static inner class ConfigurationClassFilter as follows
ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
this.filters = filters;
}
Copy the code
AutoConfigurationMetadataLoader:
final class AutoConfigurationMetadataLoader {
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
private AutoConfigurationMetadataLoader(a) {}static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
returnloadMetadata(classLoader, PATH); }... }Copy the code
The meta-inf/spring. Factories:
Through getCandidateConfigurations method reads the meta-inf/spring. The class that is configured in the factories:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// Load all the configuration classes in meta-INF/spring.Factories
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
public static List<String> loadFactoryNames(Class
factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
// This method reads meta-INF /spring.factories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if(result ! =null) {
return result;
}
try {
// FACTORIES_RESOURCE_LOCATION : META-INF/spring.factories 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
extension
For official components, the condition is used to determine whether classes should be auto-assembled or not. For third-party components, the SPI mechanism is used to implement extensions
- The official package spring – the boot – starter – XXX
- Third-party package XXX-spring-boot-starter
Both official and third-party components are automatically assembled through the above mechanism.
The core of Springboot’s automatic configuration: passImportSelector
Import configuration classes of components in batches to IOC containers.
Knowledge extension:
@Conditional Details: juejin.cn/post/684490…
@Enable* Module driver details: juejin.cn/post/684490…
ImportSelector batch dynamic import beans: juejin.cn/post/684490…
Original address of the article
Autumn200.com/2020/06/27/…