关于java:给她讲最爱的SpringBoot源码

32次阅读

共计 17103 个字符,预计需要花费 43 分钟才能阅读完成。

1 Spring boot 源码环境构建

举荐环境:

idea:2020.3

gradle: 版本 gradle-6.5.1

jdk:1.8

留神!idea 和 gradle 的版本有兼容性问题,要留神搭配

1.1 Spring boot 源码下载

1、从 github 获取源码,网址:

https://github.com/spring-projects/spring-boot

咱们要搭建的是 2.4.3.RELEASE 版本,所以点击 release 之后在 tags 查找相应版本或者拜访

https://github.com/spring-projects/spring-boot/releases/tag/v2.4.3

找到 后点击 sourcecode 下载源码压缩包

目录构造

Spring-boot-project 外围代码,代码量很多(197508 行)
Spring-boot-tests 测试代码

2、间接用提供的源码包(举荐)

将源码导入到 idea。漫长的期待……

1.2 Spring boot 源码编译

1、环境配置

举荐配置:

2、开始 gradle 构建

应用 idea 的 build,不要用 gradle 的工作

看到上面的 BUILE SUCESSFUL 示意胜利

1.3 Spring boot 冒烟测试

在 springboot-boot-tests 模块下很多冒烟测试的,会拖慢下面的编译,只留下了一个:

spring-boot-smoke-test-hibernate52 工程来进行冒烟测试,关上 Hibernate52Application.java 文件,间接执行 main 办法启动 springboot,胜利!

org.springframework.boot.tests.hibernate52.Hibernate52Application

package org.springframework.boot.tests.hibernate52;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

}

执行 run

console 中呈现咱们相熟的图标。

2 Spring boot 源码深度分析

引言
应用过 SpringBoot 开发我的项目的读者应该都可能感觉到
SpringBoot 的开发实现后,只须要通过执行一个 main 办法就能够将整个 web 我的项目启动
无需将我的项目的 jar 文件放在 tomcat 下,而后启动 tomcat,进而启动我的项目。除此之外,好多依赖的 jar 包也无需咱们再进行手动配置,缩小了配置,同时也缩小了许多 xml 文件的配置,大大简化了咱们的开发过程
那么
springboot 在启动的时候到底做了哪些事件?

2.1 Spring boot 启动流程分析

第一步:new SpringApplication(primarySources)

第二步:run!

2.1.1 Spring boot 启动流程分析

Debug 一下,追踪一下整个启动过程

main 办法作为程序的入口,执行 SpringApplication.run(), 传入参数是启动类的 class 对象

1)Spring boot 源码入口
@SpringBootApplication
public class Hibernate52Application {public static void main(String[] args)  {SpringApplication.run(Hibernate52Application.class, args);
    }

}

跟踪 run 办法;进入到

参数一可反对多个次要资源。

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class<?>[] {primarySource}, args);
    }

持续进入到 run 办法

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return new SpringApplication(primarySources).run(args);
    }
2)结构器(new)
// 办法指标
//1、初始化资源加载器(classloader)//2、解决 primarySources
//3、web 利用类型推断(web、reactive、servlet)//4、通过 spring.factories 加载配置类并初始化监听器(SPI)【重点】//5、提取主类
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        //null;资源加载器,用来获取 Resource 和 classLoader 以及加载资源
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        // 寄存主加载类;set 中可同时创立多个 Application, 最初要解析这个起源上的注解
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 推断 web 类型:servlet 或 reactive
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
//        0 个,从 spring.factories 中找出 Bootstrapper 对应的属性
        this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
//        7 个,设置初始化器,从 spring.factories 中找出 ApplicationContextInitializer 对应的属性
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//        9 个,设置监听器 从 spring.factories 中找出 ApplicationListener 对应的属性
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 找出主函数 main 的类
        this.mainApplicationClass = deduceMainApplicationClass();}

下面 的代码最终会调用到 getSpringFactoriesInstances,从 spring.factories 加载属性配置

加载外围源码如下

上面代码

