详解Spring Framework提供的扩展点:ApplicationContextInitializer应用上下文初始化器,以及它在SpringBoot中的应用【享学Spring】(上)

简介: 详解Spring Framework提供的扩展点:ApplicationContextInitializer应用上下文初始化器,以及它在SpringBoot中的应用【享学Spring】(上)

前言


我事前百度了一下ApplicationContextInitializer的相关文章,无一例外全都是基于SpringBoot进行讲解的。

殊不知,这个类属于Spring Framework的而并非属于SpringBoot,so我认为开门见山就在SpringBoot里讲解它是欠缺妥当的。毕竟想要理解好SpringBoot,先了解Spring Framework才是第一要素


所以本文叙述的ApplicationContextInitializer完全是在Spring环境下的,和SpringBoot并无关联,希望它会成为一股清流,哈哈_


ApplicationContextInitializer


先啥都不说,先看看Spring的官方javadoc怎么解释此类:用于在刷新容器之前初始化Spring的回调接口。


任何一个SPI,它的执行时机特别特别的重要,所以这点必须重视


ApplicationContextInitializer是Spring框架原有的概念, 这个类的主要目的就是在 ConfigurableApplicationContext类型(或者子类型)的ApplicationContext进行刷新refresh之前,允许我们对ConfigurableApplicationContext的实例做进一步的设置或者处理。


通常用于需要对应用程序进行某些初始化工作的web程序中。例如利用Environment上下文环境注册属性源、激活配置文件等等。

另外它支持Ordered和@Order方式排序执行~


// @since 3.1
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
  // Initialize the given application context.
  void initialize(C applicationContext);
}


此接口,Spring Framework自己没有提供任何的实现类。但小伙伴们可能都知道SpringBoot对它有较多的扩展实现。


本文因为纯在Spring Framework环境下,所以主要讲解它在org.springframework.web.context.ContextLoader以及org.springframework.web.servlet.FrameworkServlet中的应用。最后我会示例怎么样通过自定义的方式实现容器刷新前的自定义行为。


在ContextLoader中的应用


在我之前讲解ContextLoader之前,在之前博文:基于注解驱动的相关文章中,有重点讲述过此类。参考:

【小家Spring】Spring容器(含父子容器)的启动过程源码级别分析(含web.xml启动以及全注解驱动,和ContextLoader源码分析)


本文的关注点,自然只是ContextLoader里对ApplicationContextInitializer的处理:


public class ContextLoader {
  ...
  // 它是个list,也就是说可以向此容器注册N上下文初始化器
  private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers = new ArrayList<>();
  // 为啥不用addAll?  因为此处泛型需要强转~
  public void setContextInitializers(@Nullable ApplicationContextInitializer<?>... initializers) {
    if (initializers != null) {
      for (ApplicationContextInitializer<?> initializer : initializers) {
        this.contextInitializers.add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer);
      }
    }
  }
  // ======================它的执行时机如下;======================
  public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    ...
    configureAndRefreshWebApplicationContext(cwac, servletContext);
    ...
  }
  protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    ...
    customizeContext(sc, wac);
    wac.refresh();
    ...
  }
  // 由此可见所有的ApplicationContextInitializer的执行,都发生在wac.refresh();之前
  protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
    //这里是从ServletContext 读取配置
    //因为我们可以这么配置的globalInitializerClasses:ApplicationContextInitializer实现类的全类名(可以逗号分隔配置多个)
    // 或者key是它:contextInitializerClasses
    // 拿到配置的这些全类名后,下面都要反射创建对象的~~~ 
    List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(sc);
    for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
      Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
      if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
        throw new ApplicationContextException(String.format(
            "Could not apply context initializer [%s] since its generic parameter [%s] " +
            "is not assignable from the type of application context used by this " +
            "context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),
            wac.getClass().getName()));
      }
      // 创建好实例对象后,添加进全局的list里面(默认使用空构造函数)
      this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
    }
    // 采用order排序后,再分别执行~~~~
    AnnotationAwareOrderComparator.sort(this.contextInitializers);
    for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
      initializer.initialize(wac);
    }
  }
  ...
}


