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

  1. 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.

  2. 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.

  3. 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

    1. 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
    2. 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
    3. 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
    4. 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
  1. @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
  2. @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
  3. 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
  1. @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
  2. @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