首先会用 classLoader 加载类门路下的所有 spring.factories 的配置内容,loadSpringFactories 办法将返回一个 key= 接口名,value= 实现类汇合的 Map 构造

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    // 先试着取缓存
        Map<String, List<String>> result = cache.get(classLoader);
        if (result != null) {return result;}

        result = new HashMap<>();
        try {
             // 获取所有 spring.factories 的 URL(3 个中央)Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
            // 遍历 URL
            while (urls.hasMoreElements()) {URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                 // 加载每个 URL 中的 properties 配置
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
           
                for (Map.Entry<?, ?> entry : properties.entrySet()) {String factoryTypeName = ((String) entry.getKey()).trim();
                          // 将实现类的配置依照 "," 符号宰割开
                    String[] factoryImplementationNames =
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                    for (String factoryImplementationName : factoryImplementationNames) {
                             // 一一增加到接口对应的汇合当中
                        result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                                .add(factoryImplementationName.trim());
                    }
                }
            }

            // Replace all lists with unmodifiable lists containing unique elements
            result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                    .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
            // 退出缓存
            cache.put(classLoader, result);
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
        return result;
    }

次要的 spring.factories

spring-boot-2.4.3/spring-boot-project/spring-boot-autoconfigure/build/resources/main/META-INF/spring.factories


spring-boot-2.4.3/spring-boot-project/spring-boot/build/resources/main/META-INF/spring.factories


spring-beans-5.3.4.jar!/META-INF/spring.factories

结构器流程总结

1、解决资源加载器、次要资源 primarySources

2、web 利用类型推断

3、从 spring.factories 中找出疏导包装器、初始化器、监听器

4、设置应用程序主类

3)boot 运行(run)

公布事件

打印 banner

初始化 ioc 容器,启动 tomcat

七大步骤

// 七大步骤
    public ConfigurableApplicationContext run(String... args) {
        // 计时器
        StopWatch stopWatch = new StopWatch();
        stopWatch.start(); // 开始计时
        //  创立启动上下文对象
        DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        // 可配置的程序容器
        ConfigurableApplicationContext context = null;
        // 设置属性 不重要
        configureHeadlessProperty();
            // 第一步:获取并启动监听器    从 spring.factories 文件中加载【测试点】SpringApplicationRunListeners listeners = getRunListeners(args);
            // 监听器公布 ApplicationStartingEvent 事件.
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        try {
            // 对参数进行包装(ApplicationArguments)ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 第二步:筹备应用程序环境【关键点】ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            // 配置疏忽 bean 的信息(不重要)configureIgnoreBeanInfo(environment);
            // 第三步:打印 banner(可自定义,参考讲义)【关键点】Banner printedBanner = printBanner(environment);
            // 第四步:创立 spring 容器
            context = createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            // 第五步:筹备 applicationContext
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            // 第六步:ioc 的 refresh 创立容器,初始化 bean,tomcat 也在这里被启动起来【关键点】refreshContext(context); 
            // 第七步:上下文刷新后触发(空办法)afterRefresh(context, applicationArguments);
            stopWatch.stop();// 进行计时
            if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            // 公布 started 事件
            listeners.started(context);
            // 执行 runner 的 run 办法【测试点】callRunners(context, applicationArguments);
        } catch (Throwable ex) {
            // 异样解决
            handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            // 触发 running 事件
            listeners.running(context);
        } catch (Throwable ex) {handleRunFailure(context, ex, null);
            throw new IllegalStateException(ex);
        }
        // 返回最终构建的容器对象
        return context;
    }

2.1.2 Spring boot 七大步骤详解

1)获取并启动监听器

这里的启动监听就是咱们须要监听 SpringBoot 的启动流程监听,实现 SpringApplicationRunListener 类即可监听

    // 获取 spring.factories 中 key 为 SpringApplicationRunListener 的对象实例。private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};
        // 通过从 spring.factories 中获取 SpringApplicationRunListener 类型的配置类
        return new SpringApplicationRunListeners(logger,
                getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
                this.applicationStartup);
    }

查看具体 SpringApplicationRunListener 都有哪些办法