可以看到ContextLoader中对ApplicationContextInitializer的使用还是非常简单。它所做无非就是把ServletContext上下配置 + 本类自己配置的全部拿出来,在容器刷新之前执行而已。


在FrameworkServlet中的应用

这个是Spring MVC的核心API,被称为前端控制器。它会负责启动web子容器~

同样在这期间,它也会处理ApplicationContextInitializer:


public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
  private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers = new ArrayList<>();
  // 添加的代码和上面一模一样~
  public void setContextInitializers(@Nullable ApplicationContextInitializer<?>... initializers) {
    if (initializers != null) {
      for (ApplicationContextInitializer<?> initializer : initializers) {
        this.contextInitializers.add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer);
      }
    }
  }
  // 执行时机
  @Override
  protected final void initServletBean() throws ServletException {
    ...
      this.webApplicationContext = initWebApplicationContext();
      initFrameworkServlet();
    ...
  }
  protected WebApplicationContext initWebApplicationContext() {
    ...
    configureAndRefreshWebApplicationContext(cwac);
    ...
  }
  protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    ...
    postProcessWebApplicationContext(wac);
    applyInitializers(wac);
    wac.refresh();  
  }
  protected void applyInitializers(ConfigurableApplicationContext wac) {
    // 此处可以看到,只会拿globalInitializerClasses这个key配置的了~~~~ 只有全局的此处才会继续有用
    String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
    if (globalClassNames != null) {
      for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
        this.contextInitializers.add(loadInitializer(className, wac));
      }
    }
    // 本Servlet里不仅仅可以直接set进来,也可以contextInitializerClasses直接配置全类名
    if (this.contextInitializerClasses != null) {
      for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
        this.contextInitializers.add(loadInitializer(className, wac));
      }
    }
    // 最终排序、执行~~~
    AnnotationAwareOrderComparator.sort(this.contextInitializers);
    for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
      initializer.initialize(wac);
    }
  }
}


整体的执行流程,几乎和ContextLoader的一模一样,没什么太值得说的。


如何自定义一个自己的ApplicationContextInitializer呢?


我们已经知道Spring内部并没有提供任何一个ApplicationContextInitializer的实现,

很显然这像是Spirng提供的一个SPI钩子接口,具体实现我们自己去定制接口。


上面说的都是ApplicationContextInitializer的它应用,它的执行。本处我们更应该关心的是:它是何时、怎么被注册进去的呢???


从上面源码分析也可以看出,在Spring环境下,我们自定义实现一个ApplicationContextInitializer让并且它生效的方式有两种:


  1. 手动调用他们的setXXX方法添加进去
  2. 通过ServletContext初始化参数放进去(其实如果是web.xml时代是配置即可)


其实此处有个小细节:此接口在Spring3.1开始提供的,所以很容易联想到它可以不依赖于web.xml配置方式,使用全注解驱动的方式也是可行的


借助WebApplicationInitializer方式自定义实现


我们的需求:需要在容器启动之前注册我们自己的ApplicationContextInitializer。


我们知道Servlet3.0规范中提供了一个SPI来启动Spring容器,Spring对它进行了实现:

// @since 3.1
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
  ...
}

Servlet容器启动时,它会加载进来所有的WebApplicationInitializer接口实现类。


image.png


分别看看AbstractContextLoaderInitializer和AbstractDispatcherServletInitializer它哥俩恰好就够提供了可扩展的方法:


public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
  // 它最终会被本类的此处调用:listener.setContextInitializers(getRootApplicationContextInitializers());
  // 我们知道ContextLoaderListener所有事情都是委托给了ContextLoader类去完成的~
  @Nullable
  protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
    return null;
  }
}
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
  // 它最终会本类的此处调用dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
  @Nullable
  protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
    return null;
  }
}

由此可见,我们只需要在自己写的WebApplicationInitializer实现里,复写上述两个方法,即能达到自定义应用上下文初始化器的目的。


Demo Show


先写一个初始化器ApplicationContextInitializer实现类:


@Order(10)
public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 该方法此处不能用 因为还没初始化会报错:call 'refresh' before accessing beans via the ApplicationContext
        //int beanDefinitionCount = applicationContext.getBeanDefinitionCount();
        System.out.println(applicationContext.getApplicationName() + ":" + applicationContext.getDisplayName());
    }
}


再复写WebApplicationInitializer实现类的相关方法:把我们自定义的初始化器return


/**
 * 自己实现 基于注解驱动的ServletInitializer来初始化DispatcherServlet
 */
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
        return new ApplicationContextInitializer[]{new MyApplicationContextInitializer()};
    }
    @Override
    protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
        return new ApplicationContextInitializer[]{new MyApplicationContextInitializer()};
    }
    /**
     * 根容器的配置类;(Spring的配置文件)   父容器;
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{RootConfig.class, JdbcConfig.class, AsyncConfig.class, ScheduldConfig.class};
    }
    /**
     * web容器的配置类(SpringMVC配置文件)  子容器;
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{WebMvcConfig.class};
    }
    ...
}


启动容器,能看到输入日志如下:(生效了)


/demo_war_war:Root WebApplicationContext
/demo_war_war:WebApplicationContext for namespace 'dispatcher-servlet'
相关文章
|
2月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
379 3
|
26天前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
探索Spring Boot的@Conditional注解的上下文配置
|
2月前
|
人工智能 监控 安全
如何快速上手【Spring AOP】?核心应用实战(上篇)
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。 AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。
|
2月前
|
安全 算法 Java
在Spring Boot中应用Jasypt以加密配置信息。
通过以上步骤,可以在Spring Boot应用中有效地利用Jasypt对配置信息进行加密,这样即使配置文件被泄露,其中的敏感信息也不会直接暴露给攻击者。这是一种在不牺牲操作复杂度的情况下提升应用安全性的简便方法。
718 10
|
3月前
|
安全 Java Nacos
0代码改动实现Spring应用数据库帐密自动轮转
Nacos作为国内被广泛使用的配置中心,已经成为应用侧的基础设施产品,近年来安全问题被更多关注,这是中国国内软件行业逐渐迈向成熟的标志,也是必经之路,Nacos提供配置加密存储-运行时轮转的核心安全能力,将在应用安全领域承担更多职责。
|
3月前
|
NoSQL Java Redis
Redis基本数据类型及Spring Data Redis应用
Redis 是开源高性能键值对数据库,支持 String、Hash、List、Set、Sorted Set 等数据结构,适用于缓存、消息队列、排行榜等场景。具备高性能、原子操作及丰富功能,是分布式系统核心组件。
398 2
|
3月前
|
Java Linux 网络安全
Linux云端服务器上部署Spring Boot应用的教程。
此流程涉及Linux命令行操作、系统服务管理及网络安全知识,需要管理员权限以进行配置和服务管理。务必在一个测试环境中验证所有步骤,确保一切配置正确无误后,再将应用部署到生产环境中。也可以使用如Ansible、Chef等配置管理工具来自动化部署过程,提升效率和可靠性。
353 13
|
4月前
|
Java 数据库 开发者
Spring Boot 框架超级详细总结及长尾关键词应用解析
本文深入讲解Spring Boot框架的核心概念、功能特性及实际应用,涵盖自动配置、独立运行、starter依赖等优势。通过Web开发、微服务架构、批处理等适用场景分析,结合在线书店实战案例,演示项目初始化、数据库设计、分层架构实现全流程。同时探讨热部署、多环境配置、缓存机制与事务管理等高级特性,助你高效掌握Spring Boot开发技巧。代码示例详尽,适合从入门到进阶的学习者。
1255 0
|
Java 应用服务中间件 Maven
传统maven项目和现在spring boot项目的区别
Spring Boot:传统 Web 项目与采用 Spring Boot 项目区别
658 0
传统maven项目和现在spring boot项目的区别
|
XML Java 数据库连接
创建springboot项目的基本流程——以宠物类别为例
创建springboot项目的基本流程——以宠物类别为例
214 0
创建springboot项目的基本流程——以宠物类别为例