SpringBoot principle -- automatic configuration

Posted by skippence on Wed, 17 Nov 2021 13:07:36 +0100

Original website:

brief introduction

explain

        This article describes how spring boot implements automatic configuration.

Problem elicitation

SpringBoot has the following functions:

  1. The created SpringBoot project (if the startup class is DemoApplication) can be run directly
  2. You can customize the configuration in the configuration file
  3. You can use a function by adding an annotation to the startup class

So how does SpringBoot implement these functions?

entrance

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

 @SpringBootApplication

@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 {
    ...
}
  • @SpringBootConfiguration   // It is only the encapsulation of the @ Configuration annotation, which has the same function as the @ Configuration annotation.
    • @Configuration / / add components to the IoC container through javaConfig
  • @EnableAutoConfiguration
    • @AutoConfigurationPackage / / automatically configure the package, scan the classes under the path specified by @ ComponentScan, and add them to the IOC
    • @Import(AutoConfigurationImportSelector.class) / / add the bean s defined in META-INF/spring.factories to the IoC container
  • @ComponentScan / / package scan.
    • The root path of the package scanned by default is   The location of the package where the Spring Boot main program startup class is located.
    • During the scanning process, the @ AutoConfigurationPackage annotation described above is used to parse, so as to get the specific location of the package where the main program startup class of the Springboot project is located.

@SpringBootConfiguration

brief introduction

        Encapsulation of @ Configuration annotation, which indicates that the current class is a Configuration class, and will include one or more instances of methods marked with @ Bean annotation declared in the current class into the spring container

Source code

package org.springframework.boot;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

@EnableAutoConfiguration

brief introduction

        @ The EnableAutoConfiguration annotation indicates that the auto configuration function is enabled.

        @ The import annotation is used to import Configuration classes. Import AutoConfigurationImportSelector here. When the container is refreshed, it will call the selectImports method of the AutoConfigurationImportSelector class, scan the META-INF/spring.factories file, instantiate the Configuration item corresponding to org.springframework.boot.autoconfigure.EnableAutoConfiguration into the corresponding Configuration class in the form of JavaConfig marked with @ Configuration through reflection, and load it into the IoC container.

@EnableAutoConfiguration

@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 {};
}

@AutoConfigurationPackage

Auto configuration package  

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// Import Registrar component class
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
public abstract class AutoConfigurationPackages {
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            // Scan the components under the package of the main program class and its sub packages into the Spring container
            register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        }

        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImports(metadata));
        }
	}
    // Other codes
}

@Import(AutoConfigurationImportSelector.class)

        Import the AutoConfigurationImportSelector class into the Spring container. AutoConfigurationImportSelector can help SpringBoot applications load all qualified configurations into the IoC container (ApplicationContext) created and used by the current SpringBoot.
In this class, the selectImports() method is used to tell SpringBoot which components need to be imported

selectImports   // Import components into container

sketch

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
	// This method tells springboot which components need to be imported
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		//Judge whether the enableautoconfiguration annotation is enabled. It is enabled by default (whether to perform automatic assembly)
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		//1. Load the configuration file META-INF/spring-autoconfigure-metadata.properties to get all the conditions that support automatic configuration classes
		//Function: SpringBoot uses an Annotation processor to collect some automatic assembly conditions, which can be configured in META-INF/spring-autoconfigure-metadata.properties.
		// SpringBoot will filter the collected @ Configuration once, and then eliminate the Configuration classes that do not meet the conditions
		// Fully configured class name. Condition = value
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
		
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
	
	//Other methods
}

Explain in detail

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // Judge whether the enableautoconfiguration annotation is enabled. It is enabled by default (whether to perform automatic assembly)
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}

        // Get auto configuration item
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // Gets a list of all default supported autoconfiguration class names
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        // Remove the duplicate configuration classes, and the starter written by yourself may have duplicate configuration classes
		configurations = removeDuplicates(configurations);
        // If you do not want some automatic configuration classes to be configured automatically, you can configure them through the exclude or excludeName property of EnableAutoConfiguration,
        // It can also be configured in the configuration file through the configuration item "spring.autoconfigure.exclude".
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        // Verification exclusion class (the class specified in exclusions must be an auto configuration class, or an exception will be thrown)
		checkExcludedClasses(configurations, exclusions);
        // From configurations, remove all configuration classes that you do not want to configure automatically
		configurations.removeAll(exclusions);
        // Filter the candidate automatic configuration classes, and filter the automatic configuration classes that finally meet the corresponding operation environment of the current project according to the dependencies added in the project pom.xml
        // There are two ways to determine whether to load a class:
        //   Judge according to spring-autoconfigure-metadata.properties.
        //   Judge whether @ Conditional is satisfied
		configurations = getConfigurationClassFilter().filter(configurations); 
        Import automatic configuration into event notification listener
        // After filtering, the implementation class of AutoConfigurationImportListener in META-INF/spring.factories file in Jar package under the classpath will be automatically loaded,
        // And trigger the fireAutoConfigurationImportEvents event event.
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}
    
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        // Getspringfactoryesloaderfactory class gets the default EnableAutoConfiguration.class class name and passes in the loadFactoryNames method
		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;
	}
    
    // Default EnableAutoConfiguration.class class name
    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }
}
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 {
            // If the class loader is not null, load "META-INF/spring.factories" and encapsulate the full path information of the configuration class set in it as Enumeration 
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
            // Loop the Enumeration class object and generate the Properties object according to the corresponding node information,
            // The value is obtained through the passed in key, and the value is cut into small strings and converted into Array method result set
			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);
		}
	}
}
  • First, notice the selectImports method. In fact, it can be seen from the method name that this method is used to import components into the container, and then jump to getAutoConfigurationEntry. The method is used to obtain automatic configuration items.
  • The getCandidateConfigurations method is to get an auto configuration List, which contains all the auto configuration class names.
  • SpringFactoriesLoader#loadFactoryNames: jump to the loadSpringFactories method and find that the ClassLoader class loader specifies a FACTORIES_RESOURCE_LOCATION constant.
  • Then use PropertiesLoaderUtils to wrap the contents of these files scanned by ClassLoader into properties objects, obtain the corresponding values of EnableAutoConfiguration.class class (class name) from properties, and then add them to the container.

