spring boot

1. spring boot 启动代码

首先, 看一段 spring boot 的启动代码如下. 发现启动分为2大部分:

  • SpringApplication.run() 容器启动
  • @SpringBootApplication 注解
// (1) 标明是 spring boot 应用, 开启自动配置
@SpringBootApplication   
public class Example {
    public static void main(String[] args) {
        // (2) SpringApplication.run() xxx启动 spring boot 应用
        ConfigurableApplicationContext context = SpringApplication.run(Example.class);
    }
}

2. 关于 SpringApplication.run() 容器启动

看到使用 SpringApplication.run(Class<?>[] primarySources) 这句话启动应用. 内部其实返回了

return new SpringApplication(primarySources).run(args);

这个方法的关键在于两点:

  1. new SpringApplication(primarySources) 构造器
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        // resourceLoader 是 spring framework 的内容, 参看 spring framework
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));   // java config 数组
        //(1) 判断是否是 web 应用. 返回 (REACTIVE, NONE, SERVLET) 类型.  内部就是用 try{Class.forName()}, 看几个 class 是否存在
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // (2)
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // (3)
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // (4)
        this.mainApplicationClass = deduceMainApplicationClass();
    }
    
    private ResourceLoader resourceLoader;  // spring 资源加载器
    private Set<Class<?>> primarySources;   // java config 数组
    private WebApplicationType webApplicationType;   // web 类型
    
    
    private List<ApplicationContextInitializer<?>> initializers;
    private List<ApplicationListener<?>> listeners;
    
    • (2),(3) 两步用到了 getSpringFactoriesInstances 方法来初始化字节的 initializers 属性和 listener 属性. 该方法如下:
      private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
          ClassLoader classLoader = getClassLoader();
          // 解析 'META_INF/spring.factories' 中的配置, 该文件每行是一个 key-value 对; key: 工厂类名, value: 工厂实现类的类名
          // 将 factoryName 和 fatoryImplementationName 解析出来
          Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
          // 内部用反射 constructor 构造对象
          List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
          AnnotationAwareOrderComparator.sort(instances);
          return instances;
      }
      
      因此, (2),(3)两步就是反射构造函数去实例化 META_INF/spring.factories 文件中配置的2组类 ApplicationContextInitializerApplicationListener. 文件内容为:
      # Initializers
      org.springframework.context.ApplicationContextInitializer=\
      org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
      org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
      
      # Application Listeners
      org.springframework.context.ApplicationListener=\
      org.springframework.boot.autoconfigure.BackgroundPreinitializer
      
      [说明]: META_INF/spring.factories 文件是 spring-boot 自动配置的关键
      以 mybatis 为例, 和 spring-boot 集成时, 会引入一个 mybatis-spring-boot-autoconfigure.jar 包. 最重要的是在 META_INF/spring.factories中定义了一些列工厂类, 这些类本身作为 bean, 又可以加载其它 bean. 像 mybatis 的自动配置入口就注册了 sqlSessionFactorysqlSessionTemplate:
      @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
      class MybatisAutoConfiguration implements InitializingBean{
       @Bean
       @ConditionalOnMissingBean
       public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
         return new SqlSessionTemplate(sqlSessionFactory);
       }
      }
      
    • 第(4)步: deduceMainApplicationClass() 是尝试获取执行 main 方法的类. 没什么用, 主要用来打日志
      // 通过构造一个 RuntimeException, 查找 main 方法的调用栈来决定启动类是什么
      private Class<?> deduceMainApplicationClass() {
              try {
                  StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
                  for (StackTraceElement stackTraceElement : stackTrace) {
                      // 调用栈发现 main 方法, 获取主类
                      if ("main".equals(stackTraceElement.getMethodName())) {
                          return Class.forName(stackTraceElement.getClassName());
                      }
                  }
              }
              catch (ClassNotFoundException ex) {
                  // Swallow and continue
              }
              return null;
          }
      
  2. SpringApplication 的 run 方法
    该方法逻辑较多
    public ConfigurableApplicationContext run(String... args) {
        ////////////////////////////////////
        //StopWatch 主要用来统计 run() 方法的启动时长. 后面有掉用 stopWatch.stop() 方法
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ////////////////////////////////////
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        // 设置了 java.awt.headless 环境变量, 和 awt 有关, 可以无视
        configureHeadlessProperty();
        ////////////////////////////////////
        // 反射构造器, 实例化 spring.factories 中的 SpringApplicationRunListener 实现类并启动
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        ////////////////////////////////////
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ////////////////////////////////////
            // (1) prepareEnvironment 加载配置变量. 该方法, 根据内部会根据 spring-boot 配置的 profile 加载配置.
            //     生成 PropertySource 和 Environment. 这两个类时干什么的, 参见 spring-framework
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            ////////////////////////////////////
            // (2) 打印 banner
            Banner printedBanner = printBanner(environment);
            // (3) 创建 ApplicaitonContext. 详见下方
            context = createApplicationContext();
            // (4) 对 spring.factories 中记录的配置的 SpringBootExceptionReporter 实现类
            //     org.springframework.boot.diagnostics.FailureAnalyzers 做初始化
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            // (5) 见下面
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // (6) 内部就是替我们手动执行了 applicationContext.refresh()
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            // 同下(8)
            listeners.started(context);
            // (7) 执行所有类型为 `ApplicationRunner` 和 `CommandLineRunner` 的 bean 的 run() 方法
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }
    
        try {
            // (8) 有关 SpringApplicationRunListeners 
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }
    
  • (1)处, prepareEnvironment 加载配置变量
    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                ApplicationArguments applicationArguments) {
            // Create and configure the environment
            ConfigurableEnvironment environment = getOrCreateEnvironment();
            configureEnvironment(environment, applicationArguments.getSourceArgs());
            ConfigurationPropertySources.attach(environment);
            listeners.environmentPrepared(environment);
            bindToSpringApplication(environment);
            if (!this.isCustomEnvironment) {
                environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                        deduceEnvironmentClass());
            }
            ConfigurationPropertySources.attach(environment);
            return environment;
        }
    
  • (2)处, 打印的 banner 如下
      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::
    
  • (3) createApplicationContext 创建 ConfigurableApplicationContext
    该方法根据判断出的 web 类型(普通web, webserver等)通过反射构造器, 创建 ApplicationContext 对象. 相当于帮你手写了 new AnnotationConfigApplicationContext() 初始化容器
    protected ConfigurableApplicationContext createApplicationContext() {
        // 根据 webApplicationType 类型,获得 ApplicationContext 类型
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            } catch (ClassNotFoundException ex) {
                throw new IllegalStateException("Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex);
            }
        }
        // 创建 ApplicationContext 对象
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
    
  • (5) prepareContext 准备 ApplicationContext 对象,主要是初始化它的一些属性。
    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
                SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        // (1) 给 applicationContext 设置配的变量
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        // (2) 缓存 ApplicationContextInitializer
        applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //(3) 在 applicationContexdt 的 BeanFactory 中缓存启动参数. 名称为 "springApplicationArguments"
        //    内部是缓存在 SingletonBeanRegistry 的 LinkedHashSet 数据结构中
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)
                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
    
        // (4) 加载一系列的 BeanDefinition. 具体如下方法
        load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }
    // prepareContext() 的第(4)步骤: 分析怎么加载的 BeanDefinition
    protected void load(ApplicationContext context, Object[] sources) {
        if (logger.isDebugEnabled()) {
            logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
        }
        // (1) 创建 BeanDefinitionRegistry 对象. 具体什么是 BeanDefinitionRegistry 和 BeanDefinition, 参见 spring framework
        BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
        if (this.beanNameGenerator != null) {
            loader.setBeanNameGenerator(this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            loader.setResourceLoader(this.resourceLoader);
        }
        if (this.environment != null) {
            loader.setEnvironment(this.environment);
        }
        // 执行 BeanDefinition 加载. 具体加载步骤参见 spring-framework
        loader.load();
    }
    
  • (8) 是 SpringApplicationRunListeners 相关处理, 包括 started()run() 方法. 参看事件设计模式

3. @SpringBootApplication 注解开启自动配置

自动配置和自动装配不同. 自动装配指 spring bean 的生成, 自动配置是 spring-boot 的主要动能. 首先先看看该注解的定义

@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication 

下面逐步分析 @SpringBootApplication 上的每个注解

  1. @Inherited
    该注解是 java 自带注解, 其含义是: 当用 @Inherited 实现了自定义注解, 如果在 class 上使用自定义注解, 则继承该 class 的子类自动被标记了自定义注解

  2. @SpringBootConfiguration
    spring boot 的注解. 看到定义时用到了 @Configuration, 因此两者含义一致, 表示该类作为 beans.xml 的替代者来声明 bean

    @Configuration
    public @interface SpringBootConfiguration {}
    
  3. @ComponentScan
    spring 注解, 自动扫描包下@Componment@Configuration@Service

  1. @EnableAutoConfiguration
    spring boot 实现自动配置的核心注解
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    
    • @AutoConfigurationPackage 注解

      @Import(AutoConfigurationPackages.Registrar.class)
      public @interface AutoConfigurationPackage
      
    • @Import

      1. 该注解的功能

        • (1) @Import(OtherConfiguration.class): 导入其它 @Configuration 声明的类, 相当于 beans.xml 中 <import> 标签的代替
        • (2) @Import(OtherImportSelector.class): 导入 ImportSelector 中代码定义的类名
          package com.test;
          class ServiceImportSelector implements ImportSelector {
              @Override
              public String[] selectImports(AnnotationMetadata importingClassMetadata) {
                  //可以是@Configuration注解修饰的类,也可以是具体的Bean类的全限定名称
                  return new String[]{"com.test.ConfigB"};
              }
          }
          
          @Import(ServiceImportSelector.class)
          @Configuration
          class ConfigA {
              @Bean
              @ConditionalOnMissingBean
              public ServiceInterface getServiceA() {
                  return new ServiceA();
              }
          }
          
        • (3) @Import(ImportBeanDefinitionRegistrar.class): 用于在导入 bean 时, 重新定义或更改 bean. 例如动态注入属性,改变Bean的类型和Scope等等
          // 定义ServiceC
          package com.test;
          class ServiceC implements ServiceInterface {
          
              private final String name;
          
              ServiceC(String name) {
                  this.name = name;
              }
          
              @Override
              public void test() {
                  System.out.println(name);
              }
          }
          
          // 定义ServiceImportBeanDefinitionRegistrar动态注册ServiceC,修改EnableService
          package com.test;
          
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          @Target(ElementType.TYPE)
          @Import(ServiceImportBeanDefinitionRegistrar.class)
          @interface EnableService {
              String name();
          }
          
          class ServiceImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
              @Override
              public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
                  Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
                  String name = (String) map.get("name");
                  BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(ServiceC.class)
                          //增加构造参数
                          .addConstructorArgValue(name);
                  //注册Bean
                  registry.registerBeanDefinition("serviceC", beanDefinitionBuilder.getBeanDefinition());
              }
          }
          
          // 使用 @Import 注解 
          package com.test;
          @EnableService(name = "TestServiceC")
          @Configuration
          class ConfigA {}     // 会自动生成
          
          // main 函数 
          public static void main(String[] args) {
              ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigA.class);
              ServiceInterface bean = ctx.getBean(ServiceInterface.class);
              bean.test();
          }
          

        [注]: @Import 注解的解析, 参考 https://zhuanlan.zhihu.com/p/147025312

      2. @Import(AutoConfigurationImportSelector.class) 是重头戏的开始, 如后文

