ApplicationContextInitializer在Spring容器执行refresh之前执行

发布时间 2023-12-10 08:34:08作者: 残城碎梦

ApplicationContextInitializer用于在刷新Spring容器之前的回调接口。

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自己没有提供任何的实现类。

本文主要讲解它在org.springframework.web.context.ContextLoader以及org.springframework.web.servlet.FrameworkServlet中的应用。

在ContextLoader中的执行时机

源码-来自:ContextLoaderListener

源码-来自:ContextLoader

在FrameworkServlet中的执行时机

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

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

源码-来自:HttpServletBean

源码-来自:FrameworkServlet

先获取WebApplicationContext,如果已存在,则直接调用configureAndRefreshWebApplicationContext进行刷新,否则需要就创建。

如何自定义一个ApplicationContextInitializer

我们已经知道Spring内部并没有提供任何一个ApplicationContextInitializer的实现,具体实现我们自己去定制接口。

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

借助WebApplicationInitializer方式自定义实现

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

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

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

WebApplicationInitializer的继承关系:

源码-来自:AbstractDispatcherServletInitializer

1)序号①调用了父类AbstractContextLoaderInitializer的方法创建spring容器

2)需要②调用了自身的方法创建springmvc容器,并实例化了FrameworkServlet

所以我们使用全注解方法开发Spring应用的时候,在继承AbstractAnnotationConfigDispatcherServletInitializer的同时,复写getRootApplicationContextInitializers和getServletApplicationContextInitializers这两个方法即可。

先写一个初始化器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};
    }

    /**
     * web容器的配置类(SpringMVC配置文件)  子容器;
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{WebMvcConfig.class};
    }
    //...
}

通过ServletContext初始化参数放进去

在分析ContextLoader中的执行时机的时候,我们看到有这么一行代码

public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";

public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";

protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> 
        determineContextInitializerClasses(ServletContext servletContext) {

    List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
        new ArrayList<>();

    String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
    if (globalClassNames != null) {
        for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
            classes.add(loadInitializerClass(className));
        }
    }

    String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
    if (localClassNames != null) {
        for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
            classes.add(loadInitializerClass(className));
        }
    }

    return classes;
}

这里是通过ServletContext初始化参数放进去的,在web.xml时代,我们只需要在web.xml中配置即可。

第一种:使用globalInitializerClasses属性来配置,在web.xml文件中的配置如下:

<context-param>
    <param-name>globalInitializerClasses</param-name>
    <param-value>com.harvey.MyApplicationContextInitializer</param-value> <!--多个用逗号或者分号分隔-->
</context-param>

第二种:配置在DispatcherServlet的初始变量中:

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:spring-mvc.xml</param-value>
    </init-param>
    <!-- 配置初始化变量,将会调用set方法set进去 -->
    <init-param>
        <param-name>contextInitializerClasses</param-name>
        <param-value>com.harvey.MyApplicationContextInitializer</param-value>
    </init-param>
</servlet>