java - ContextRefreshEvent 的 Spring ApplicationListener。如何每个层次结构只调用一次?

标签 java spring applicationcontext

一旦我的 spring web 应用程序启动了它的所有 bean,我就需要执行某个过程。为此,我创建了一个 ApplicationListener<ContextRefreshedEvent> .

但是,当我运行该应用程序时,它会被调用多次(因为我们有不同命名空间的上下文,例如 mvc-servlet 等)但我需要这个特定的监听器只被调用一次,并且当所有上下文都正确初始化时.

有没有办法实现我想要做的事情?

我正在使用 spring 3.1.0.RELEASE。

最佳答案

是的,有办法,但可能有点棘手。您正在谈论的子上下文可能是为 DispatcherServlet 启动的上下文。如果您有不止一个,您将获得每个调度程序 servlet 的一个上下文。

Spring 将这些委托(delegate)给容器,因此在初始化方面没有单点管理。首先,初始化根应用程序上下文,然后由容器初始化各种 servlet。对于其中的每一个,另一个上下文可能会启动。

幸运的是,servlet 规范通过 load-on-startup 参数来拯救

The load-on-startup element indicates that this servlet should be loaded (instantiated and have its init() called) on the startup of the web application. The optional contents of these element must be an integer indicating the order in which the servlet should be loaded. If the value is a negative integer, or the element is not present, the container is free to load the servlet whenever it chooses. If the value is a positive integer or 0, the container must load and initialize the servlet as the application is deployed. The container must guarantee that servlets marked with lower integers are loaded before servlets marked with higher integers. The container may choose the order of loading of servlets with the same load-on-start-up value.

所以你基本上应该做两件事:

  1. 在每个 servlet 上指定一个 load-on-startup 元素,并确保每个 servlet 都有一个独特的、更大的数字
  2. 确保您的监听器捕捉到正确的事件

例子

考虑以下(简化的)web.xml 定义

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>  
    <servlet>
        <servlet-name>anotherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/first/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>anotherServlet</servlet-name>
        <url-pattern>/second/*</url-pattern>
    </servlet-mapping>

</web-app>

检测正确的上下文

此设置将导致对监听器的 3 次调用。在这种情况下,anotherServlet 是链中的最后一个,因此您可以按如下方式识别它:

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
    ApplicationContext context = event.getApplicationContext();
    if (context instanceof ConfigurableWebApplicationContext) { // sanity check
        final ConfigurableWebApplicationContext ctx =
                (ConfigurableWebApplicationContext) event.getApplicationContext();
        if ("anotherServlet-servlet".equals(ctx.getNamespace())) {
            // Run your initialization business here
        }
    }
}

如果您有兴趣了解它的来源,请查看 FrameworkServlet#initServletBean

并不是说此时您仍然可以抛出异常,这仍然会阻止应用程序正确部署。

订购

最后,您还可以确保最后处理您的事件,以防为该特定事件注册了多个监听器。为此,您需要实现 Ordered 接口(interface):

public class YourListener implements ApplicationListener<ContextRefreshedEvent>, Ordered {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) { }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    } 
}

关于java - ContextRefreshEvent 的 Spring ApplicationListener。如何每个层次结构只调用一次?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22229077/

相关文章:

java - Java中多项式相除?

spring - Spring boot 拦截器中的应用程序属性值

java - 如何用 spring 设置正则表达式字符串而不被转义?

java - JNDI 解决了 ApplicationContext 在 Spring 中没有解决的问题?

java - Android - fragment 中 TabLayout 的中间选项卡错误

java - tomcat中的独立应用程序

java - 如何处理 Spring 包裹的 hibernate 异常?

spring - 通过 SpEL 获取 Tomcat 的 Context.xml 参数

unit-testing - Grails-在单元测试中模拟servletContext

java - 通过Java执行.sql文件