美文网首页java学习之路
spring boot 源码解析(七) springBoot原理

spring boot 源码解析(七) springBoot原理

作者: 唯有努力不欺人丶 | 来源:发表于2020-12-15 23:12 被阅读0次

    其实关于这块我是觉得翻来覆去的讲了好多遍了,尤其是开头讲的启动,自动配置之类的,不过课程这么设置也应该有它自己的原理。下面让我们按照教程一步一步学习了解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 起码有个浅显的认识而不是之前只会使用啦,每天进步一点点~~我们共勉!

    相关文章

      网友评论

        本文标题:spring boot 源码解析(七) springBoot原理

        本文链接:https://www.haomeiwen.com/subject/uipbgktx.html