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

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

前置常识

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

    @C@interface B{}//定义一个B注解,B注解中增加了C注解@Bclass 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 //这个注解就相当于一个@Configurationpublic @interface SpringBootConfiguration {

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

    //一个测试类@SpringBootTestpublic 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的源码@Componentpublic @interface Configuration {    //这个参数翻译一下就是: 代理bean办法 ,默认为true    boolean proxyBeanMethods() default true;}//这样的就是bean办法@Beanpublic A a(){    return new A();}

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

    //@Configuration@Compoentpublic 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,配置扫描门路和过滤类