通过代码实例了解SpringBoot启动原理

这篇文章主要介绍了通过代码实例了解SpringBoot启动原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

SpringBoot和Spring相比,有着不少优势,比如自动配置,jar直接运行等等。那么SpringBoot到底是怎么启动的呢?

下面是SpringBoot启动的入口:

@SpringBootApplication
public class HelloApplication {

  public static void main(String[] args) {
    SpringApplication.run(HelloApplication.class, args);
  }
}

一、先看一下@SpringBoot注解:

@Target({ElementType.TYPE})  //定义其使用时机
@Retention(RetentionPolicy.RUNTIME) //编译程序将Annotation储存于class档中,可由VM使用反射机制的代码所读取和使用。
@Documented //这个注解应该被 javadoc工具记录
@Inherited //被注解的类会自动继承. 更具体地说,如果定义注解时使用了 @Inherited 标记,然后用定义的注解来标注另一个父类, 父类又有一个子类(subclass),则父类的所有属性将被继承到它的子类中.
@SpringBootConfiguration //@SpringBootConfiguration就相当于@Configuration。JavaConfig配置形式
@EnableAutoConfiguration
@ComponentScan(
  excludeFilters = {@Filter(
  type = FilterType.CUSTOM,
  classes = {TypeExcludeFilter.class}
), @Filter(
  type = FilterType.CUSTOM,
  classes = {AutoConfigurationExcludeFilter.class}
)} //自动扫描并加载符合条件的组件。以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。
注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。
)
public @interface SpringBootApplication {
  @AliasFor(
    annotation = EnableAutoConfiguration.class
  )
  Class<?>[] exclude() default {};

  @AliasFor(
    annotation = EnableAutoConfiguration.class
  )
  String[] excludeName() default {};

  @AliasFor(
    annotation = ComponentScan.class,
    attribute = "basePackages"
  )
  String[] scanBasePackages() default {};

  @AliasFor(
    annotation = ComponentScan.class,
    attribute = "basePackageClasses"
  )
  Class<?>[] scanBasePackageClasses() default {};
}

所以,实际上SpringBootApplication注解相当于三个注解的组合,@SpringBootConfiguration,@ComponentScan和@EnableAutoConfiguration。

@SpringBootConfiguration和@ComponentScan,很容易知道它的意思,一个是JavaConfig配置,一个是扫描包。关键在于@EnableAutoConfiguration注解。先来看一下这个注解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
  String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

  Class<?>[] exclude() default {};

  String[] excludeName() default {};
}

Springboot应用启动过程中使用ConfigurationClassParser分析配置类时,如果发现注解中存在@Import(ImportSelector)的情况,就会创建一个相应的ImportSelector对象, 并调用其方法 public String[] selectImports(AnnotationMetadata annotationMetadata), 这里 EnableAutoConfigurationImportSelector的导入@Import(EnableAutoConfigurationImportSelector.class) 就属于这种情况,所以ConfigurationClassParser会实例化一个 EnableAutoConfigurationImportSelector 并调用它的 selectImports() 方法。

AutoConfigurationImportSelector implements DeferredImportSelector extends ImportSelector。

下面是AutoConfigurationImportSelector的执行过程:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
  private static final String[] NO_IMPORTS = new String[0];
  private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
  private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
  private ConfigurableListableBeanFactory beanFactory;
  private Environment environment;
  private ClassLoader beanClassLoader;
  private ResourceLoader resourceLoader;
  public AutoConfigurationImportSelector() {
  }

  public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
    } else {
      // 从配置文件中加载 AutoConfigurationMetadata
      AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
      AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
      // 获取所有候选配置类EnableAutoConfiguration
      // 使用了内部工具使用SpringFactoriesLoader,查找classpath上所有jar包中的
      // META-INF\spring.factories,找出其中key为
      // org.springframework.boot.autoconfigure.EnableAutoConfiguration
      // 的属性定义的工厂类名称。
      // 虽然参数有annotationMetadata,attributes,但在 AutoConfigurationImportSelector 的
      // 实现 getCandidateConfigurations()中,这两个参数并未使用
      List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
      //去重
      configurations = this.removeDuplicates(configurations);
      Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
      // 应用 exclusion 属性
      this.checkExcludedClasses(configurations, exclusions);
      configurations.removeAll(exclusions);
      // 应用过滤器AutoConfigurationImportFilter,
      // 对于 spring boot autoconfigure,定义了一个需要被应用的过滤器 :
      // org.springframework.boot.autoconfigure.condition.OnClassCondition,
      // 此过滤器检查候选配置类上的注解@ConditionalOnClass,如果要求的类在classpath
      // 中不存在,则这个候选配置类会被排除掉
      configurations = this.filter(configurations, autoConfigurationMetadata);
     // 现在已经找到所有需要被应用的候选配置类
     // 广播事件 AutoConfigurationImportEvent
      this.fireAutoConfigurationImportEvents(configurations, exclusions);
      return StringUtils.toStringArray(configurations);
    }
  }

  protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
    String name = this.getAnnotationClass().getName();
    AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
    Assert.notNull(attributes, () -> {
      return "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?";
    });
    return attributes;
  }

  protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
  }

