spring boot 源码解析(七) springBoot原理及运行流程

其实关于这块我是觉得翻来覆去的讲了好多遍了,尤其是开头讲的启动,自动配置之类的,不过课程这么设置也应该有它自己的原理。下面让我们按照教程一步一步学习了解SpringBoot的启动原理。

SpringBoot启动原理

因为之前我自己一步一步往下找走过这个,但是很多方法都是临时百度或者连蒙带猜的,这里老师一步一步讲解能让思路更清晰。我这里用图文并茂的方式记录下。

  1. 在启动类中的run方法


    启动类中启动
  2. 点进去发现本质是创建SpringApplication对象并运行run方法
    创建SpringApplication对象并运行run方法

    2.1 . 创建SpringApplication对象过程
@SuppressWarnings({ "unchecked", "rawtypes" })
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //这句代码是判断当前应用是不是web应用
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //获取类路径META-INF/spring.factories下配置的所有ApplicationContextInitlalizer保存
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //获取类路径META-INF/spring.factories下配置的所有ApplicationListener保存
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 从多个配置类中找到有main方法的主配置类。
        this.mainApplicationClass = deduceMainApplicationClass();
    }
WebApplicationType源码

获取配置文件中的类

这一块springBoot自动注入的时候看过

创建SpringApplication

2.2. 运行run方法:


运行run方法

下面一步一步分析源码:
public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
         //据说这个注解以上都是jwt的东西,从这个注解往下开始分析
        //获取类路径/META-INF/spring.factories下所有的SpringApplicationRunListeners
        SpringApplicationRunListeners listeners = getRunListeners(args);
        //回调所有SpringApplicationRunListener的starting方法
        listeners.starting();
        try {
        //封装命令行参数。
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //准备环境
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
        //控制台打印这个spring的图标及版本信息
            Banner printedBanner = printBanner(environment);
        //创建applicationContext。在这里会决定创建web容器还是普通的。2.x版本又加了个reactive容器
            context = createApplicationContext();
        //1.x版本没这句,但是看代码可以猜一下,应该是/META-INF/spring.factories下所有的异常报告?
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
        //准备上下文环境,将environment保存到ioc中。这个方法中有两个方法:
        //applyInitializers(context)回调所有2.1中保存的ApplicationContextInitlalizer的initialize方法。                
        //listeners.contextPrepared(context):回调所有SpringApplicationRunListener的contextPrepared方法。
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        //在这个方法的最后回调所有的SpringApplicationRunListener的contextLoaded方法。
        //刷新容器:IOC容器初始化,如果是web应用,还会创建嵌入式的tomcat
        //扫描,创建,加载所有组件的地方(配置类,组件,自动配置)
            refreshContext(context);
        //从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调。(注意,这里ApplicationRunner先于CommandLineRunner回调)
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

至此,这个springBoot项目算是启动了。
其实这里听的云里雾里的,毕竟好多延伸出更多的东西。尽量理解吧。

事件监听机制

配置在META-INF/spring.factories:

  • ApplicationContextInitlalizer
  • SpringApplicationRunListener

只需要放在IOC容器中:

  • ApplicationRunner
  • CommandLineRunner

这些都是组件。我们在代码中写好,要配置在指定的位置。后两个只要注入bean纳入spring管理就行了,比较简单,而前两个要写在配置文件中,我们可以找一个现成的看看人家怎么写的,参考一下:


怎么配置ApplicationContextInitlalizer和SpringApplicationRunListener

然后我们照着自己写一下(这里四个实现类就不说了,就是自己建个类分别继承上面四个接口,方法只要写打印语句我们看看什么时候输出就行了):


spring.factories中代码
输出顺序如下

启动项目会发现输出顺序如上图。
因为我们方法中都是输出语句所以显着比较简单,但是实际工作中可以在这个方法中写各种逻辑代码的。挺实用一个功能。

SpringBoot场景启动器starter

自定义starter:

  • 这个场景需要依赖什么?
  • 如何编写自动配置

一些用得到的经验:

  • @Configuration //指定这个类是一个配置类
  • @ConditionalOnxxx //在指定条件下配置生效
  • @AutoConfigureAfter //指定自动配置类的顺序
  • @Bean //给容器中添加组件
  • @ConfigurationProperties //结合相关 xxxProperties类来绑定相关配置
  • @EnableConfigurationProperties //让xxProperties生效加入到容器中
    自动配置类要能加载:必须将这个启动就加载的自动配置类配置在META-INF/spring.factories中(参考上文中容器初始化和lister的用法)

这些经验说完了,下面简单说一下spring 中启动器的常规用法:

  • 启动模块是一个空jar文件,仅仅提供辅助性依赖管理。这些依赖可能用于自动装配或其他类库。
  • 命名规则:
    • 推荐使用xxx-starter命名规约
    • 官方命名空间:
      • 前缀:spring-boot-starter
      • 模式: spring-boot-starter-模块名
      • 举例: spring-boot-starter-web,spring-boot-starter-jdbc
    • 自定义命名空间:
      • 后缀:spring-boot-starter
      • 模式: 模块名-spring-boot-starter
      • 举例: mybatis-spring-boot-starter

总结一下:启动器只用来做依赖导入。 专门写一个自动配置模块。启动器依赖自动配置,别人只需要引入启动器
接下来自己写一个简单的启动器:
首先大概说一下,如果按照官方规范的来做,这个demo最少三个项目:

  • starter场景启动器
  • autoConfigurate自动装配
  • 引用这个场景启动器的自己的项目

所以这里也是直接三个项目(因为我是eclipse,所以同时创建三个,hiahia)


如下目录结构

这里面starter里啥也没有就是引用了autoConfigure依赖而已,一个java类都莫得,所以不用多说了。
然后第一个假装是自己项目。用啥引入啥就行。也没啥特别的。
最主要的要说一下这个自动配置项目。
实现了自动配置的功能,而且还是动态的。具体怎么实现的呢?其实简单来说就是一个配置属性类xxxProperties。一个配置类xxxAutoConfiguration。
最后这个自动配置是根据自动扫描META-INF/spring.factories里的类名实现的。
下面附上实现的代码:


配置属性绑定类

这个类就是用来绑定配置文件中的属性的。


用到这种配置属性的类

注意这个类上没有任何注解!因为所有的注入都统一用自动配置类完成
自动配置类

这个类把之前的配置属性类和那个service都注入进来了。接下来重点是怎么让这个类自动执行呢?META-INF/spring.factories
这个可以参照spring的示例写

至此这个自动配置就完成了,然后把starter中引入这个依赖。再在我们的项目中引入starter依赖就完事了。
接着我们就可以测试一下啦:

配置我们之前自动注入类中需要的配置

注入我们需要用到的service

然后接口访问:
测试成功!配置参数动态注入成功!

到了这里我们想要的效果就实现啦。这个代码主要就是为了实现这个思路,功能比较浅薄。但是我们举一反三,就知道为什么spring boot只配置一些重要参数就能启动一些东西啦!
本篇笔记就记到这里,如果稍微有帮到你记得点个喜欢点个关注!这一篇其实都是原理啊,底层啊之类的东西,挺好的,不见得学了能立竿见影的看到进步,可是拓宽思路和原理,尤其是知道底层对一些问题的分析也更容易。而且这篇算是我看的教程的springboot基础篇的结束了,下面就要讲一些整合中间件的教程了!感觉学了快一个月,现在翻源码颇有心得了,对spring boot 起码有个浅显的认识而不是之前只会使用啦,每天进步一点点~~我们共勉!

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

推荐阅读更多精彩内容