springboot的启动流程源码分析

java1234

共 10855字,需浏览 22分钟

 ·

2020-10-02 18:47

点击上方蓝色字体,选择“标星公众号”

优质文章,第一时间送达

  作者 |  yangxiaohui227 

来源 |  urlify.cn/6jeaiu

66套java从入门到精通实战课程分享

测试项目,随便一个简单的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 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 listeners;

    SpringApplicationRunListeners(Log log, Collection 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 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 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 configurations = getCandidateConfigurations(annotationMetadata, attributes);//获取配置类,这个是重点后续跟进
        configurations = removeDuplicates(configurations);//去重,下面是做一些排除操作,因为@EnableAutoConfiguration 注解可以配置要排除哪些类
        Set 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 即可






     



感谢点赞支持下哈 

浏览 23
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报