package org.springframework.boot;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.support.SpringFactoriesLoader;


public interface SpringApplicationRunListener {

    /**
     * Called immediately when the run method has first started. Can be used for very
     * early initialization.
     * @param bootstrapContext the bootstrap context
     */
    // 当调用 run 办法后会立刻调用,能够用于十分晚期的初始化
    default void starting(ConfigurableBootstrapContext bootstrapContext) {starting();
    }

    /**
     * Called immediately when the run method has first started. Can be used for very
     * early initialization.
     * @deprecated since 2.4.0 in favor of {@link #starting(ConfigurableBootstrapContext)}
     */
    @Deprecated
    default void starting() {}

    /**
     * Called once the environment has been prepared, but before the
     * {@link ApplicationContext} has been created.
     * @param bootstrapContext the bootstrap context
     * @param environment the environment
     */
    // 环境筹备好之后调用
    default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
            ConfigurableEnvironment environment) {environmentPrepared(environment);
    }

    /**
     * Called once the environment has been prepared, but before the
     * {@link ApplicationContext} has been created.
     * @param environment the environment
     * @deprecated since 2.4.0 in favor of
     * {@link #environmentPrepared(ConfigurableBootstrapContext, ConfigurableEnvironment)}
     */
    @Deprecated
    default void environmentPrepared(ConfigurableEnvironment environment) { }

    /**
     * Called once the {@link ApplicationContext} has been created and prepared, but
     * before sources have been loaded.
     * @param context the application context
     */
    // 在加载资源之前,ApplicationContex 筹备好之后调用
    default void contextPrepared(ConfigurableApplicationContext context) { }

    /**
     * Called once the application context has been loaded but before it has been
     * refreshed.
     * @param context the application context
     */
    // 在加载应用程序上下文但在其刷新之前调用
    default void contextLoaded(ConfigurableApplicationContext context) { }

    /**
     * The context has been refreshed and the application has started but
     * {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
     * ApplicationRunners} have not been called.
     * @param context the application context.
     * @since 2.0.0
     */
    /**
     * 上下文曾经刷新且应用程序已启动且所有{@link CommandLineRunner commandLineRunner}
     * 和 {@link ApplicationRunner ApplicationRunners} 未调用之前调用
     */
    default void started(ConfigurableApplicationContext context) { }

    /**
     * Called immediately before the run method finishes, when the application context has
     * been refreshed and all {@link CommandLineRunner CommandLineRunners} and
     * {@link ApplicationRunner ApplicationRunners} have been called.
     * @param context the application context.
     * @since 2.0.0
     */
    /**
     * 当应用程序上下文被刷新并且所有{@link CommandLineRunner commandLineRunner}
     * 和 {@link ApplicationRunner ApplicationRunners} 都已被调用时,在 run 办法完结之前立刻调用。*/

    default void running(ConfigurableApplicationContext context) { }

    /**
     * Called when a failure occurs when running the application.
     * @param context the application context or {@code null} if a failure occurred before
     * the context was created
     * @param exception the failure
     * @since 2.0.0
     */
    // 在启动过程产生失败时调用
    default void failed(ConfigurableApplicationContext context, Throwable exception) {}}
2)筹备应用程序环境

创立并配置 SpringBooty 利用将要应用的 Environment

