• springboot的启动流程源码分析


    .测试项目,随便一个简单的springboot项目即可:

     直接debug调试:

     可见,分2步,第一步是创建SpringApplication对象,第二步是调用run方法:

    1.SpringApplication对象的创建过程:

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { //resourceLoader为null,因为我们没有传入,primarySources这里包含主启动类的ThymeleafApplication.class
            this.resourceLoader = resourceLoader; //资源加载器,这里是null
            Assert.notNull(primarySources, "PrimarySources must not be null");
            this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //将主启动类字节码存起来
            this.webApplicationType = WebApplicationType.deduceFromClasspath(); //检测当前的项目web类型,后续会分析
            setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));//这里涉及springboot的一个重要知识点,后续分析
            setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//这里涉及springboot的一个重要知识点,后续分析
            this.mainApplicationClass = deduceMainApplicationClass();//这里检测main方法所在的类
        }

    通过SpringApplication的创建过程,我们分析下,它的主要几个方法:

    this.webApplicationType = WebApplicationType.deduceFromClasspath();

      因为我引入的是springboot-web相关依赖,所以,在本次测试项目中,webApplication的类型是AnnotationConfigServletWebServerApplicationContext

     setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class))setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)):

    后续很多地方也会用到这个功能,在此解析下:

    所以上面2个方法分别是从spring.factories文件中,找到key为org.springframework.context.ApplicationContextInitializer 的所有值,然后创建对应的实例,另一个ApplicationListener同理
    
    

    接下来我们分析下: this.mainApplicationClass = deduceMainApplicationClass();

    
    

    检测main方法所在的类,我们自己写的代码,自己肯定很容易知道是哪个类,但springboot框架不知道,那怎么检测呢,我们先看一个异常栈信息:

    
    

     如上图所示,我们只要从一个异常的堆栈中就可以获取到main方法了,所以源码检测main方法也是一样的:

     至此SpringApplication对象创建完毕,后续我们分析下它的run方法都做了些啥:

    public ConfigurableApplicationContext run(String... args) {
            StopWatch stopWatch = new StopWatch(); //一个计时器,用于计算启动的时间
            stopWatch.start();//计时开始
            ConfigurableApplicationContext context = null;
            Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();//异常报告器列表
            configureHeadlessProperty();//这个是给System对象设置一个属性:java.awt.headless=true
            SpringApplicationRunListeners listeners = getRunListeners(args); //从spring.factories文件中读取SpringApplicationRunListener的实现类,并创建对应的实例,这个类后续分析
            listeners.starting();//调用监听器的starting方法
            try {
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //这里仅仅是对请求参数进行封装成一个对象
                ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);//创建并配置环境的一些属性,然后调用监听器的相应方法,后续会分析
                configureIgnoreBeanInfo(environment);//往System对象中设置属性spring.beaninfo.ignore的值
                Banner printedBanner = printBanner(environment);//打印启动的图标,后续分析
                context = createApplicationContext();//在创建SpringApplication对象时,已经检测出当前环境是什么样的webAppliction的类型,这里就是创建该类型的实例,本次代码演示的是:AnnotationConfigServletWebServerApplicationContext
                exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                        new Class[] { ConfigurableApplicationContext.class }, context);//从spring.factories文件中读取SpringBootExceptionReporter的实现类,并创建对应的实例
                prepareContext(context, environment, listeners, applicationArguments, printedBanner);//为后续实例化所有的bean进行初始化工作,后续分析
                refreshContext(context);//这里开始实例化所有的bean,也就是spring ioc的核心,调用父类的refresh()方法,同时对tomcat进行了实例化,这些源码我都有专门的博客分析过了,所以这里不再重复
                afterRefresh(context, applicationArguments); //目前是空实现
                stopWatch.stop(); //计时结束
                if (this.logStartupInfo) {
                    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);//打印本次启动花了多长时间: Root WebApplicationContext: initialization completed in 4186 ms
                }
                listeners.started(context);//调用Listenners的stared方法
                callRunners(context, applicationArguments);//调用ApplicationRunner和CommandLineRunner的方法
    }
    catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context);//调用监听器的running方法 } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }

    通过上面的分析,整个启动流程在原来spring启动项目的基础上,在不同的阶段,加上了监听器的各个方法调用,现在我们来详细分析上面的一些方法:

     SpringApplicationRunListeners 和  SpringApplicationRunListener 关系,很明显,前者包含了多个后者:

    class SpringApplicationRunListeners {
    
        private final Log log;
    
        private final List<SpringApplicationRunListener> listeners;
    
        SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
            this.log = log;
            this.listeners = new ArrayList<>(listeners);
        }
    
        void starting() {
            for (SpringApplicationRunListener listener : this.listeners) {
                listener.starting(); //开始启动,此时环境和上下文都还没开始启动
            }
        }
    
        void environmentPrepared(ConfigurableEnvironment environment) {
            for (SpringApplicationRunListener listener : this.listeners) {
                listener.environmentPrepared(environment);  //环境已经准备好了,我们可以通过实现对应的listener接口来修改环境中变量
            }
        }
    
        void contextPrepared(ConfigurableApplicationContext context) {
            for (SpringApplicationRunListener listener : this.listeners) {
                listener.contextPrepared(context); //上下文已经准备好
            }
        }
    
        void contextLoaded(ConfigurableApplicationContext context) {
            for (SpringApplicationRunListener listener : this.listeners) {
                listener.contextLoaded(context);//上下文已经加载完毕
            }
        }
    
        void started(ConfigurableApplicationContext context) {
            for (SpringApplicationRunListener listener : this.listeners) {
                listener.started(context); //上下文已经启动完毕
            }
        }
    
        void running(ConfigurableApplicationContext context) {
            for (SpringApplicationRunListener listener : this.listeners) {
                listener.running(context);//上下文启动完毕后,进入运行状态
            }
        }
    
        void failed(ConfigurableApplicationContext context, Throwable exception) {
            for (SpringApplicationRunListener listener : this.listeners) {
                callFailedListener(listener, context, exception);
            }
        }
    
        private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context,
                Throwable exception) {
            try {
                listener.failed(context, exception);//启动失败做些啥
            }
            catch (Throwable ex) {
                if (exception == null) {
                    ReflectionUtils.rethrowRuntimeException(ex);
                }
                if (this.log.isDebugEnabled()) {
                    this.log.error("Error handling failed", ex);
                }
                else {
                    String message = ex.getMessage();
                    message = (message != null) ? message : "no error message";
                    this.log.warn("Error handling failed (" + message + ")");
                }
            }
        }
    
    }

    可见SpringApplicationRunListener 会在不同的启动周期调用不同的方法,不必非常刻意理解每个方法的具体含义,我们可以根据每个方法在源码中的调用位置,自己实现一个SpringApplicationRunListener做一些个性化的修改

    接下来,我们分析下:ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);方法,准备环境对象

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                ApplicationArguments applicationArguments) {
            // Create and configure the environment
            ConfigurableEnvironment environment = getOrCreateEnvironment();//获取一个环境对象,不存在就创建
            configureEnvironment(environment, applicationArguments.getSourceArgs());//进行命令行参数配置,我们命令行参数没数据是一个空数组,所以不会做什么,这里主要的设置了ConversionService对象到enviroment中
            ConfigurationPropertySources.attach(environment);//配置configurationProperties
            listeners.environmentPrepared(environment); //监听器处理环境准备好事件
            bindToSpringApplication(environment);//绑定environment到SpringApplication中
            if (!this.isCustomEnvironment) {
                environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                        deduceEnvironmentClass());
            }
            ConfigurationPropertySources.attach(environment);
            return environment;
        }

    下面看看Banner printedBanner = printBanner(environment); 效果如下:我们也可以自定义

    private Banner printBanner(ConfigurableEnvironment environment) {
            if (this.bannerMode == Banner.Mode.OFF) { //如果是设置了不打印banner,这里就不会打印
                return null;
            }
            ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
                    : new DefaultResourceLoader(getClassLoader()); //资源加载器
            SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner); //创建banner打印器
            if (this.bannerMode == Mode.LOG) {
                return bannerPrinter.print(environment, this.mainApplicationClass, logger); //打印到日志文件
            }
            return bannerPrinter.print(environment, this.mainApplicationClass, System.out);//打印到控制台,这里我们以打印到控制台来根据
        }

    继续分析: bannerPrinter.print(environment, this.mainApplicationClass, System.out);

    Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
            Banner banner = getBanner(environment); //从环境中获取banner
            banner.printBanner(environment, sourceClass, out); //打印banner 注意Banner是一个接口,这里会根据子类来调用不同的打印方法,如图片banner和文本banner是不一样的
            return new PrintedBanner(banner, sourceClass);
        }

    现在重点在于如何获取banner:getBanner(environment);

    private Banner getBanner(Environment environment) {
            Banners banners = new Banners();
            banners.addIfNotNull(getImageBanner(environment)); //获取图片banner
            banners.addIfNotNull(getTextBanner(environment));//获取文本banner
            if (banners.hasAtLeastOneBanner()) {
                return banners;
            }
            if (this.fallbackBanner != null) {
                return this.fallbackBanner;
            }
            return DEFAULT_BANNER; //都获取不到,就用默认的banner
        }

    //看看图片banner是如何获取的

     接下来,我们看看文本banner的获取:

     当加载不到默认的图片banner或者文本banner就会使用默认的banner,我们看看默认的banner是啥:

     

     

     所以,如果我们要打印自定义的banner,只要在resources文件夹下加入banner.txt  或者banner.gif/banner.jpg/banner.png即可:
    接下来,我们分析: prepareContext(context, environment, listeners, applicationArguments, printedBanner);准备上下文:

    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
                SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
            context.setEnvironment(environment); //给context设置环境对象,context在这里是AnnotationConfigServletWebServerApplicationContext
            postProcessApplicationContext(context);//这里根据条件给context设置一些重要的对象
            applyInitializers(context);//Spring.factories文件中配置的ApplicationContextInitializer实现类,对context进行一些初始化操作,我们想对context进行特定操作也可以通过这种方式
            listeners.contextPrepared(context);//监听器打印上下文已经准备好事件
            if (this.logStartupInfo) {
                logStartupInfo(context.getParent() == null);
                logStartupProfileInfo(context);//打印日志,当前启动的是那个yml  “No active profile set, falling back to default profiles: default”
            }
            // Add boot specific singleton beans
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();//获取bean工厂
            beanFactory.registerSingleton("springApplicationArguments", applicationArguments);//注册bean
            if (printedBanner != null) {
                beanFactory.registerSingleton("springBootBanner", printedBanner); //注册banner这个bean,这里比较有趣了,后续你可以通过beanFactory获得该bean,然后继续打印banner
            }
            if (beanFactory instanceof DefaultListableBeanFactory) {
                ((DefaultListableBeanFactory) beanFactory)
                        .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
            }
            if (this.lazyInitialization) {
                context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
            }
            // Load the sources
            Set<Object> sources = getAllSources(); //这里的source是指main方法所在类(ThymeleafApplication),SpringApplication.run(ThymeleafApplication.class, args);
            Assert.notEmpty(sources, "Sources must not be empty");
            load(context, sources.toArray(new Object[0]));// 为何要加载呢?原因是主启动类贴有@SpringBootApplication等注解,该启动类需要注册成一个配置bean,并解析它的注解
            listeners.contextLoaded(context);//上下文加载完成事件
        }

    //之后分析下callRunners(context, applicationArguments);//调用ApplicationRunner和CommandLineRunner的方法

    private void callRunners(ApplicationContext context, ApplicationArguments args) {
            List<Object> runners = new ArrayList<>();
            runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
            runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
            AnnotationAwareOrderComparator.sort(runners);
            for (Object runner : new LinkedHashSet<>(runners)) {  //从容器中获取ApplicationRunner和CommandLineRunner接口的实现类,并调用对应的方法,如果我们需要在项目启动完毕后做一些事情,如读取一些配置信息,可以在实现这2个接口
                if (runner instanceof ApplicationRunner) {
                    callRunner((ApplicationRunner) runner, args);
                }
                if (runner instanceof CommandLineRunner) {
                    callRunner((CommandLineRunner) runner, args);
                }
            }
        }

     最后讲讲springboot自动装配机制:前面 load(context, sources.toArray(new Object[0])); 我们分析过,它会加载主启动类,而主启动类有个注解@SpringBootApplication,该注解会被解析,所以我们分析下该注解:

     这里有个重要的注解:

     该注解向容器注入了一个bean,@import可以把其当作@componet注解,一样是往容器中注入某个bean,那么我们可以分析该bean:

     

     AutoConfigurationImportSelector 实现了ImportSelector 接口,该接口有个方法返回需要注入spring容器的bean的全限定类名:

     现在我们只要分析实现类的这个方法即可:

    @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            }
            AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); //该方法是重点
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());//将获取到的配置类转成数组
        }
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return EMPTY_ENTRY;
            }
            AnnotationAttributes attributes = getAttributes(annotationMetadata); //获取@EnableAutoConfiguration 注解的属性exclude 和 excludeName
            List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);//获取配置类,这个是重点后续跟进
            configurations = removeDuplicates(configurations);//去重,下面是做一些排除操作,因为@EnableAutoConfiguration 注解可以配置要排除哪些类
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = getConfigurationClassFilter().filter(configurations);
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }

     如上图所示:这里的意思是加载spring.factories文件中key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的所有value值,注意的是这些value值并不要求是EnableAutoConfiguration的子类,他们可以没有任何继承关系

    这些value值的bean都会被spring管理,这也就是各种框架整合springBoot的核心所在,因为你得项目如果想交给spring管理,你可以将自己的配置类配到spring.factories文件中,key为org.springframework.boot.autoconfigure.EnableAutoConfiguration 即可

    
    
    
     
  • 相关阅读:
    【NOIP2018PJ正式赛】摆渡车
    【NOIP2018PJ正式赛】龙虎斗
    【NOIP2018PJ正式赛】标题统计
    高精度除单精度
    关于输出的东东
    高精度乘单精度
    【NOIP2012模拟10.26】电影票
    【NOIP2012模拟10.26】雕塑
    【NOIP2012模拟10.26】火炬手
    【NOIP2016提高A组模拟9.7】千帆渡
  • 原文地址:https://www.cnblogs.com/yangxiaohui227/p/13748357.html
Copyright © 2020-2023  润新知