The spring-boot version of this article is 2.5.0-Snapshot
Before learning springboot source code, I read a lot of @SpringBootApplication parsing articles on the Internet. After learning Spring source code and part of Springboot source code, I found that most of those articles are not complete, and even some are wrong. So I want to write a slightly more complete analysis of the article.
Front knowledge
-
The inheritability (or factionality, transitivity) of notes, however you want to call it.
@C @interface B{}// Define a B annotation with C annotation added to the B annotation @B class A{}// Class A uses the B annotation, which also uses the C annotation Copy the code
When a class adds an annotation, it also adds an annotation within that annotation, regardless of the number of nested layers.
-
BeanDefinition interface
Spring uses ASM technology to load class files, and instead of creating objects directly, it parses information about the class, such as all the annotations, Aware interfaces, and FactoryBean interfaces that spring needs to process. This interface subclass is very powerful, and you can use this BeanDefinition to set a class’s parent class, interface, constructor to use, methods to call during initialization, parameters to pass in, and so on. It is possible to completely create a new class through BeanDefinition, which is an important way to extend third-party components. Spring builds a bean based on the information contained in the BeanDefinition.
-
The @import function, which requires some understanding of spring’s parsing process, is outlined here.
@Import(B.class) // Select different processing modes according to the type of class B class A{} Copy the code
If Spring finds an @import annotation, it will parse the annotation’s class, which generally has three types
-
A subclass of the ImportSelector interface
public interface ImportSelector { // The annotation information metadata passed in for the annotated class can be obtained from importingClassMetadata // All annotation information, including passed annotations // Return a String[] containing the full class name of a class. Spring loads these classes as beans according to the full class name String[] selectImports(AnnotationMetadata importingClassMetadata); } Copy the code
-
DeferredImportSelector
//DeferredImportSelector inherits ImportSelector but has slightly different processing logic public interface DeferredImportSelector extends ImportSelector{ // Use getImportGroup to return an implementation class of the Group. Use the Group's two methods to return an Entry // Spring initializes based on the information contained in Entry // If getImportGroup returns null, call ImportSelector selectImports again @Nullable default Class<? extends Group> getImportGroup() { return null; } // An inner class interface Group { void process(AnnotationMetadata metadata, DeferredImportSelector selector); Iterable<Entry> selectImports(a); // Inner class of inner class class Entry { private final AnnotationMetadata metadata; private finalString importClassName; }}}Copy the code
The actual logic of how this DeferredImportSelector is handled is a bit more complicated, so if you’re interested, look at the Spring source code
class ConfigurationClassParser { public void parse(Set<BeanDefinitionHolder> configCandidates) { / /... / / DeferredImportSelector processing this.deferredImportSelectorHandler.process(); }}Copy the code
-
A subclass of ImportBeanDefinitionRegistrar
// The annotation information metadata passed in for the annotated class, BeanDefinition's registry default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // Here you can get all beandefinitions through Registry and modify them It is also possible to register some new BeanDefinitions through Registry } Copy the code
-
Except for the above three, it’s a normal class, loaded with general logic, like @compoent
Because @import has these four processing modes, the general functions of @enablexxx are implemented by @import
-
@SpringBootApplication
Now you can start analyzing this annotation.
// omit JDK meta-annotations
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
Copy the code
-
@ComponentScan
// There are two custom exclusion filters that Spring creates through reflection to judge each scanned class @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) Copy the code
public class TypeExcludeFilter implements TypeFilter{ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { // Get all subclasses of TypeExcludeFilter, // Call match to determine, as long as one is true, then return true for (TypeExcludeFilter delegate : getDelegates()) { if (delegate.match(metadataReader, metadataReaderFactory)) { return true; }}return false; }}Copy the code
public class AutoConfigurationExcludeFilter implements TypeFilter{ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { // This class returns true if it has the @Configuration annotation and is an auto-configuration class Spring-boot loads the key/value pairs in spring.factories with the full name of the class // See @enableAutoConfiguration below returnisConfiguration(metadataReader) && isAutoConfiguration(metadataReader); }}Copy the code
// @componentScan package fetch logic Set<String> basePackages = new LinkedHashSet<>(); // Get the value to parse basePackages String[] basePackagesArray = componentScan.getStringArray("basePackages"); Collections.addAll(basePackages, basePackagesArray); // Get the package name from the basePackageClasses for(Class<? > clazz : componentScan.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } // If there is no configuration package in the componentScan annotation, inject the package of the annotated class, which is the logic defined in Spring // So if you put configuration classes on top of other packages in Spring, there is no need to configure package paths if (basePackages.isEmpty()) { basePackages.add(ClassUtils.getPackageName(declaringClass)); } Copy the code
-
@EnableAutoConfiguration
@AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) // Import a class public @interface EnableAutoConfiguration { Copy the code
public class AutoConfigurationImportSelector implements DeferredImportSelector{ // Use the AutoConfigurationGroup method to get Entry @Override public Class<? extends Group> getImportGroup() { returnAutoConfigurationGroup.class; }}Copy the code
private static class AutoConfigurationGroup implements DeferredImportSelector.Group{ // Write a simple logic for emmm. // Get the key-value pairs in classpath*:spring.factories and store them in a collection. // Then get the EnableAutoConfiguration value of the full class named key from the collection, wrapped as an Entry // Return Iterable
after merging, excluding, sorting etc. // Spring gets the information in the Entry to load the corresponding class } Copy the code -
AutoConfigurationPackage is a feature that many articles don’t write, and many articles attribute to it spring-Boot’s ability to scan current packages and subpackages. This is a bit wrong, because scanning packages is a feature for @ComponentScan. And is the logic defined in Spring, spring-boot is just used.
@Import(AutoConfigurationPackages.Registrar.class)/ / import a ImportBeanDefinitionRegistrar subclasses public @interface AutoConfigurationPackage { String[] basePackages() default{}; Class<? >[] basePackageClasses()default {}; } Copy the code
public abstract class AutoConfigurationPackages { // This is a static inner class static class Registrar implements ImportBeanDefinitionRegistrar { // Spring calls this method @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // The second parameter is the package path obtained from the AutoConfigurationPackage annotation // If the package path cannot be resolved, use the package path of the annotated class register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); }}private static final String BEAN = AutoConfigurationPackages.class.getName(); // The registerBeanDefinitions method above calls this method public static void register(BeanDefinitionRegistry registry, String... packageNames) { / / the logic is very simple, is to determine any AutoConfigurationPackages whole class BeanDefinition called name // If so, get and add the package path to beanDefinition if (registry.containsBeanDefinition(BEAN)) { BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN); beanDefinition.addBasePackages(packageNames); } else { / / if there is no the BeanDefinition, then create a new BasePackagesBeanDefinition registered in the spring registry.registerBeanDefinition(BEAN, newBasePackagesBeanDefinition(packageNames)); }}// Get the package path public static List<String> get(BeanFactory beanFactory) { // Get the saved package path from BasePackages return beanFactory.getBean(BEAN, BasePackages.class).get(); } // This is also a static inner class static final class BasePackages { List<String> get(a) { // Returns the package path passed in return this.packages; }}}Copy the code
At the top of the logic is simple, if there is no additional configuration, so is the annotated class package path as a BasePackagesBeanDefinition and register in the spring.
So what does this do? Its role is to provide other components with spring-boot scan paths. Such as: mybatis – spring – the boot – autoconfigure automatic configuration class imported AutoConfiguredMapperScannerRegistrar one line of code in the source code.
/ / this is called the get method of the AutoConfigurationPackages above the package path
// Mybatis will scan the classes in the package path and filter out the annotated @mapper interface
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
Copy the code
-
@SpringBootConfiguration
@Configuration // This annotation is equivalent to an @configuration public @interface SpringBootConfiguration { Copy the code
The @springBootConfiguration annotation doesn’t know what it’s doing. It says: “Configuration can be found automatically (e.g. during tests).”
// a test class @SpringBootTest public class LearnMybatisTest { @Test public void test(a) {}}/ / @ SpringBootTest source code @BootstrapWith(SpringBootTestContextBootstrapper.class) @ExtendWith({SpringExtension.class}) public @interface SpringBootTest { / / this method in SpringBootTestContextBootstrapper did get @ SpringBootConfiguration, // Configure some information, but the specific configuration is not clear... org.springframework.boot.test.context.SpringBootTestContextBootstrapper#getOrFindConfigurationClasses Copy the code
-
@Configuration. Many people only know what this annotation is for in the Configuration class, but don’t know what it does. Some people think this annotation is the same as @Component, but there are a few differences.
// Take a look at @configuration @Component public @interface Configuration { // The proxy bean method defaults to true boolean proxyBeanMethods(a) default true; } // This is the bean method @Bean public A a(a){ return new A(); } Copy the code
Try writing a configuration class where the main logic is that one bean method calls another bean method.
//@Configuration @Compoent public class A{ @Bean public A a(a){ return new A(); } @Bean public A a1(a){ A a= a(); return a; } @Bean public A a2(a){ A a= a(); returna; }}Copy the code
If you use @compoent, calling the same bean method multiple times will return different objects. Using @Configuration returns the same object. The proxyBeanMethods parameter is used to determine whether to add a proxy to the method. The proxy is used to retrieve the bean created by the method from the Spring container instead of calling the bean method directly.
// Where to create the agent org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanFactory // Spring will determine if it is @configuration and mark it full // If there is a Component, ComponentScan, Import, ImportResource, Bean annotation, mark it as Lite // Spring will only create method proxies for configuration classes marked full Copy the code
// Proxy logic class ConfigurationClassEnhancer { private static final Callback[] CALLBACKS = new Callback[] { // @configuration the main logic of the agent. new BeanMethodInterceptor(), // Is associated with BeanFactoryAware. Its internal logic is not complex, but it does not know where to use it new BeanFactoryAwareMethodInterceptor(), // No operation, equivalent to no proxy, direct call. NoOp.INSTANCE }; private Enhancer newEnhancer(Class<? > configSuperClass,@Nullable ClassLoader classLoader) { Enhancer enhancer = new Enhancer(); // Cglib creates the proxy by calling the Accept method of CallbackFilter once for each method and passing in a method that returns an integer // This integer represents the index of the CALLBACKS, and the corresponding callback is the enhanced logic used by this method. // So in this CallbackFilter, if the method does not use @bean, then return 2 and the corresponding enhancement is noop.instance // If @bean is used, return 0, indicating that the BeanMethodInterceptor is an enhanced logic. enhancer.setCallbackFilter(ConditionalCallbackFilter); returnenhancer; }}Copy the code
In the BeanMethodInterceptor, there is a concept of a method being called. If method A calls method B and the proxy intercepts method B, then a is the method being called. This method is retrieved from an internal ThreadLocal. You can determine if one method in the proxy class is calling another. If so, get the beanName of the bean returned by this method, based on b’s method name or some annotations, and get it from the Spring container and return it to METHOD A.
conclusion
@springBootConfiguration to initialize the environment for integration tests
@configuration to add a proxy to the method
@enableAutoConfiguration, import the auto-configuration classes from Spring. factories
AutoConfigurationPackage, which wraps the annotated package path as a BeanDefinition for other components to use
@ComponentScan, which configures scanning paths and filtering classes