// 不看细节,看返回的环境数据即可
    // 创立并配置 SpringBooty 利用将要应用的 Environment
    // 过程如下://    1、创立配置环境 ConfigurableEnvironment
    //    2、加载属性文件资源
    //    3、配置监听
    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                       DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        // 依据不同的 web 类型创立不同实现的 Environment 对象
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 配置环境
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        // 发送环境已筹备实现事件
        listeners.environmentPrepared(bootstrapContext, environment);
        DefaultPropertiesPropertySource.moveToEnd(environment);
        // 依据命令行参数中 spring.profiles.active 属性配置 Environment 对象中的 activeProfile(比方 dev、prod、test)configureAdditionalProfiles(environment);
        // 绑定环境中 spring.main 属性绑定到 SpringApplication 对象中
        bindToSpringApplication(environment);
        // 如果用户应用 spring.main.web-application-type 属性手动设置了 webApplicationType
        if (!this.isCustomEnvironment) {
            // 将环境对象转换成用户设置的 webApplicationType 相干类型,他们是继承同一个父类,间接强转
            environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment; // 环境查看(控制台)}

 

查看环境

3)控制台打印 Banner
    private Banner printBanner(ConfigurableEnvironment environment) {
        // banner 模式,能够是 console、log、off
        if (this.bannerMode == Banner.Mode.OFF) {return null;}
        ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
                : new DefaultResourceLoader(null);
        SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
        // 日志打印 banner
        if (this.bannerMode == Mode.LOG) {return bannerPrinter.print(environment, this.mainApplicationClass, logger);
        }
        // 控制台打印 banner
        return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
    }

最终打印

通过 org.springframework.boot.ResourceBanner#printBanner

    @Override
    public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
        try {String banner = StreamUtils.copyToString(this.resource.getInputStream(),
                    environment.getProperty("spring.banner.charset", Charset.class, StandardCharsets.UTF_8));

            for (PropertyResolver resolver : getPropertyResolvers(environment, sourceClass)) {banner = resolver.resolvePlaceholders(banner);
            }
            out.println(banner);// 此处打印
        }
        catch (Exception ex) {logger.warn(LogMessage.format("Banner not printable: %s (%s:'%s')", this.resource, ex.getClass(),
                    ex.getMessage()), ex);
        }
    }

截图如下

4)创立利用上下文对象
    protected ConfigurableApplicationContext createApplicationContext() {return this.applicationContextFactory.create(this.webApplicationType);
    }

调用到上面

public interface ApplicationContextFactory {

    /**
     * A default {@link ApplicationContextFactory} implementation that will create an
     * appropriate context for the {@link WebApplicationType}.
     */
    // 返回一个应用程序上下文
    ApplicationContextFactory DEFAULT = (webApplicationType) -> {
        try {switch (webApplicationType) {
            case SERVLET:
                return new AnnotationConfigServletWebServerApplicationContext();
            case REACTIVE:
                return new AnnotationConfigReactiveWebServerApplicationContext();
            default:
                return new AnnotationConfigApplicationContext();}
        }
        catch (Exception ex) {
            throw new IllegalStateException("Unable create a default ApplicationContext instance,"
                    + "you may need a custom ApplicationContextFactory", ex);
        }
    };
5)筹备利用上下文