public abstract class SpringFactoriesLoader {
  public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
  private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();

  public SpringFactoriesLoader() {
  }
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
  }
}

  private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    String[] candidates = StringUtils.toStringArray(configurations);
    // 记录候选配置类是否需要被排除,skip为true表示需要被排除,全部初始化为false,不需要被排除
    boolean[] skip = new boolean[candidates.length];
    // 记录候选配置类中是否有任何一个候选配置类被忽略,初始化为false
    boolean skipped = false;
    Iterator var8 = this.getAutoConfigurationImportFilters().iterator();
    // 获取AutoConfigurationImportFilter并逐个应用过滤
    while(var8.hasNext()) {
      AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var8.next();
      // 对过滤器注入其需要Aware的信息
      this.invokeAwareMethods(filter);
      // 使用此过滤器检查候选配置类跟autoConfigurationMetadata的匹配情况
      boolean[] match = filter.match(candidates, autoConfigurationMetadata);

      for(int i = 0; i < match.length; ++i) {
        if (!match[i]) {
         // 如果有某个候选配置类不符合当前过滤器,将其标记为需要被排除,
    // 并且将 skipped设置为true,表示发现了某个候选配置类需要被排除
          skip[i] = true;
          skipped = true;
        }
      }
    }

    if (!skipped) {
    // 如果所有的候选配置类都不需要被排除,则直接返回外部参数提供的候选配置类集合
      return configurations;
    } else {
    // 逻辑走到这里因为skipped为true,表明上面的的过滤器应用逻辑中发现了某些候选配置类
    // 需要被排除,这里排除那些需要被排除的候选配置类,将那些不需要被排除的候选配置类组成
    // 一个新的集合返回给调用者
      List<String> result = new ArrayList(candidates.length);

      int numberFiltered;
      for(numberFiltered = 0; numberFiltered < candidates.length; ++numberFiltered) {
        if (!skip[numberFiltered]) {
          result.add(candidates[numberFiltered]);
        }
      }

      if (logger.isTraceEnabled()) {
        numberFiltered = configurations.size() - result.size();
        logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
      }

      return new ArrayList(result);
    }
  }

  /**
   * 使用内部工具 SpringFactoriesLoader,查找classpath上所有jar包中的
   * META-INF\spring.factories,找出其中key为
   * org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
   * 的属性定义的过滤器类并实例化。
   * AutoConfigurationImportFilter过滤器可以被注册到 spring.factories用于对自动配置类
   * 做一些限制,在这些自动配置类的字节码被读取之前做快速排除处理。
   * spring boot autoconfigure 缺省注册了一个 AutoConfigurationImportFilter :
   * org.springframework.boot.autoconfigure.condition.OnClassCondition
  **/
  protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
  }

二、下面看一下SpringBoot启动时run方法执行过程

