前言
我事前百度了一下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让并且它生效的方式有两种:
- 手动调用他们的setXXX方法添加进去
- 通过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接口实现类。
分别看看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'