外围代码如下

    /**
     * Spring 容器筹备
     */
    private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
                                ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
                                ApplicationArguments applicationArguments, Banner printedBanner) {context.setEnvironment(environment);// 设置环境
        postProcessApplicationContext(context);// 设置上下文
        // 执行所有 ApplicationContextInitializer 对象的 initialize 办法(这些对象是通过读取 spring.factories 加载)applyInitializers(context);// 设置初始化工作(不必看)// 公布上下文筹备实现事件到所有监听器
        listeners.contextPrepared(context);// 触发监听器
        bootstrapContext.close(context);
        if (this.logStartupInfo) { // 日志操作
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        // 获取工厂 DefaultListableBeanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 注册单例对象
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            // 注册 banner 单例对象
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        if (beanFactory instanceof DefaultListableBeanFactory) {
            // 是否笼罩 bean
            ((DefaultListableBeanFactory) beanFactory)
                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        if (this.lazyInitialization) { // 是否懒加载
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        // 加载(业务类的注解须要扫描)bean 到上下文
        load(context, sources.toArray(new Object[0]));
        // 发送上下文加载实现事件
        listeners.contextLoaded(context);
    }
6)刷新应用程序上下文

ioc 容器初始化

重要!

tomcat 的启动在这里!

  // 外围办法
    private void refreshContext(ConfigurableApplicationContext context) {
        // ……
        // 开始执行启动容器(调用模板办法)refresh((ApplicationContext) context);
    }

扩大问题:

如果在 springboot 里应用了 web 容器,它是如何启动的?

refreshContext 外面,沿着 refresh – onRefresh,留神是 ServletWebServerApplicationContext 的

咱们说,在一般的 spring 里 onRefresh 是个空办法,留给子类去实现,那么,

看看这个 ServletWebServerApplicationContext 实现类它的 onRefresh 偷偷干了些啥见不得人的事?……

7)容器回调办法

空办法

    protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {}

run 办法启动后
次要做如下几件事件:

1、收回启动完结事件

2、执行实现 ApplicationRunner、CommandLineRunner 的 run 办法
3、公布应用程序已启动(ApplicationStartedEvent)事件

4、异样解决

小疑难:

boot 启动了一个 web,那么肯定有一个 DispacherServlet,它是啥时候被加载的呢???

提醒:

@EnableAutoConfiguration 注解的 spi,在 spring-boot-autoconfigure 的 spring.factories 里

EnableAutoConfiguration 的加载类里有个:DispatcherServletAutoConfiguration 做了主动拆卸

机密就藏在这货里

那主动拆卸又是什么鬼呢?除了 DS,还有各种 starter,怎么加载的呢?下节课持续……

2.2 boot 自定义 Banner

banner 主动生成工具,ascii 文字展现

http://www.network-science.de/ascii/

Spring boot 启动如下

在门路

\spring-boot-tests\spring-boot-smoke-tests\spring-boot-smoke-test-hibernate52\src\main\resources

下创立 banner.txt(留神:文件名称不能变,否则无奈加载)

banner.txt 内容如下

88b           d88             88888888ba                                  
888b         d888             88      "8b                          ,d     
88`8b       d8'88             88      ,8P                          88     
88 `8b     d8'88 8b       d8 88aaaaaa8P'  ,adPPYba,   ,adPPYba, MM88MMM  
88  `8b   d8'88 `8b     d8' 88""""""8b, a8"     "8a a8"     "8a  88     
88   `8b d8'88  `8b   d8'  88      `8b 8b       d8 8b       d8  88     
88    `888'88   `8b,d8'   88      a8P "8a,   ,a8" "8a,   ,a8"  88,    
88     `8'88     Y88'    88888888P"`"YbbdP"'`"YbbdP"' "Y888  
                      d8'd8'                                               

2.3 面试题

1、Spring Boot 的外围注解是哪个?它次要由哪几个注解组成的

启动类下面的注解是 @SpringBootApplication,它也是 Spring Boot 的外围注解,次要组合蕴含了以下 3 个注解:

  • @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的性能。
  • @EnableAutoConfiguration:关上主动配置的性能,也能够敞开某个主动配置的选项,如敞开数据源主动配置性能:@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class})。

​ 组合了

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
  • @ComponentScan:Spring 组件扫描

2、Spring Boot 主动配置原理是什么?

注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是主动配置的外围,

@EnableAutoConfiguration 给容器导入 META-INF/spring.factories 里定义的主动配置类。

筛选无效的主动配置类。

每一个主动配置类联合对应的 xxx.java 读取配置文件进行主动配置性能

3、Spring Boot 中的 starter 到底是什么 ?

首先,这个 Starter 并非什么新的技术点,基本上还是基于 Spring 已有性能来实现的。首先它提供了一个自动化配置类,个别命名为 XXXAutoConfiguration,在这个配置类中通过条件注解来决定一个配置是否失效(条件注解就是 Spring 中本来就有的),而后它还会提供一系列的默认配置,也容许开发者依据理论状况自定义相干配置,而后通过类型平安的属性注入将这些配置属性注入进来,新注入的属性会代替掉默认属性。正因为如此,很多第三方框架,咱们只须要引入依赖就能够间接应用了。当然,开发者也能够自定义 Starter

4、运行 Spring Boot 有哪几种形式?

1)打包用命令或者放到容器中运行

2)用 Maven/ Gradle 插件运行

3)间接执行 main 办法运行

如果本文对您有帮忙,欢送 关注 点赞`,您的反对是我保持创作的能源。

转载请注明出处!

正文完
 0