关于springboot:SpringBootApplication源码详细解析

37次阅读

共计 9051 个字符,预计需要花费 23 分钟才能阅读完成。

本文章的 spring-boot 版本为 2.5.0-SNAPSHOT

在学习 springboot 源码前我在网上看了很多 @SpringBootApplication 解析的文章,在学习了 spring 源码和局部 springboot 源码后发现那些文章大多都没讲残缺,甚至有些是错的。所以想写一个略微残缺点的解析文章。

前置常识

  1. 注解的继承性(或者叫派生性,传递性),怎么叫无所谓。

    @C
    @interface B{}// 定义一个 B 注解,B 注解中增加了 C 注解
    
    @B
    class A{}// A 类应用了 B 注解,也相当于同时应用了 C 注解

    一个类增加了一个注解,那么同时也相当于增加了该注解内的注解,这个层级无论嵌套多少层都无效。

  1. BeanDefinition接口

    spring 通过 asm 技术加载类文件,而后并不是间接创建对象,而是解析这个类的信息,如:所有须要 spring 解决的注解,Aware 接口,FactoryBean 接口等信息。这个接口的子类性能十分弱小,能够通过这个 BeanDefinition 设置一个类的父类,接口,要应用的结构器,须要在初始化过程中调用的办法和传入的参数等。能够说通过 BeanDefinition 能够齐全的创立一个新的类,这也是第三方组件的重要扩大形式。spring 会依据 BeanDefinition 中蕴含的信息构建一个 bean。

  1. @Import的作用,这个须要对 spring 的解析流程有些理解能力晓得具体作用,这里只讲个大略。

    @Import(B.class) // 应用形式,依据 B 类的类型抉择不同的解决形式
    class A{}

    spring 在解析类是如果发现有 @Import 注解,那么解析注解中的类,个别有三种类型

    1. 一个一般类,应用通用逻辑去加载,跟 @Compoent 一样
    2. ImportSelector接口的子类

      public interface ImportSelector {
          // 传入被注解类的注解信息元数据,能够从 importingClassMetadata 中获取被注解类
          // 的所有注解信息,包含传递的注解
          // 返回蕴含了一个类的全类名的 String[],spring 会依据全类名去加载这些类成为 bean
          String[] selectImports(AnnotationMetadata importingClassMetadata);
      }
    3. DeferredImportSelector

      //DeferredImportSelector 继承了 ImportSelector 然而解决逻辑略有不同
      public interface DeferredImportSelector extends ImportSelector{
          // 先应用 getImportGroup 返回一个 Group 的实现类,这个 Group 相当于一个处理器,能够多个类应用同一个 Group,调用 Group 的两个办法返回一个 Entry
          //spring 会依据 Entry 中蕴含的信息进行初始化
          // 如果 getImportGroup 返回 null,那么还是调用 ImportSelector 的 selectImports 办法
          @Nullable
          default Class<? extends Group> getImportGroup() {return null;}
      
          // 一个外部类
          interface Group {void process(AnnotationMetadata metadata, DeferredImportSelector selector);
              Iterable<Entry> selectImports();
              
              // 外部类的外部类
              class Entry {
                  private final AnnotationMetadata metadata;
                  private final String importClassName;
              }
          }
      
      }

      这个 DeferredImportSelector 解决的具体逻辑有些简单,有趣味的能够看 spring 源码

      class ConfigurationClassParser {public void parse(Set<BeanDefinitionHolder> configCandidates) {
              //...
              //DeferredImportSelector 的解决
              this.deferredImportSelectorHandler.process();}
      }
    4. ImportBeanDefinitionRegistrar的子类

      // 传入被注解类的注解信息元数据,BeanDefinition 的注册器
      default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                           BeanDefinitionRegistry registry) {
          // 这里能够通过 registry 获取指定的 BeanDefinition,对其进行革新
          // 也能够通过 registry 注册一些新的 BeanDefinition
      }

因为 @Import 有这四种解决形式,所以个别的 @Enablexxx 的性能都是由 @Import 实现

@SpringBootApplication

上面就能够开始剖析这个注解了。