public ConfigurableApplicationContext run(String... args) {
  StopWatch stopWatch = new StopWatch();
  stopWatch.start();
  ConfigurableApplicationContext context = null;
  Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  this.configureHeadlessProperty();
  //从类路径下META-INF/spring.factories获取
  SpringApplicationRunListeners listeners = getRunListeners(args);
  //回调所有的获取SpringApplicationRunListener.starting()方法
  listeners.starting();
  try {
    //封装命令行参数
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(
          args);
    //准备环境
    ConfigurableEnvironment environment = prepareEnvironment(listeners,
          applicationArguments);
    //创建环境完成后回调
    SpringApplicationRunListener.environmentPrepared();表示环境准备完成
    this.configureIgnoreBeanInfo(environment);
    //打印Banner图
    Banner printedBanner = printBanner(environment);
    //创建ApplicationContext,决定创建web的ioc还是普通的ioc
    context = createApplicationContext();
    //异常分析报告
    exceptionReporters = getSpringFactoriesInstances(
          SpringBootExceptionReporter.class,
          new Class[] { ConfigurableApplicationContext.class }, context);
    //准备上下文环境,将environment保存到ioc中
    //applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法
    //listeners.contextPrepared(context)
    //prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded()
    this.prepareContext(context, environment, listeners, applicationArguments,
          printedBanner);
    //刷新容器,ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat)
    //扫描,创建,加载所有组件的地方,(配置类,组件,自动配置)
    this.refreshContext(context);
    this.afterRefresh(context, applicationArguments);
    stopWatch.stop();
    if (this.logStartupInfo) {
      new StartupInfoLogger(this.mainApplicationClass)
              .logStarted(getApplicationLog(), stopWatch);
    }
    //所有的SpringApplicationRunListener回调started方法
    listeners.started(context);
    //从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调,
    //ApplicationRunner先回调,CommandLineRunner再回调
    this.callRunners(context, applicationArguments);
  }
  catch (Throwable ex) {
    this.handleRunFailure(context, ex, exceptionReporters, listeners);
    throw new IllegalStateException(ex);
  }
  try {
    //所有的SpringApplicationRunListener回调running方法
    listeners.running(context);
  }
  catch (Throwable ex) {
    this.handleRunFailure(context, ex, exceptionReporters, null);
    throw new IllegalStateException(ex);
  }
  //整个SpringBoot应用启动完成以后返回启动的ioc容器
  return context;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • SPRING BOOT启动命令参数及源码详析

    前言 使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring Boot命令行参数相关的功能及相关源码分析. 命令行参数使用 启动Spring Boot项目时,我们可以通过如下方式传递参数: java -jar xxx.jar --server.port=8081 默认情况下Spring Boot使用8080端口,通过上述参数将其修改为8081端口,而且通

  • Spring Boot详细打印启动时异常堆栈信息详析

    前言 SpringBoot在项目启动时如果遇到异常并不能友好的打印出具体的堆栈错误信息,我们只能查看到简单的错误消息,以致于并不能及时解决发生的问题,针对这个问题SpringBoot提供了故障分析仪的概念(failure-analyzer),内部根据不同类型的异常提供了一些实现,我们如果想自定义该怎么去做? FailureAnalyzer SpringBoot提供了启动异常分析接口FailureAnalyzer,该接口位于org.springframework.boot.diagnosticsp

  • SpringBoot如何指定某些类优先启动

    一.需求 1.项目中对某些IP地址和端口做了限制,只有写在配置文件的内容(ip)才可以访问项目. 2.在进行测试案例运行时保证读取配置文件中ip和port的类(CbeConfig)得提前运行. 二.工作 1.如下的测试时肯定不行 @Test public void getCbeTest(){ CbeConfig cbeConfig = new CbeConfig(); System.out.println("IP是" + cbeConfig.getIp()); System.out.p

  • springboot 热启动的过程图解

    热启动依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> idea 默认不会进行自动编译,还需要设置 idea 自动编译 Settings>Compiler> 勾选 Build project automatically shift+ct

  • 通过idea创建Spring Boot项目并配置启动过程图解

     一.操作步骤 ①使用idea新建一个Spring Boot项目 ②修改pom.xml ③修改application.properties ④修改编写一个Hello Spring Boot的Controller ⑤启动项目访问 二.详细步骤 1.File-->New-->Project 2.选择Spring Initializr 然后Next 3.输入Artiface 然后Next 4.勾选Web .模版我们选择官方推荐的Thymeleaf模版引擎,其他框架.中间件.数据库根据需要选择即可,而

  • spring boot 监听容器启动代码实例

    在使用Spring框架开发时, 有时我们需要在spring容器初始化完成后做一些操作, 那么我们可以通过自定义ApplicationListener 来实现. 自定义监听器 @Component public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEve

  • springboot 场景启动器使用解析

    这篇文章主要介绍了springboot 场景启动器使用解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 为什么springboot不需要我们去配置那么繁琐的东西? 我们直接看pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmln

  • SpringBoot打印启动时异常堆栈信息详解

    SpringBoot在项目启动时如果遇到异常并不能友好的打印出具体的堆栈错误信息,我们只能查看到简单的错误消息,以致于并不能及时解决发生的问题,针对这个问题SpringBoot提供了故障分析仪的概念(failure-analyzer),内部根据不同类型的异常提供了一些实现,我们如果想自定义该怎么去做? FailureAnalyzer SpringBoot提供了启动异常分析接口FailureAnalyzer,该接口位于org.springframework.boot.diagnosticspack

  • SpringBoot启动后启动内嵌浏览器的方法

    1.下载内嵌浏览器Jar包 下载地址:点击下载 2.项目下加入对应jar:然后右键:Add as Library... 3.添加启动项目后事件 效果:内嵌浏览器打开指定页面,全屏 import com.teamdev.jxbrowser.chromium.Browser; import com.teamdev.jxbrowser.chromium.swing.BrowserView; import org.springframework.boot.ApplicationArguments; im

  • 通过代码实例了解SpringBoot启动原理

    这篇文章主要介绍了通过代码实例了解SpringBoot启动原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 SpringBoot和Spring相比,有着不少优势,比如自动配置,jar直接运行等等.那么SpringBoot到底是怎么启动的呢? 下面是SpringBoot启动的入口: @SpringBootApplication public class HelloApplication { public static void main(Str

  • Springboot启动原理和自动配置原理解析

    目录 启动原理 SpringApplication 1.初始化 2.调用run方法 自动配置原理 放本地文件夹都快吃土了,准备清理文件夹,关于Springboot的! 启动原理 @SpringBootApplication public class Start { public static void main(String[] args) { SpringApplication.run(Start.class, args); } } SpringApplication 1.初始化 public

  • Springboot启动原理详细讲解

    主启动类方法: @SpringBootApplication public class MyJavaTestApplication { public static void main(String[] args) { SpringApplication.run(MyJavaTestApplication.class, args); } } 点击进入方法 public static ConfigurableApplicationContext run(Class<?>[] primarySour

  • 分析SpringBoot的启动原理

    背景 1> 大家都知道SpringBoot是通过main函数启动的,这里面跟踪代码到处都没有找到while(true),为什么启动后可以一直跑? 2> SpringBoot默认使用tomcat作为web容器.大家也可以通过在pom文件中exclusion掉tomcat,denpendency jetty 的方法来使用jetty.那SpringBoot是怎么做到在不同web容器之间切换的呢? 3> 传统的web容器比如jetty本质上是直接通过java start.jar 来启动,之后来加

  • python连接读写操作redis的完整代码实例

    python读写操作redis数据库 redis有16个逻辑数据库(编号db0到db15),每个逻辑数据库数据是隔离的,默认db0.选择第n个逻辑数据库,命令select n ,python连接时可指定数据库编号(0~15). 为python安装支持库: pip install redis 连接redis 第一种方式,直连: import redis def redis_opt(): redis_conn = redis.Redis(host='127.0.0.1', port=6379, pa

  • SpringBoot java-jar命令行启动原理解析

    在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启动的,而不需要另外配置一个Web Server.那么spring boot如何启动的呢?今天我们就来一起探究一下它的原理.首先我们来创建一个基本的spring boot工程来帮助我们分析,本次spring boot版本为 2.2.5.RELEASE. // SpringBootDemo.java @SpringBootApplication public class Spr

  • SpringBoot内置tomcat启动原理详解

    前言 不得不说SpringBoot的开发者是在为大众程序猿谋福利,把大家都惯成了懒汉,xml不配置了,连tomcat也懒的配置了,典型的一键启动系统,那么tomcat在springboot是怎么启动的呢? 内置tomcat 开发阶段对我们来说使用内置的tomcat是非常够用了,当然也可以使用jetty. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-bo

  • 详解SpringBoot启动代码和自动装配源码分析

    目录 一.SpringBoot启动代码主线分析 二.SpringBoot自动装配原理分析 1.自动装配的前置知识@Import 2.@SpringApplication注解分析 2.1@SpringBootConfiguration 2.2@EnableAutoConfiguration ​随着互联网的快速发展,各种组件层出不穷,需要框架集成的组件越来越多.每一种组件与Spring容器整合需要实现相关代码.SpringMVC框架配置由于太过于繁琐和依赖XML文件:为了方便快速集成第三方组件和减少

  • Springboot配置文件内容加密代码实例

    这篇文章主要介绍了Springboot配置文件内容加密代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 使用的是jasypt-spring-boot-starter,具体介绍可以参考  https://gitee.com/yangziyi2017/Jasypt-Spring-Boot 引入依赖 <dependency> <groupId>com.github.ulisesbocchio</groupId> <

  • springboot 整合 freemarker代码实例

    这篇文章主要介绍了springboot 整合 freemarker代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE&l

随机推荐