summary

The steps to realize automatic assembly at the bottom of Springboot are:

Springboot application startup;
@SpringBootApplication works;
@EnableAutoConfiguration;
@AutoConfigurationPackage: this composite annotation is mainly @ Import(AutoConfigurationPackages.Registrar.class). It imports the Registrar class into the container, and the function of the Registrar class is to scan the peer directory of the main configuration class and its sub packages, and import the corresponding components into the Springboot creation management container
@Import(AutoConfigurationImportSelector.class): it imports the AutoConfigurationImportSelector class into the container. The function of AutoConfigurationImportSelector class is to use the internal tool class SpringFactoriesLoader to find the information in the jar package used on the classpath through the selectImports method   MATE-INF/spring.factories is loaded, and the configuration class information is handed over to the SpringFactory loader for a series of container creation processes.

Default configuration

brief introduction

explain

There are many default configurations in springboot, and the corresponding dependencies are spring boot starter XXX. In most cases, these dependencies can be used directly by importing them without adding @ EnableXxx.

The automatic configuration classes corresponding to spring boot starter XXX supported by springboot by default are shown in: Automatic configuration (English official website)

Case analysis: AOP automatic configuration

Other web sites

Look at the source code with problems - spring AOP (I) - simple book

Configuration class  

How to find configuration classes has been mentioned in the "Introduction" above. The configuration classes of spring boot starter AOP here are:

org/springframework/boot/autoconfigure/aop/AopAutoConfiguration.class in spring-boot-autoconfigure-2.3.0.RELEASE.jar package

package org.springframework.boot.autoconfigure.aop;

import org.aspectj.weaver.Advice;

import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
 * Auto-configuration} for Spring's AOP support. Equivalent to enabling
 * {@link EnableAspectJAutoProxy @EnableAspectJAutoProxy} in your configuration.
 * <p>
 * The configuration will not be activated if {@literal spring.aop.auto=false}. The
 * {@literal proxyTargetClass} attribute will be {@literal true}, by default, but can be
 * overridden by specifying {@literal spring.aop.proxy-target-class=false}.
 *
 * @author Dave Syer
 * @author Josh Long
 * @since 1.0.0
 * @see EnableAspectJAutoProxy
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Advice.class)
	static class AspectJAutoProxyingConfiguration {

		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = false)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
				matchIfMissing = false)
		static class JdkDynamicAutoProxyConfiguration {

		}

		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = true)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
				matchIfMissing = true)
		static class CglibAutoProxyConfiguration {

		}

	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
			matchIfMissing = true)
	static class ClassProxyingConfiguration {

		ClassProxyingConfiguration(BeanFactory beanFactory) {
			if (beanFactory instanceof BeanDefinitionRegistry) {
				BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
				AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
		}

	}

}

analysis

From the above code, you can see the following two points:

aop on and off

There are comments on the class: @ ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)

That is, aop is enabled by default. (can be closed by spring.aop.auto=false)

  Opening of AspectJ

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration{

}

After adding the spring boot starter AOP dependency, aspectjweaver-1.9.4.jar will be automatically introduced, and there is Advice.class in aspectjweaver-1.9.4.jar. Therefore, the conditions required for the @ ConditionalOnClass annotation have been met.

Opening of CGLIB

@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
        matchIfMissing = true)
static class CglibAutoProxyConfiguration {

}

That is, if apring.aop.proxy-target-class is not configured, CGLIB will be used, and @ EnableAspectJAutoProxy(proxyTargetClass = true) will take effect.

It is also verified in another place: find the following configuration information in META-INFO/spring-configuration-metadata.json of spring-boot-autoconfigure-2.3.0.RELEASE.jar:

Other web sites

What is the SpringBoot startup process?
Spring boot auto configuration details_ Growth is a lifetime thing - CSDN blog
In depth principle and source code analysis of SpringBoot

SpringBoot application startup process analysis - Zhihu

Topics: Spring Spring Boot