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 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 起码有个浅显的认识而不是之前只会使用啦,每天进步一点点~~我们共勉!

你可能感兴趣的:(spring boot 源码解析(七) springBoot原理及运行流程)