4. @Import(AutoConfigurationImportSelector.class) 自动配置重头戏

AutoConfigurationImportSelector 类定义如下. 可以发现它除了实现几个Aware类接口外,最关键的就是实现了 DeferredImportSelector (继承自ImportSelector)接口 https://blog.csdn.net/dm_vincent/article/details/77619752

class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered{

}
  1. 该类中的 getCandidateConfigurations 获取配置类数组

    1. 函数展示

      // AutoConfigurationImportSelector.java
      
      protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
          // <1> 加载指定类型 EnableAutoConfiguration 对应的,在 `META-INF/spring.factories` 里的类名的数组
          // getSpringFactoriesLoaderFactoryClass(): return EnableAutoConfiguration.class;
          // SpringFactoriesLoader.loadFactoryNames():  在 `META-INF/spring.factories` 里的类名的数组
          List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
          return configurations;
      }
      
    2. getCandidateConfigurations 方法如何被调用的

      • (1)处: 在上述分析 .run() 方法时, 第(6)步, 替我们手动执行了 applicationContext.refresh()
      • (3)处:
      public Iterable<Group.Entry> getImports() {
          for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
              // <1> 处理被 @Import 注解的注解
              // this.group 由 getImportGroup() 方法返回, 具体在"该类的 `getImportGroup()`"章节分析
              this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                      deferredImport.getImportSelector());
          }
          // <2> 选择需要导入的
          return this.group.selectImports();
      }
      
  2. 该类的 getImportGroup()

    1. 函数展示
    // AutoConfigurationImportSelector.java
    
    @Override    // 实现自 DeferredImportSelector 接口
    public Class<? extends Group> getImportGroup() {
        return AutoConfigurationGroup.class;
    }
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,348评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,122评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,936评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,427评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,467评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,785评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,931评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,696评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,141评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,483评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,625评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,291评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,892评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,492评论 2 348

推荐阅读更多精彩内容