springboot源码解析-自定义系统初始化器

Posted 猿起缘灭

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot源码解析-自定义系统初始化器相关的知识,希望对你有一定的参考价值。

开篇之前先把祖师爷搬出来
  费玉清:问大家一个脑筋急转弯,说西方人在浴缸中洗澡,打一种小吃,小吃街里很常见的那种
      思考。。。
      思考。。。
      思考。。。
  揭晓谜底:涮羊肉
  反正谜底我已经揭晓了,至于大家能不能看到,我就不管了,哈哈

概述

  本系列主要分析springboot启动过程中干了什么事情,尽量以白话的形式写出来,因为本人也很小白,望包涵。

  系统初始化器是springboot在容器刷新之前执行的一个回调函数,其主要的作用就是向容器中注册属性,平时我们可能不会用到吧,但是在spring框架内部这个系统初始化器使用非常多,大家就当看看大佬是如何做的初始化,以及可以想一下他们为什么这样做,或许以后自己写程序也可以学习这种思想。

 

实现方式

  通过实现ApplicationContextInitializer接口来定义系统初始化器,这个接口是一个函数式接口,就是接口中只有一个方法,是java8的新特性。下面来看一下这个接口的定义。

/**
 * Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
 * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
 *
 * <p>Typically used within web applications that require some programmatic initialization
 * of the application context. For example, registering property sources or activating
 * profiles against the {@linkplain ConfigurableApplicationContext#getEnvironment()
 * context\'s environment}. See {@code ContextLoader} and {@code FrameworkServlet} support
 * for declaring a "contextInitializerClasses" context-param and init-param, respectively.
 *
 * <p>{@code ApplicationContextInitializer} processors are encouraged to detect
 * whether Spring\'s {@link org.springframework.core.Ordered Ordered} interface has been
 * implemented or if the @{@link org.springframework.core.annotation.Order Order}
 * annotation is present and to sort instances accordingly if so prior to invocation.
 *
 * @author Chris Beams
 * @since 3.1
 * @param <C> the application context type
 * @see org.springframework.web.context.ContextLoader#customizeContext
 * @see org.springframework.web.context.ContextLoader#CONTEXT_INITIALIZER_CLASSES_PARAM
 * @see org.springframework.web.servlet.FrameworkServlet#setContextInitializerClasses
 * @see org.springframework.web.servlet.FrameworkServlet#applyInitializers
 */
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

    /**
     * Initialize the given application context.
     * @param applicationContext the application to configure
     */
    void initialize(C applicationContext);

}

大家可以看一下上面的解释,主要分为三段,总结下来就是:

  1. 这个接口的方法是在ConfigurableApplicationContext上下文中refresh()方法执行之前执行的一个回调
  2. 通常用在一些需要为应用上下文初始化的web应用中,比如,向容器中注册属性和激活配置
  3. 实现类可以使用@Order注解修饰,在调用之前可以对实例进行排序(注:@Order可以决定bean的执行顺序,值越小优先级越高)

上面对应用初始化器做了一个简单的介绍,并且看了应用初始化器的接口定义,下面就使用具体的例子来实战,自定义一些初始化,验证一下,这个先提前说一下,应用初始化有3种注入方式,具体看下面的例子。

第一种方式:使用META-INF/spring.factories

1.自定义系统初始化器

@Order(1)
public class FirstApplicationContextInitializer implements ApplicationContextInitializer<GenericApplicationContext> {

    @Override
    public void initialize(GenericApplicationContext context) {
        ConfigurableEnvironment environment = context.getEnvironment();
        Map<String,String> map = new HashMap<>();
        map.put("first","hello first");
        environment.getSystemProperties().putAll(map);
        System.out.println("firstApplicationContextInitializer is start");
    }
}

解释一下上面代码:这个代码很简单,就是向系统环境中添加一个新的属性,属性的key是first,value是hello first,然后当这个系统初始化器被执行的时候会打印firstApplicationContextInitializer is start

 

2.在resource目录下新建META-INF目录,之后在META-INF目录下新建文件spring.factories,在文件中添加如下代码

org.springframework.context.ApplicationContextInitializer=com.example.demo.initialize.FirstApplicationContextInitializer

解释:等号前面是系统初始化器接口的路径,这个不要改,等号后面是自定义的具体实现类,路径要写自己程序的这个类所在的路径

 

3.写一个controller,然后后去系统的环境,看看能不能后去到在系统初始化器中添加的first属性

@RestController
public class InitializeController {

    @Autowired
    ApplicationContext applicationContext;

    @GetMapping("/first")
    public String test(){
        String a = applicationContext.getEnvironment().getProperty("first");
        return a;
    }

    @GetMapping("/second")
    public String test1(){
        String a = applicationContext.getEnvironment().getProperty("second");
        return a;
    }

    @GetMapping("/third")
    public String test2(){
        String a = applicationContext.getEnvironment().getProperty("third");
        return a;
    }

}

ok,做完以上3部,就可以启动springboot程序了,下面是我的启动程序时的打印结果。

 .   ____          _            __ _ _
 /\\\\ / ___\'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\
( ( )\\___ | \'_ | \'_| | \'_ \\/ _` | \\ \\ \\ \\
 \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  \'  |____| .__|_| |_|_| |_\\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.6.RELEASE)
firstApplicationContextInitializer is start
2020-05-29 10:20:52.989  INFO 1576 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication on ganxinledeMacBook-Pro.local with PID 1576 (/Users/ganxinle/workspace/demo/target/classes started by ganxinle in /Users/ganxinle/workspace/demo)
2020-05-29 10:20:52.991  INFO 1576 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
2020-05-29 10:20:53.686  INFO 1576 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-05-29 10:20:53.696  INFO 1576 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-05-29 10:20:53.696  INFO 1576 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.33]
2020-05-29 10:20:53.749  INFO 1576 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-05-29 10:20:53.749  INFO 1576 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 721 ms
2020-05-29 10:20:53.860  INFO 1576 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService \'applicationTaskExecutor\'
2020-05-29 10:20:53.979  INFO 1576 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path \'\'
2020-05-29 10:20:53.982  INFO 1576 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 1.258 seconds (JVM running for 1.577)
2020-05-29 10:21:12.351  INFO 1576 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet \'dispatcherServlet\'
2020-05-29 10:21:12.351  INFO 1576 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet \'dispatcherServlet\'
2020-05-29 10:21:12.354  INFO 1576 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 3 ms

大家可以看到我标红的地方,就是打印的结果,下面在浏览器中调用http://localhost:8080/first

可以看到已经获取到first属性的值。

总结:第一种实现方式已经介绍完毕,大家看过之后肯定有疑问,为什么要在resource目录下搞一个META-INF,还新建一个spring.factories,为什么在spring.factories中要那样配置,憋着机,等我介绍完3中方式之后,会把源码扒拉出来,让大家看一下具体的原因,没兴趣看源码的,到时候可以不用看那部分,哈哈

以上是关于springboot源码解析-自定义系统初始化器的主要内容,如果未能解决你的问题,请参考以下文章

springboot源码解析-从源码角度分析系统初始化器的实现原理

springboot源码解析-从源码角度分析系统初始化器的实现原理

springboot 源码解析## 如何自定义 starter 让 springboot 扫描到你的配置

一起学习springboot源码(系统初始化器)

源码分析|SpringBoot启动流程

Springboot自定义xml文件解析