精华内容
下载资源
问答
  • springboot源码分析
    2022-01-27 10:17:14

    SpringBoot源码分析(三)之源码编译


    前言

    对于想研究Spring Boot源码的同学来说,最好能在本地进行编译,同时在分析源码的时候,可以添加自己的注释,接下来,我们先来搭建一下我们本地源码环境。
    由于Spring Boot源码项目在2.3.0版本之前都是用的Maven构建的,在2.3.0版本之后改用Gradle构建项目。为了简单省事,本文选择了SpringBoot2.2.5版本的源码进行编译,因为我们大部分人使用的都是Maven,省去了安装Gradle环境的环节。

    一、源码下载

    既然我们选择了SpringBoot2.2.5版本的源码,我们可以去github上找到对应版本的Spring Boot源码(https://github.com/spring-projects/spring-boot).我们选择的是v2.2.5.RELEASE,
    在这里插入图片描述
    选择好版本之后,直接点击下载即可。
    在这里插入图片描述

    二、本地编译

    我们将下载的Spring Boot源码解压,导入到IDEA中。

    1.修改根pom文件

    首先,我们先将pom文件上Spring Boot的版本号由2.2.5.RELEASE改为2.2.5.snapshot,同时我们需要添加一个 disable.checks属性并将其设置成true,可以在编译源码的时候关闭maven的代码检查。
    pom.xml

    <properties>
    	<revision>2.2.5.snapshot</revision>
    	<main.basedir>${basedir}</main.basedir>
    	<!-- 添加属性 忽悠Maven的代码检查-->
    	<disable.checks>true</disable.checks>
    </properties>
    

    如何不设置disable.checks属性的话,那么在Maven编译打包的时候,就需要忽略maven的代码检查。

    mvn clean install -DskipTests -Pfast
    

    2.编译源码

    此时,我们就可以编译我们的源码了。打开IDEA右侧的Maven条框,点击上方的“M”图标(execute Maven Goal),将mvn clean install -DskipTests指令输入进去,点击回车按键即可。
    在这里插入图片描述
    编译的过程中,可能会出现如下错误:
    在这里插入图片描述
    按照提示,执行命令即可:

    mvn spring-javaformat:apply
    

    然后再次执行编译命令,慢慢等候即可。

    但是笔者编译的时候使用的是MAC电脑,在编译spring boot gradle plugin时,出现了Failed to execute goal org.codehaus.mojo:exec-maven-plugin:1.6.0:exec (gradle) on project spring-boot-gradle-plugin: Command execution failed.
    在这里插入图片描述

    经过查询报错日志得知,该异常是因为MAC电脑下 /Library/Internet\ PlugIns/JavaAppletPlugin.plugin/Contents/Home/lib中没有tools.jar
    解决的办法就是在该路径下添加tools.jar,本文参考的是:https://blog.csdn.net/GUO_NULL192/article/details/119649802

    1.先执行/usr/libexec/java_home -V,快速找到MAC下的java jdk路径。
    如图,会显示两个jdk路径,其中/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home路径下没有tools.jar,跟报错日志说法一致。
    在这里插入图片描述
    2.将我们jdk1.8中的tools.jar移动到/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home下即可。

    cp /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/lib/tools.jar /Library/Internet\ Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/lib
    

    此刻,我们再次执行编译命令,慢慢等候几分钟即可。

    三、源码环境的使用

    既然编译好了Spring Boot源码,那么我们就可以来创建我们自己的Spring Boot项目了,当然创建的Spring Boot项目最好和我们编译的源码版本一致。
    创建自己的Spring Boot项目并关联我们编译好的源码,一般有两种方式:一种是直接在源码工程中创建我们的项目作为源码工程中的一个模块;另一种就是单独创建一个Spring Boot工程,将源码导入到该工程中。

    1.源码的工程下创建Spring Boot项目

    源码编译好之后,我们就可以在Spring Boot源码项目中来创建我们自己的SpringBoot项目了,我们在 spring-boot-project项目下创建 module:springboot-demo。
    并将pom中parent中spring boot的版本改成2.2.5.snapshot。
    在这里插入图片描述
    此时,我们的demo项目可以成功运行。在查看Spring Boot中的注解时,直接点击就可以跳到源码部分。当然了,我们只编译了Spring Boot的源码,对于Spring的注解,我们还是看不了的。

    2.在独立的项目中导入源码

    在独立的Spring Boot工程中使用我们编译之后的源码,可能更方便大家来针对源码的功能进行调试。在创建Spring Boot的demo工程时,使用的版本最好和我们编译的Spring Boot版本一致。
    1.先创建2.2.5.RELEASE版本的spring boot项目;
    2.将编译好的2.2.5.snapshot版本的源码导入到项目;
    依次点击IDEA->File->Project Structure,
    找到Project settings下的Libraries,在libraries中找到spring boot并点击,在右侧的+里面找到我们源码编译的目录,并选择导入即可。
    在这里插入图片描述

    此时,我们的源码就导入到项目中了。我们可以通过点击项目中Spring Boot中的方法或者注解,跳转到源码中去,在源码中我们可以进行修改和注释,在第一次改动源码的时候,会弹出一个提示:这个文件不属于该项目。我们点击第二条“I want to edit all files in this directory”即可。
    在这里插入图片描述
    再次打开源码:我们就能发现我们在demo中修改的源码,会同步到spring boot 2.2.5 snapshot源码中。
    在这里插入图片描述

    更多相关内容
  • springboot源码分析

    2022-03-08 19:27:15
    SpringBoot核心理念 能够实现帮助开发者快速的整合第三方框架(Spring、Mybatis、hibernate) 原理:Maven依赖封装整合和自定义starter. 完全去除XML配置,采用注解形式 原理:SpringBoot其实根据 Spring 体系原生的...

    快速开发底层原理

    SpringBoot核心理念

    1. 能够实现帮助开发者快速的整合第三方框架(Spring、Mybatis、hibernate)
      原理:Maven依赖封装整合和自定义starter.
    2. 完全去除XML配置,采用注解形式
      原理:SpringBoot其实根据 Spring 体系原生的注解实现包装
    3. 不需要外部容器,内嵌入服务器(Tomcat) .
      原理:Java语言创建tomcat服务器,让后将本地class文件交给tomcat加载。
      案例:注解方式启动MVC将SpringMVC交给内部tomcat运行。,

    内置tomcat原理

    手动利用springmvc 实现不需要 tomcat 实现项目启动,仿 springboot项目,main方法启动项目。
    在这里插入图片描述

    starter基本思想

    starter 其实就是springboot 对第三方常用框架组件的命名方式。

    Starter是Spring Boot中的一个非常重要的概念,Starter相当于模块,它能将模块所需的依赖整合起来并对模块内的Bean根据环境( 条件)进行自动配置。使用者只需要依赖相应功能的Starter,无需做过多的配置和依赖,Spring Boot就能自动扫描并加载相应的模块。

    starter命名是有规范:
    如果是SpringBoot官方自定义Starter命名为

    • Spring-boot-starter-XX 官方.
    • mybatis-Spring-boot-starter 非官方的

    自定义starter

    1、新建项目
    在这里插入图片描述

    2、引入 maven 依赖

      <!--  自定义starter -->
        <dependencies>
            <!-- Compile dependencies -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
            </dependency>
            <!--  能够让我们编写的配置文件在 其他人引用的时候有一定提示-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
            </dependency>
        </dependencies>
    

    3、编写Java类

    TokenAutoConfiguration

    @Configuration
    @EnableConfigurationProperties(TokenProperties.class)
    public class TokenAutoConfiguration {
    
        @Bean
        public TokenService tokenService() {
            return new TokenService();
        }
    }
    

    TokenService

    public class TokenService {
    
        @Autowired
        private TokenProperties tokenProperties;
    
        public String getToken() {
            System.out.println("模拟生成token");
            return tokenProperties.getTokenRedisHost() + "," + tokenProperties.getTokenRedisPwd();
        }
    }
    

    TokenProperties

    @ConfigurationProperties(prefix = "kaico")
    public class TokenProperties {
        private String tokenRedisHost;
        private String tokenRedisPwd;
    
        public void setTokenRedisHost(String tokenRedisHost) {
            this.tokenRedisHost = tokenRedisHost;
        }
    
        public void setTokenRedisPwd(String tokenRedisPwd) {
            this.tokenRedisPwd = tokenRedisPwd;
        }
    
        public String getTokenRedisHost() {
            return tokenRedisHost;
        }
    
        public String getTokenRedisPwd() {
            return tokenRedisPwd;
        }
    }
    

    spring.factories

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.kaico.config.TokenAutoConfiguration
    

    spring.factories 该文件作用:SPI 是 Java 提供的一种服务加载方式

    4、将该jar包打入maven仓库中 mvn clean install

    5、其他springboot 项目引入该jar包,在application 配置文件中配置 TokenProperties 类指定的两个参数,调用 TokenService 的方法时可以获取到这个参数。

    SpringBoot源码深度解析

    启动类源码分析

    Springboot启动流程:
    1、创建new SpringApplication
    2、调用run方法
    在这里插入图片描述SpringApplication 的构造方法
    在这里插入图片描述获取当前启动环境,servlet运行、非servlet运行、响应式运行
    在这里插入图片描述扫描每一个jar包,将需要加载的ApplicationContextInitializer类加入到initializers集合中去

    在一个Springboot应用中,classpath上会包含很多jar包,有些jar包需要在
    ConfigurableApplicationContext#refresh() 调用之前对应用上下文做一些初始化动作,因此它们会提供自己的ApplicationContextInitializer实现类,然后放在自己的META-INF/spring.factories 属性文件中,这样相应的ApplicationContextInitializer实现类就会被SpringApplication#initialize发现:

    在这里插入图片描述


    在这里插入图片描述
    拿到启动类
    在这里插入图片描述

    WebApplicationType 的作用

    在这里插入图片描述webApplicationType

    • NONE 不会嵌入我们的web服务器最终通过外部tomcat服务器
    • SERVLET需要使用servlet服务器运行
    • REACTIVE 使用响应式web启动

    设置banner图

    1、启动类设置,手动设置banner对象
    在这里插入图片描述在这里插入图片描述
    2、在src/mainlresouces下新建banner.txt,在文件中加入打印的信息,springboot默认加载resource下的banner.txt文件的内容
    在这里插入图片描述读取resource 中的banner.txt 文件和配置的banner配置的图片路径或者默认resource 中banner命名的图片名称
    在这里插入图片描述SpringbootApplication类中
    在这里插入图片描述

    SpringBoot加载配置原理

    核心注解:@SpringBootApplication
    在这里插入图片描述@EnableAutoConfiguration注解作用【加载第三方配置的启动类】,注入jar包中配置类到IOC容器中,也就是读取Springboot-autoconfigure jar包中 spring.factories中配置的109个类注册到IOC容器中去,但也不是所有的类都会注册进去,需要看你有没有引入对应的springboot依赖。和springMVC相关的 bean也注入到IOC容器中去了。
    在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

    内置Tomcat 源码分析

    server 配置类
    在这里插入图片描述
    支持三种 web服务器:Tomcat、Jetty、Undertow
    在这里插入图片描述默认创建Tomcat
    在这里插入图片描述先创建tomcat,在启动SpringMVC容器。

    SpringMVC 容器加载源码分析

    springMVC配置文件
    在这里插入图片描述创建DispatcherServlet注入到IOC容器
    在这里插入图片描述

    Springboot启动总结

    核心分为两个步骤:

    1. 创建 SpringApplication对象
    2. 调用SpringApplication run 实现启动同时返回当前的容器上下文

    具体流程:
    3. 创建 SpringApplication对象 Springboot 容器初始化操作
    在这里插入图片描述

    1. 在SpringApplication的构造函数中获取当前应用启动类型 原理:判断当前classpath是否有加载我们的servlet类 返回servlet web启动方式
      在这里插入图片描述

    2. setInitializers 读取SpringBoot包下面的META-INF/spring.factories 获取到对应ApplicationContextInitializer 的实现类装配到集合(initializers)中
      在这里插入图片描述

    3. setListeners 读取SpringBoot包下面的META-INF/spring.factories 获取到对应ApplicationListener 的实现类装配到集合(listeners)中
      在这里插入图片描述

    4. mainApplicationClass 获取当前运行的主函数
      在这里插入图片描述

    5. 调用SpringApplication run方法实现启动
      在这里插入图片描述

    6. StopWatch stopWatch = new StopWatch(); 记录我们SpringBoot项目启动时间

    7. getRunListeners(args); 读取我们的ETA-INF/spring.factories SpringApplicationRunListeners 类型存入到集合中
      在这里插入图片描述在这里插入图片描述

    8. listeners.starting(); 循环调用监听starting方法
      在这里插入图片描述

    9. ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
      listeners.environmentPrepared(environment);
      利用 listeners 读取我们配置文件数据到SpringBoot容器中
      在这里插入图片描述

    10. Banner printedBanner = printBanner(environment); 打印我们SpringBoot Banner
      在这里插入图片描述

    11. 创建SpringBoot上下文AnnotationConfigServletWebServerApplicationContext对象,根据 webApplicationType 创建对应的环境
      在这里插入图片描述在这里插入图片描述

    12. refreshContext(context); 刷新我们上下文
      在这里插入图片描述

    13. 开始创建我们的tomcat容器
      run方法中的 refreshContext(context); 再到 applicationContext.refresh();
      在这里插入图片描述


    在这里插入图片描述

    在这里插入图片描述


    在这里插入图片描述

    在这里插入图片描述

    1. 开始加载我们的SpringMVC
      获取spring-boot-configurations下面的META-INF/ spring.factorie所有的类注册到IOC容器中。
      org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,
      org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\

    配置类
    在这里插入图片描述在这里插入图片描述

    1. afterRefresh 定义一个空的模版给其他子类实现重写

    2. listeners.started(context); 使用广播和回调机制通知监听器已SpringBoot容器启动成功

    3. listeners.running(context); 使用广播和回调机制通知监听器已SpringBoot容器启动成功running

    listeners (监听器集合) 获取源码分析,作用:加载配置文件配置信息到springboot容器中。
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/854613854927413fa101930546ec4589.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAa2FpY28yMDE4,size_20,color_FFFFFF,t_70,g_se,x_16

    在这里插入图片描述

    读取自定义配置文件到Springboot容器中

    1、编写Java类,实现 SpringApplicationRunListener 接口,可以实现分布式配置中心读取配置信息
    在这里插入图片描述2、通过在项目中的 resource下的 META-INF/spring.factories 文件中配置这个Java类就可以将这个Java类注入到SpringBoot容器中。
    springboot 默认读取配置文件,默认读取配置文件的路径为:

    在这里插入图片描述

    展开全文
  • 很完整的源码分析教程,本人亲自使用,内容完整,技术较心,可用于学习,研究
  • ============== SpringBoot源码专题持续更新中... ==================== 本项目结合以下源码分析文章阅读或调试效果会更佳哦 目录 持续更新中... 更多源码分析文章请取消至: : ===============待办事项清单=====...
  • SpringBoot源码分析Demo

    2018-11-18 16:23:17
    SpringBoot源码分析Demo,资源分为三个DEMO,其中解析SpringBoot 自动化配置的实现,以及注解解析demo
  • SpringBoot源码分析

    千次阅读 2019-08-02 16:40:06
    一.SpringApplication.run(Xxx.class, args) 源码分析 @SpringBootApplication public class IndexApplication { public static void main(String[] args) { SpringApplication.run(IndexApplication.clas...

    一. SpringApplication.run(Xxx.class, args) 源码分析

    @SpringBootApplication
    public class IndexApplication {
        public static void main(String[] args) {
            SpringApplication.run(IndexApplication.class, args);
        }
    }

    进入SpringApplication的run方法:

    调用重载的方法run:

    可以看出,核心分为两步,第一步new SpringApplication(),第二步调用run方法,所以启动类我们可以这样写:

    @SpringBootApplication
    public class IndexApplication {
        public static void main(String[] args) {
            SpringApplication application = new SpringApplication(IndexApplication.class);
            application.run(args);
        }
    }

    启动项目,可以发现能够正常启动,下面我们重点分析这两步:

    1. new SpringApplication究竟做了什么?

    核心分为3步:

    ① this.webApplicationType

    进入deduceWebApplicationType()方法,可以看出,该方法目的是拿到当前的webApplicationType。

    private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.reactive.DispatcherHandler";
    private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.servlet.DispatcherServlet";
    private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
        "org.springframework.web.context.ConfigurableWebApplicationContext" };
    public enum WebApplicationType {
        NONE,
        SERVLET,
        REACTIVE
    }

    如果是当前所有jar包中,存在REACTIVE_WEB_ENVIRONMENT_CLASS,也就是DispatcherHandler,并且没有DispatcherServlet类,则返回REACTIVE(响应式编程);由于我们项目找不到该类,并且有DispatcherServlet类,所以不走第一个if; 下面进入for循环,代表当前环境中没有ConfigurableWebApplicationContext,则返回NONE,由于我们项目该类,所以跳出循环;最后返回SERVLET类型。

    关于枚举WebApplicationType 有如下解读:

    NONE应用程序不应作为Web应用程序运行,也不应启动嵌入式Web服务器
    REACTIVE应用程序应作为响应式Web应用程序运行,并应启动嵌入响应式Web服务器
    SERVLET应用程序应作为基于servlet的Web应用程序运行,并应启动嵌入式Servlet Web服务器

    ② setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class) );

        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

    获取到当前需要加载的ApplicationContextInitializer.class和ApplicationListener.class。

    getSpringFactoriesInstances()作用是获取到当前需要加载的class文件

    setInitializers,setListeners作用是把getSpringFactoriesInstances()获取的配置类放入List集合中去。

    即会去spring-boot的jar包和spring-boot-autoconfigure的jar包下的spring.factories去查找上面两个标红的class对应的value。

    可以看到,ApplicationContextInitializer对应的value有:(下图依次为spring-boot,spring-boot-autoconfigure)

    可以发现,一共获取到6个value,并且debug调试也可以得到总数:

    ApplicationListener对应的value有,注意" \ "表示换行:(下图依次为spring-boot,spring-boot-autoconfigure)

    可以发现,一共获取到10个value,并且debug调试也可以得到总数:

      获取到上述ApplicationContextInitializer和ApplicationListener对应的配置类之后,会分别调用setInitializers()和setListeners()

      会分别放入相应的List集合中。

    private List<ApplicationContextInitializer<?>> initializers;
    public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
        this.initializers = new ArrayList<>();
        this.initializers.addAll(initializers);
    }
    private List<ApplicationListener<?>> listeners;
    public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
        this.listeners = new ArrayList<>();
        this.listeners.addAll(listeners);
    }

    ③ this.mainApplicationClass = deduceMainApplicationClass(); 获取到当前启动类,即IndexApplication。

     至此,new SpringApplication就分析完毕。

    2. run (该方法为SpringBoot的核心)

    首先贴出run方法的全部代码:

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments,printedBanner);
            refreshContext(context);
            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;
    }

      流程分析:

    ① StopWatch stopWatch = new StopWatch(); 记录我们SpringBoot项目启动时间

    ② Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); 

    异常收集器,SpringBootExceptionReporter是一个回调接口,用于支持对SpringApplication启动错误的自定义报告。

    ③ SpringApplicationRunListeners listeners = getRunListeners(args);  读取我们的META-INF/spring.factories

    SpringApplicationRunListeners 类型存入到集合listeners 中,默认只有一个value,不过我们可以自定义,如:

    /**
     * 读取配置文件
     */
    public class MySpringApplicationRunListener implements SpringApplicationRunListener, Ordered {
        private SpringApplication application;
        private String[] args;
        @Override
        public void starting() {
            System.out.println(">>>>starting<<<<");
        }
        public MySpringApplicationRunListener(SpringApplication application, String[] args) {
            this.application = application;
            this.args = args;
        }
        @Override
        public void environmentPrepared(ConfigurableEnvironment environment) {
            // 配置文件读取到程序中 思路需要自己将本地文件读取到程序中,然后再放入到SpringBoot容器
            Properties properties = new Properties();
            try {
                //1.读取我们的my.properties文件
                properties.load(this.getClass().getClassLoader().
                        getResourceAsStream("my.properties"));
                //2.读取名称名称为my
                PropertySource propertySource = new
                        PropertiesPropertySource("my", properties);
                //3.将资源添加到SprigBoot项目中
                MutablePropertySources propertySources = environment.getPropertySources();
                //4.通过该api接口可以将配置文件读取 到SpringBoot项目中
                propertySources.addLast(propertySource);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void contextPrepared(ConfigurableApplicationContext context) {
        }
        @Override
        public void contextLoaded(ConfigurableApplicationContext context) {
        }
        @Override
        public void started(ConfigurableApplicationContext context) {
        }
        @Override
        public void running(ConfigurableApplicationContext context) {
        }
        @Override
        public void failed(ConfigurableApplicationContext context, Throwable exception) {
        }
        @Override
        public int getOrder() {
            return -1;//优先级,值越小优先级越高,application.yml/properties默认为0
        }
    }
    

    上述即为自定义配置文件的底层写法,加上了该文件后,则listeners的size变为2,这里仅作个理解,下面讲解默认无该类。

    ④ listeners.starting();

    在此方法中,使用了上文取到的listeners,循环遍历(其实只有一个listener,就是EventPublishingRunListener),调用监听starting方法。

    ⑤ ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

    可以看到,和上面③中我们自定义读取配置文件一致,循环遍历调用environmentPrepared方法,读取配置文件,底层然后通过addLast放入SpringBoot容器。(该方法扩展很多,可以自己重点去研究)

    ⑥ Banner printedBanner = printBanner(environment);

    打印我们springboot启动的的banner伪图片,没有业务作用。

    ⑦ context = createApplicationContext();

    public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
            + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

    会判断我们的webApplicationType(1-①中有说明),因为当前环境为Web环境,则类型为SERVLET类型,所以会返回上图红框中类型的context上下文,即这一步作用是:创建SpringBoot上下文AnnotationConfigServletWebServerApplicationContext对象。

    ⑧  refreshContext(context);  刷新我们上下文

    这个方法其实是调用了AbstractApplicationContext的refresh()方法,但是有一点尤其注意,这也是springboot项目的重中之重,就是tomcat容器的启动也是在这里操作的,refresh()方法中的onfresh()方法被EmbeddedApplicationContext重写了,在此做了tomcat的启动加载,并且SpringMVC也是在这一步加载的。

    ⑨ afterRefresh(context, applicationArguments);

    定义一个空的模版给其他子类实现重写。

    ⑩ listeners.started(context);  使用广播和回调机制通知监听器SpringBoot容器已经启动成功

    spring容器已经刷新过且应用已经启动,但是CommandLineRunners和ApplicationRunners还未调用,直接通过spring容器自己发送(因为ApplicationListener已经加入spring容器)

    ⑪ listeners.running(context);

    此步骤已经调用了CommandLineRunners(作用:在使用SpringBoot构建项目时,我们通常有一些预先数据的加载,实现该接口即可),该步骤表示SpringBoot正式启动完成。

    ⑫ return context;  最后返回当前上下文

    拓展:CommandLineRunner和ApplicationRunner

    在开发中可能会有这样的情景。需要在容器启动的时候执行一些内容,比如Spring启动加载且加载一次,比如初始化一些用户信息等。SpringBoot给我们提供了两个接口来帮助我们实现这种需求。这两个接口分别为CommandLineRunner和ApplicationRunner。他们的执行时机为容器启动完成的时候。

    这两个接口中有一个run方法,我们只需要实现这个方法即可。这两个接口的不同之处在于:ApplicationRunner中run方法的参数为ApplicationArguments,而CommandLineRunner接口中run方法的参数为String数组。目前我在项目中用的是ApplicationRunner。是这么实现的:

    @Component
    public class LzPartnerInitialConfig implements ApplicationRunner {
        @Override
        public void run(ApplicationArguments args) throws Exception {
           // todo
        }
    }

    二. @SpringBootApplication注解源码分析

    进入@SpringBootApplication注解,可以看到该注解又包装了3大注解:

    @SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan

    @SpringBootConfiguration,其实就是封装了@Configuration注解,目的是为了语义化

    @Configuration注解用于定义配置类,可替换xml配置文件,结合AnnotationConfigApplicationContext可以通过注解方式启动Spring并初始化SpringIOC容器,该注解不在本文重点讨论范围之内。

    @ComponentScan,即扫包,该注解主要就是定义扫描的路径从中找出标识了需要装配的类自动装配到spring的bean容器中。

    上面两个注解通俗易通,下面我们重点分析@EnableAutoConfiguration注解:

    @EnableAutoConfiguration说白了就是加载我们第三方配置类。

    可以看到,该注解又引入了AutoConfigurationImportSelector类

    那么我们进入AutoConfigurationImportSelector类,重点看到selectImports()方法:

    在标红处打断点,可以看到会注入112个配置类到IOC容器中:

    那么问题来了,这些配置类是从何而来?

    答案:上上图红框代码即代表读取spring-boot-autoconfigure/META-INF/spring.factories,找到EnableAutoConfiguration(即SpringBoot核心注解之一)对应的100多个配置类(这些全是配置类即都用@Configuration修饰),然后注册到IOC容器中,注意,并不是全部注入到IOC容器中,只是当前项目已经引入的maven/jar才会注册到IOC容器,如:当前项目并没有引入solr依赖,则solr并不会注册。

    通过上面分析,可以得出结论,通过@EnableAutoConfiguration,Spring在启动的时候,会把上面100多个配置类加载到SpringIOC容器中去。

    SpringBoot最终是如何创建tomcat和springmvc的呢?靠的就是@EnableAutoConfiguration加载的以下两个配置类:

    ServletWebServerFactoryAutoConfiguration,DispatcherServletAutoConfiguration

    org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\

    【分析 ServletWebServerFactoryAutoConfiguration】

    进入该配置类,贴出核心代码:

    @Configuration
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    @ConditionalOnClass(ServletRequest.class)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @EnableConfigurationProperties(ServerProperties.class)
    @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
            ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
            ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
            ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
    public class ServletWebServerFactoryAutoConfiguration {
        @Bean
        public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(
            ServerProperties serverProperties) {
            return new ServletWebServerFactoryCustomizer(serverProperties);
        }
        @Bean
        @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
        public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
        ServerProperties serverProperties) {
        return new TomcatServletWebServerFactoryCustomizer(serverProperties);
        }
    }

    进入return new TomcatServletWebServerFactoryCustomizer(serverProperties),

    public TomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
        this.serverProperties = serverProperties;
    }

    可以看到该类封装了我们平时在yml的配置信息,最常用的如server.port,即我们可以在application.yml修改SpringBoot内嵌tomcat的端口号等信息。

    下面我们继续分析,在ServletWebServerFactoryAutoConfiguration类中,会引入三个容器注入到IOC容器中,SpringBoot默认的WEB容器为Tomcat,所以我们重点分析Tomcat,进入EmbeddedTomcat类(Embedded英文意思为嵌入式的)

    进入EmbeddedTomcat类后,可以看到该类定义一个bean,往SpringIOC容器注入了名为TomcatServletWebServerFactory的类

    进入TomcatServletWebServerFactory类,可以发现Spring底层会执行:Tomcat tomcat = new Tomcat(),SpringBoot嵌入的Tomcat就是在这里创建的。

    【分析 DispatcherServletAutoConfiguration】

    进入该配置类,贴出核心代码:

    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    @Configuration
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass(DispatcherServlet.class)
    @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
    @EnableConfigurationProperties(ServerProperties.class)
    public class DispatcherServletAutoConfiguration {
    
        public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
        
        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet() {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(
                this.webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(
                this.webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(
                this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
            return dispatcherServlet;
        }
    
    }

    可以看到,@ConditionalOnClass(DispatcherServlet.class) && @Bean 把DispatcherServlet注入到IOC容器中,创建bean(即dispatcherServlet)后完后交给tomcat运行。其中@ConditionalOnClass注解是Springboot实现自动配置的重要支撑之一,其用途是判断当前classpath下是否存在指定类,若是则将当前的配置装载入spring容器。

    通过Debug断点调试,可以得出结论,SpringBoot启动的时候,会先创建Tomcat,然后创建dispatcherServlet去加载SpringMVC

        

    至此,SpringBoot核心原理及源码就分析完毕了 !~

    展开全文
  • SpringBoot核心源码之SpringApplication构造方法讲解

    请添加图片描述

      前面给大家介绍了SpringBoot启动的核心流程,本文开始给大家详细的来介绍SpringBoot启动中的具体实现的相关细节。

    SpringBoot2.png

    SpringApplication构造器

      首先我们来看下在SpringApplication的构造方法中是如何帮我们完成这4个核心操作的。

    image.png

    	@SuppressWarnings({ "unchecked", "rawtypes" })
    	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    		// 传递的resourceLoader为null
    		this.resourceLoader = resourceLoader;
    		Assert.notNull(primarySources, "PrimarySources must not be null");
    		// 记录主方法的配置类名称
    		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    		// 记录当前项目的类型
    		this.webApplicationType = WebApplicationType.deduceFromClasspath();
    		// 加载配置在spring.factories文件中的ApplicationContextInitializer对应的类型并实例化
    		// 并将加载的数据存储在了 initializers 成员变量中。
    		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    		// 初始化监听器 并将加载的监听器实例对象存储在了listeners成员变量中
    		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    		// 反推main方法所在的Class对象 并记录在了mainApplicationClass对象中
    		this.mainApplicationClass = deduceMainApplicationClass();
    	}
    

    1.webApplicationType

      首先来看下webApplicationType是如何来推导出当前启动的项目的类型。通过代码可以看到是通过deduceFromClassPath()方法根据ClassPath来推导出来的。

    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    

      跟踪进去看代码

    	static WebApplicationType deduceFromClasspath() {
    		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
    				&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
    				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
    			return WebApplicationType.REACTIVE;
    		}
    		for (String className : SERVLET_INDICATOR_CLASSES) {
    			if (!ClassUtils.isPresent(className, null)) {
    				return WebApplicationType.NONE;
    			}
    		}
    		return WebApplicationType.SERVLET;
    	}
    

      在看整体的实现逻辑之前,我们先分别看两个内容,第一就是在上面的代码中使用到了相关的静态变量。

    image.png

      这些静态变量其实就是一些绑定的Java类的全类路径。第二个就是 ClassUtils.isPresent()方法,该方法的逻辑也非常简单,就是通过反射的方式获取对应的类型的Class对象,如果存在返回true,否则返回false

    image.png

      所以到此推导的逻辑就非常清楚了

    image.png

    2.setInitializers

      然后我们再来看下如何实现加载初始化器的。

    // 加载配置在spring.factories文件中的ApplicationContextInitializer对应的类型并实例化
    		// 并将加载的数据存储在了 initializers 成员变量中。
    		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    

      首先所有的初始化器都实现了 ApplicationContextInitializer接口,也就是根据这个类型来加载相关的实现类。

    public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
        void initialize(C var1);
    }
    

      然后加载的关键方法是 getSpringFactoriesInstances()方法。该方法会加载 spring.factories文件中的key为 org.springframework.context.ApplicationContextInitializer 的值。

    spring-boot项目下

    # Application Context Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
    org.springframework.boot.context.ContextIdApplicationContextInitializer,\
    org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
    org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
    org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
    

    spring-boot-autoconfigure项目下

    # Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
    org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
    
    

    image.png

      具体的加载方法为 getSpringFacotiesInstance()方法,我们进入查看

    	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    		// 获取当前上下文类加载器
    		ClassLoader classLoader = getClassLoader();
    		// 获取到的扩展类名存入set集合中防止重复
    		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    		// 创建扩展点实例
    		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    		AnnotationAwareOrderComparator.sort(instances);
    		return instances;
    	}
    

      先进入 SpringFactoriesLoader.loadFactoryNames(type, classLoader)中具体查看加载文件的过程.

    image.png

      然后我们来看下 loadSpringFactories方法

    image.png

      通过Debug的方式查看会更清楚哦

    image.png

      通过 loadSpringFactories 方法我们看到把 spring.factories文件中的所有信息都加载到了内存中了,但是我们现在只需要加载 ApplicationContextInitializer类型的数据。这时我们再通过 getOrDefault()方法来查看。

    image.png

      进入方法中查看

    image.png

      然后会根据反射获取对应的实例对象。

    image.png

    image.png

      好了到这其实我们就清楚了 getSpringFactoriesInstances方法的作用就是帮我们获取定义在 META-INF/spring.factories文件中的可以为 ApplicationContextInitializer 的值。并通过反射的方式获取实例对象。然后把实例的对象信息存储在了SpringApplication的 initializers属性中。

    image.png

    3.setListeners

      清楚了 setInitializers()方法的作用后,再看 setListeners()方法就非常简单了,都是调用了 getSpringFactoriesInstances方法,只是传入的类型不同。也就是要获取的 META-INF/spring.factories文件中定义的不同信息罢了。

    image.png

      即加载定义在 META-INF/spring.factories文件中声明的所有的监听器,并将获取后的监听器存储在了 SpringApplicationlisteners属性中。

    image.png

      默认加载的监听器为:

    image.png

    4.mainApplicationClass

      最后我们来看下 duduceMainApplicaitonClass()方法是如何反推导出main方法所在的Class对象的。通过源码我们可以看到是通过 StackTrace来实现的。

    StackTrace:
    我们在学习函数调用时,都知道每个函数都拥有自己的栈空间。
    一个函数被调用时,就创建一个新的栈空间。那么通过函数的嵌套调用最后就形成了一个函数调用堆栈
    

      StackTrace其实就是记录了程序方法执行的链路。通过Debug方式可以更直观的来呈现。

    image.png

      那么相关的调用链路我们都可以获取到,剩下的就只需要获取每链路判断执行的方法名称是否是 main就可以了。

    image.png

      好了到此相关的4个核心步骤就给大家分析完了,希望对大家能有所帮助哦!
    请添加图片描述

    展开全文
  • Springboot 源码分析 —— 总纲

    千次阅读 2019-05-11 16:30:36
    Springboot 运行总流程介绍。
  • ,spring注解 热身,springboot 自动装配原理
  • <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-211130ba7a.css"> <div class="htmledit_views" id="content_views"> <pre>&...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 47,973
精华内容 19,189
关键字:

springboot源码分析

spring 订阅