zoukankan      html  css  js  c++  java
  • SpringBoot启动原理

    1.简述

      SpringBoot因为内置了tomcat或jetty服务器,不需要直接部署War文件,所以SpringBoot的程序起点是一个普通的主函数。

      主函数如下:

    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
    }
    View Code

      SpringBoot的启动过程都是通过@SpringBootApplication注解和SpringApplication.run方法来实现的。

      启动的过程可以概括为

    1. 通过SpringFactoriesLoader加载META-INF/spring.factories文件,获取并创建SpringApplicationRunListener对象。
    2. 然后由SpringApplicationRunListener 来发出starting消息。
    3. 创建参数,并配置当前SpringBoot 应用将要使用的Environment。
    4. 完成之后,依然由SpringApplicationRunListener来发出environmentPrepared 消息。
    5. 创建ApplicationContext。
    6. 初始化ApplicationContext,并设置Environment,加载相关配置等。
    7. 由SpringApplicationRunListener来发出contextPrepared消息,告知SpringBoot应用使用的ApplicationContext已准备OK。
    8. 将各种beans装载入ApplicationContext,继续由SpringApplicationRunListener来发出contextLoaded消息,告知SpringBoot应用使用的ApplicationContext已装填OK。
    9. refresh ApplicationContext,完成IoC容器可用的最后一步
    10. 由SpringApplicationRunListener来发出started消息。
    11. 完成最终的程序的启动。
    12. 由SpringApplicationRunListener来发出running消息,告知程序已运行起来了。

    2.源码分析

    (1)@SpringBootApplication注解

      @SpringBootApplication注解的源码如下

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = { //这两个排除过滤器TypeExcludeFilter和AutoConfigurationExcludeFilter不知道用来干什么的
            @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
            @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {
        //等同于EnableAutoConfiguration注解的exclude属性
        @AliasFor(annotation = EnableAutoConfiguration.class)
        Class<?>[] exclude() default {};
        //等同于EnableAutoConfiguration注解的excludeName属性
        @AliasFor(annotation = EnableAutoConfiguration.class)
        String[] excludeName() default {};
        //等同于ComponentScan注解的basePackages属性
        @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
        String[] scanBasePackages() default {};
        //等同于ComponentScan注解的basePackageClasses属性
        @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
        Class<?>[] scanBasePackageClasses() default {};
    }
    View Code

    可以看到@SpringBootApplication注解实际上是SpringBoot提供的一个复合注解,由以下三个注解组成

    • @SpringBootConfiguration:来源于 @Configuration,二者功能都是将当前类标注为配置类,并将当前类里以@Bean 注解标记的方法的实例注入到srping容器中。
    • @EnableAutoConfiguration:启用自动配置其可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前 IoC 容器之中。
    • @ComponentScan:对应于XML配置形式中的 context:component-scan,用于将一些标注了特定注解的bean定义批量采集注册到Spring的IoC容器之中,这些特定的注解大致包括:@Controller @Entity @Component @Service @Repository。

      因此@SpringBootApplication注解主要作为一个配置类,能够触发包扫描和自动配置的逻辑,从而使得SpringBoot的相关bean被注册进Spring容器。

    (2)创建SpringApplication对象

      SpringApplication类的run方法,这个方法就做了2件事:一是创建SpringApplication对象,二是启动SpringApplication

      SpringApplication构造器源码如下

    public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = Collections.emptySet();
        this.isCustomEnvironment = false;
        this.lazyInitialization = false;
        this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
        this.applicationStartup = ApplicationStartup.DEFAULT;
        this.resourceLoader = resourceLoader;
        //断言primarySources不能为null,如果为null,抛出异常提示
        Assert.notNull(primarySources, "PrimarySources must not be null");
        //启动类传入的Class
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //判断当前项目类型,有三种:NONE、SERVLET、REACTIVE
        this.bootstrappers = new ArrayList(this.getSpringFactoriesInstances(Bootstrapper.class));
        //设置ApplicationContextInitializer
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //设置监听器
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        //判断主类,初始化入口类
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }
    
    //判断主类
    private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
            StackTraceElement[] var2 = stackTrace;
            int var3 = stackTrace.length;
    
            for(int var4 = 0; var4 < var3; ++var4) {
                StackTraceElement stackTraceElement = var2[var4];
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException var6) {
        }
    
        return null;
    }
    View Code

      在构造器里主要就做了2件事,1是设置初始化器,2是设置监听器。

      设置初始化器的源码如下

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
        return this.getSpringFactoriesInstances(type, new Class[0]);
    }
    
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = this.getClassLoader();
        //从类路径的META-INF处读取相应配置文件spring.factories,然后进行遍历,读取配置文件中Key(type)对应的value
        Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        //将names的对象实例化
        List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
    View Code

      ApplicationContextInitializer.class从类路径的META-INF处读取相应配置文件spring.factories并实例化对应Initializer。

      设置监听器:和设置初始化器一个样,都是通过getSpringFactoriesInstances函数实例化监听器。

      创建了SpringApplication实例之后,就完成了SpringApplication类的初始化工作。

    (3)run方法

      得到SpringApplication实例后,接下来就调用实例方法run()。

      run方法源码如下

    public ConfigurableApplicationContext run(String... args) {
        //创建计时器
        StopWatch stopWatch = new StopWatch();
        //开始计时
        stopWatch.start();
        DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
        //定义上下文对象
        ConfigurableApplicationContext context = null;
        //Headless模式设置
        this.configureHeadlessProperty();
        //加载SpringApplicationRunListeners监听器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        //发送ApplicationStartingEvent事件
        listeners.starting(bootstrapContext, this.mainApplicationClass);
    
        try {
            //封装ApplicationArguments对象
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //配置环境模块
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            //根据环境信息配置要忽略的bean信息
            this.configureIgnoreBeanInfo(environment);
            //打印Banner标志
            Banner printedBanner = this.printBanner(environment);
            //创建ApplicationContext应用上下文
            context = this.createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            //ApplicationContext基本属性配置
            this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            //刷新上下文
            this.refreshContext(context);
            //刷新后的操作,由子类去扩展
            this.afterRefresh(context, applicationArguments);
            //计时结束
            stopWatch.stop();
            //打印日志
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
    
            //发送ApplicationStartedEvent事件,标志spring容器已经刷新,此时所有的bean实例都已经加载完毕
            listeners.started(context);
            //查找容器中注册有CommandLineRunner或者ApplicationRunner的bean,遍历并执行run方法
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            //发送ApplicationFailedEvent事件,标志SpringBoot启动失败
            this.handleRunFailure(context, var10, listeners);
            throw new IllegalStateException(var10);
        }
    
        try {
            //发送ApplicationReadyEvent事件,标志SpringApplication已经正在运行,即已经成功启动,可以接收服务请求。
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            //报告异常,但是不发送任何事件
            this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }
    View Code

      执行完run方法后,SpringBoot就启动完成了。

    3.总结

      启动类看起来就一个@SpringBootApplication注解,一个run()方法。其实是经过高度封装后的。从这个分析中学到很多东西。例如使用了spring.factories文件来完成自动配置,提高了扩展性。在启动时使用观察者模式,以事件发布的形式通知,降低耦合,易于扩展等等。

  • 相关阅读:
    React Native Android打包apk
    React-Native新列表组件FlatList和SectionList学习 | | 联动列表实现
    使用react native制作的微博客户端
    Shell 脚本中 '$' 符号的多种用法
    Shell编程 | 脚本参数与交互及常见问题
    Shell编程-条件测试 | 基础篇
    Shell编程-控制结构 | 基础篇
    Python运维中20个常用的库和模块
    20款开发运维必备的顶级工具
    Linux 系统结构详解
  • 原文地址:https://www.cnblogs.com/bl123/p/14346016.html
Copyright © 2011-2022 走看看