// 省略 jdk 的元注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
  1. @ComponentScan

    // 这里自定义了两个排除过滤器,spring 会通过反射创立这两个过滤器,对每一个扫描出的类进行判断
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
            @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public class TypeExcludeFilter implements TypeFilter{
        @Override
        public boolean match(MetadataReader metadataReader, 
                             MetadataReaderFactory metadataReaderFactory) throws IOException {
            // 获取所有的 TypeExcludeFilter(就是以后类)的子类,// 调用 match 进行判断,只有一个为 true,那么就返回 true
            for (TypeExcludeFilter delegate : getDelegates()) {if (delegate.match(metadataReader, metadataReaderFactory)) {return true;}
            }
            return false;
        }
    }
    public class AutoConfigurationExcludeFilter implements TypeFilter{
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory
                             metadataReaderFactory) throws IOException {
            // 这个类如果有 @Configuration 注解并且是主动配置类,返回 true
            // 这个主动配置的判断逻辑是:spring-boot 加载 spring.factories 中的键值对,有没有这个类的全类名
            // 具体的看下方 @EnableAutoConfiguration 的解析
            return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
        }
    }
    //@ComponentScan 包的解析逻辑
    
    Set<String> basePackages = new LinkedHashSet<>();
    
    // 获取解析 basePackages 的值
    String[] basePackagesArray = componentScan.getStringArray("basePackages");
    Collections.addAll(basePackages, basePackagesArray);
    
    // 从 basePackageClasses 解析出包名
    for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {basePackages.add(ClassUtils.getPackageName(clazz));
    }
    
    // 如果 componentScan 注解中没有配置包,那么就注入被注解的类所在的包, 这是 spring 中定义的逻辑,springboot 中也是调用的这个办法。// 所以如果在 spring 中把配置类放在其余包的下层,那么就不须要配置包门路
    if (basePackages.isEmpty()) {basePackages.add(ClassUtils.getPackageName(declaringClass));
    }
  2. @EnableAutoConfiguration

    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class) // 导入一个类
    public @interface EnableAutoConfiguration {
    public class AutoConfigurationImportSelector implements DeferredImportSelector{
        // 应用 AutoConfigurationGroup 中的办法去获取 Entry
        @Override
        public Class<? extends Group> getImportGroup() {return AutoConfigurationGroup.class;}
    }
    private static class AutoConfigurationGroup implements DeferredImportSelector.Group{
        //emmm,这里有些简单,想半天不晓得怎么写,所以就写个简略逻辑。// 这里会去 classpath*:spring.factories 中获取键值对保留到一个汇合外面。// 而后从汇合中获取 EnableAutoConfiguration 全类名为 key 的 value,包装成一个 Entry
        // 通过合并,排除,排序等步骤返回 Iterable<Entry>
        //spring 依据 Entry 中的信息去加载对应的类
    }
  3. @AutoConfigurationPackage,这个注解很多文章没有写,也有很多文章把 spring-boot 扫描以后包和子包的性能归纳于这个注解,这个错得就有些离谱,因为扫描包是 @ComponentScan 的性能,而且是 spring 中定义的逻辑,spring-boot 只是应用而已。

    @Import(AutoConfigurationPackages.Registrar.class)// 导入了一个 ImportBeanDefinitionRegistrar 的子类
    public @interface AutoConfigurationPackage {String[] basePackages() default {};
    
        Class<?>[] basePackageClasses() default {};}
    public abstract class AutoConfigurationPackages {
    
    
        // 这是个动态外部类
        static class Registrar implements ImportBeanDefinitionRegistrar {
            //spring 会调用这个办法
            @Override
            public void registerBeanDefinitions(AnnotationMetadata metadata,
                                                BeanDefinitionRegistry registry) {
                // 第二个参数是从 AutoConfigurationPackage 注解中获取的包门路
                // 如果无奈解析出包门路,那么应用被注解类所在的包门路
                register(registry, 
                         new PackageImports(metadata).getPackageNames().toArray(new String[0]));
            }
        }
    
    
        private static final String BEAN = AutoConfigurationPackages.class.getName();
    
        // 下面的 registerBeanDefinitions 办法会调用到这个办法
        public static void register(BeanDefinitionRegistry registry, String... packageNames) {
            // 这里的逻辑很简略,就是判断有没有 AutoConfigurationPackages 全类名为名称的 BeanDefinition
            // 如果有,那么获取,并把包门路增加到 beanDefinition
            if (registry.containsBeanDefinition(BEAN)) {BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
                beanDefinition.addBasePackages(packageNames);
            }
            else {
                // 如果没有该 BeanDefinition,那么创立一个新的 BasePackagesBeanDefinition 注册到 spring 中
                registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
            }
        }
    
    
    
        // 获取包门路
        public static List<String> get(BeanFactory beanFactory) {
            // 这里就是从 BasePackages 中获取保留的包门路
            return beanFactory.getBean(BEAN, BasePackages.class).get();}
        // 这也是个动态外部类
        static final class BasePackages {List<String> get() {
                // 返回传入的包门路
                return this.packages;
            }
        }
    }

    上方的逻辑比较简单,如果没有额定配置,那么就是把被注解类所在的包门路包装成一个 BasePackagesBeanDefinition 并注册到 spring 中。
    那么这个有什么用呢?他的作用就是给其余组件提供 spring-boot 的扫描门路。如:mybatis-spring-boot-autoconfigure主动配置类导入的 AutoConfiguredMapperScannerRegistrar 源码中有这样一行代码。

    // 这里就是调用上方那个 AutoConfigurationPackages 的 get 办法获取包门路
    //mybatis 会扫描这个包门路下的类,通过过滤后失去注解了 @Mapper 的接口
    List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
  4. @SpringBootConfiguration

    @Configuration // 这个注解就相当于一个 @Configuration
    public @interface SpringBootConfiguration {

    @SpringBootConfiguration这个注解不晓得具体是干什么的,这个注解的正文写的是:” 能够主动找到配置(例如在测试中)”

    // 一个测试类
    @SpringBootTest
    public class LearnMybatisTest {
        @Test
        public void test() {}
    }
    
    //@SpringBootTest 源码
    @BootstrapWith(SpringBootTestContextBootstrapper.class)
    @ExtendWith({SpringExtension.class})
    public @interface SpringBootTest {
    
    // 在 SpringBootTestContextBootstrapper 的这个办法中的确获取了 @SpringBootConfiguration,
    // 并配置了一些信息, 但具体配置了什么,看不懂。。。org.springframework.boot.test.context.SpringBootTestContextBootstrapper#getOrFindConfigurationClasses
  5. @Configuration,很多人只晓得要在配置类要增加这个注解,然而不晓得这个注解到底是干什么的,也有人认为这个注解跟 @Component 一样,但实际上是有点不一样的。

    // 先看一下 @Configuration 的源码
    @Component
    public @interface Configuration {
        // 这个参数翻译一下就是: 代理 bean 办法,默认为 true
        boolean proxyBeanMethods() default true;}
    
    // 这样的就是 bean 办法
    @Bean
    public A a(){return new A();
    }

    能够尝试写这样一个配置类,次要逻辑是一个 bean 办法中调用了另一个 bean 办法。

    //@Configuration
    @Compoent
    public class A{
        @Bean
        public A a(){return new A();
        }
    
        @Bean
        public A a1(){A a= a();
            return a;
        }
    
        @Bean
        public A a2(){A a= a();
            return a;
        }
    }

    如果应用 @Compoent,屡次调用同一个 bean 办法返回的对象是不同的。而应用@Configuration 就会返回同一个对象。这样来看 proxyBeanMethods 这个参数的很好了解了,就是是否要给办法增加代理,而这个代理的作用就是当调用 bean 办法的时候不是间接调用办法,而是从 spring 容器中获取这个办法曾经创立好的 bean 并返回。

    // 创立代理的地位
    org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanFactory
    //spring 在解析时会判断是不是 @Configuration, 是的话标记为 full
    // 如果有 Component,ComponentScan,Import,ImportResource,Bean 注解标记为 lite
    //spring 只会对标记为 full 的配置类创立办法代理
    // 代理逻辑
    class ConfigurationClassEnhancer {private static final Callback[] CALLBACKS = new Callback[] {
            //@Configuration 代理的次要逻辑。new BeanMethodInterceptor(),
            // 与 BeanFactoryAware 相干,外部逻辑不简单,但不晓得在什么中央应用,这里也没用到
            new BeanFactoryAwareMethodInterceptor(),
            // 无操作,相当于不代理,间接调用。NoOp.INSTANCE 
        };
    
        private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {Enhancer enhancer = new Enhancer();
            //cglib 在创立代理时会对每个办法都调用一次 CallbackFilter 的 accept 办法,并传入办法,返回一个整数
            // 这个整数代表 CALLBACKS 的索引,这个索引对应的 callback 就是这个办法所应用的加强逻辑。// 所以在这个 CallbackFilter 中判断如果办法没有应用 @Bean,那么返回 2,对应的加强就是 NoOp.INSTANCE 
            // 如果应用了 @Bean,那么返回 0,示意 BeanMethodInterceptor 为加强逻辑。enhancer.setCallbackFilter(ConditionalCallbackFilter);
            return enhancer;
        }
    }

    BeanMethodInterceptor 中有一个 正在调用的办法的 概念,如 a 办法调用了 b 办法,代理拦挡了 b 办法(依据下面的 CallbackFilter 的逻辑,没有 @Bean 注解是不会被拦挡的),那么此时 a 是正在调用的办法,这个办法从外部的 ThreadLocal 中获取,b 会传入代理办法,只有判断这个两个办法相不相等,就能分辨出是不是从办法外部调用的办法。如果是从外部调用的办法,那么获取 b 的办法名或一些注解,获取到这个办法反会的 bean 的 beanName,再从 spring 容器中获取返回给 a 办法。

总结

@SpringBootConfiguration,给集成测试初始化环境

@Configuration,给办法增加代理

@EnableAutoConfiguration,从 spring.factories 导入主动配置类

@AutoConfigurationPackage,把被注解的包门路包装成BeanDefinition,提供给其余组件应用

@ComponentScan,配置扫描门路和过滤类

正文完
 0