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

时间:2014-03-06 15:38:39

标签: java spring applicationcontext

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

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

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

我正在使用spring 3.1.0.RELEASE。

3 个答案:

答案 0 :(得分:4)

是的,有一种方法,但它可能有点棘手。您正在谈论的子语境可能是DispatcherServlet的上下文。如果您有多个,那么每个调度程序servlet将获得一个上下文。

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

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

来解决问题
  

load-on-startup元素表示应该加载此servlet   (在Web应用程序启动时实例化并调用其init())。这些的可选内容           element必须是一个表示顺序的整数           应该加载servlet。如果值           是负整数,或者元素不是           目前,容器可以自由加载servlet           无论什么时候选择。如果值是正数           整数或0,容器必须加载和           在应用程序初始化servlet           部署。容器必须保证           加载标有较低整数的servlet           在用更高整数标记的servlet之前。该           容器可以选择装载的顺序           具有相同加载启动值的servlet。

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

  1. 在每个servlet上指定一个load-on-startup元素,并确保其中一个具有独特的更高编号
  2. 确保您的听众抓住正确的事件
  3. 实施例

    考虑以下(简化)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接口:

    public class YourListener implements ApplicationListener<ContextRefreshedEvent>, Ordered {
    
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) { }
    
        @Override
        public int getOrder() {
            return Ordered.LOWEST_PRECEDENCE;
        } 
    }
    

答案 1 :(得分:0)

您必须更加清楚您的上下文层次结构,但如果您具有根上下文和servlet上下文的典型设置,则在servlet上下文中声明ApplicationListener bean。

ContextLoaderListener将刷新根上下文。 servlet上下文将由DispatcherServlet使用根上下文作为其父上下文进行刷新。完成此次刷新后,您的ApplicationListener将收到该活动。

答案 2 :(得分:0)

嗯,在这种情况下,我总是在我的Spring组件的公共方法上使用javax.annotation.PostConstruct注释。