【问题标题】:How to add a hook to the application context initialization event?如何为应用程序上下文初始化事件添加钩子?
【发布时间】:2012-01-30 23:19:54
【问题描述】:

对于一个普通的 Servlet,我想你可以声明一个 context listener,但是对于 Spring MVC,Spring 会让这更容易吗?

此外,如果我定义了一个上下文侦听器,然后需要访问我的 servlet.xmlapplicationContext.xml 中定义的 bean,我将如何访问它们?

【问题讨论】:

    标签: spring model-view-controller applicationcontext


    【解决方案1】:

    Spring has some standard events which you can handle.

    为此,您必须创建并注册一个实现ApplicationListener 接口的bean,如下所示:

    package test.pack.age;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationEvent;
    import org.springframework.context.ApplicationListener;
    import org.springframework.context.event.ContextRefreshedEvent;
    
    public class ApplicationListenerBean implements ApplicationListener {
    
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            if (event instanceof ContextRefreshedEvent) {
                ApplicationContext applicationContext = ((ContextRefreshedEvent) event).getApplicationContext();
                // now you can do applicationContext.getBean(...)
                // ...
            }
        }
    }
    

    然后您在 servlet.xmlapplicationContext.xml 文件中注册此 bean:

    <bean id="eventListenerBean" class="test.pack.age.ApplicationListenerBean" />
    

    Spring 会在应用上下文初始化时通知它。

    在 Spring 3 中(如果您使用此版本),ApplicationListener class is generic 和您可以声明您感兴趣的事件类型,并且该事件将被相应地过滤。您可以像这样简化您的 bean 代码:

    public class ApplicationListenerBean implements ApplicationListener<ContextRefreshedEvent> {
    
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            ApplicationContext applicationContext = event.getApplicationContext();
            // now you can do applicationContext.getBean(...)
            // ...
        }
    }
    

    【讨论】:

    • 好的,谢谢。很高兴知道 spring3 过滤事件。我之前确实注意到了 applicationlistener 类。但它的钩子也会被 RequestHandledEvent 调用。
    • 知道如果你使用注解的东西并声明两个类会发生什么吗?非注释(XML)和两个?他们会按照宣布的顺序开火吗?谢谢;)
    • 仅供参考,上下文启动的事件是 ContextStartedEvent Docs :- docs.spring.io/spring/docs/3.2.x/javadoc-api/org/…
    • @Kumar Sambhav:这是正确的,但也必须提及差异。见这里:stackoverflow.com/questions/5728376/… 和这里:forum.spring.io/forum/spring-projects/container/…
    • 来自spring documentation: "从 Spring 3.0 开始,ApplicationListener 可以通用地声明它感兴趣的事件类型。当使用 Spring ApplicationContext 注册时,事件将被相应地过滤,使用只有匹配事件对象才会调用监听器。” 因此,您可以通过实现 ApplicationListener&lt;ContextRefreshedEvent&gt; 来替换 instanceof 检查
    【解决方案2】:

    从 Spring 4.2 开始,您可以使用 @EventListener (documentation)

    @Component
    class MyClassWithEventListeners {
    
        @EventListener({ContextRefreshedEvent.class})
        void contextRefreshedEvent() {
            System.out.println("a context refreshed event happened");
        }
    }
    

    【讨论】:

    • 如何打印属性等,这个方法好像没有参数?
    • 只需添加 ContextRefreshedEvent 作为参数而不是注释参数
    【解决方案3】:

    创建注释

      @Retention(RetentionPolicy.RUNTIME)
        public @interface AfterSpringLoadComplete {
        }
    

    创建类

        public class PostProxyInvokerContextListener implements ApplicationListener<ContextRefreshedEvent> {
    
        @Autowired
        ConfigurableListableBeanFactory factory;
    
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            ApplicationContext context = event.getApplicationContext();
            String[] names = context.getBeanDefinitionNames();
            for (String name : names) {
                try {
                    BeanDefinition definition = factory.getBeanDefinition(name);
                    String originalClassName = definition.getBeanClassName();
                    Class<?> originalClass = Class.forName(originalClassName);
                    Method[] methods = originalClass.getMethods();
                    for (Method method : methods) {
                        if (method.isAnnotationPresent(AfterSpringLoadComplete.class)){
                            Object bean = context.getBean(name);
                            Method currentMethod = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
                            currentMethod.invoke(bean);
                        }
                    }
                } catch (Exception ignored) {
                }
            }
        }
    }
    

    通过@Component注解或者在xml中注册这个类

    <bean class="ua.adeptius.PostProxyInvokerContextListener"/>
    

    并在您想要在上下文初始化后运行的任何方法上使用注释,例如:

       @AfterSpringLoadComplete
        public void init() {}
    

    【讨论】:

      【解决方案4】:

      我在输入 URL 时有一个单页应用程序,它正在创建一个包含来自多个数据库的数据的 HashMap(由我的网页使用)。 我做了以下事情以在服务器启动期间加载所有内容-

      1- 创建 ContextListenerClass

      public class MyAppContextListener implements ServletContextListener
          @Autowired
      
          private  MyDataProviderBean myDataProviderBean; 
      
          public MyDataProviderBean getMyDataProviderBean() {
      
              return MyDataProviderBean;
      
          }
      
          public void setMyDataProviderBean(MyDataProviderBean MyDataProviderBean) {
      
              this.myDataProviderBean = MyDataProviderBean;
      
          }
      
          @Override
          public void contextDestroyed(ServletContextEvent arg0) {
      
              System.out.println("ServletContextListener destroyed");
      
          }
      
      
          @Override
      
          public void contextInitialized(ServletContextEvent context) {
      
              System.out.println("ServletContextListener started");
      
              ServletContext sc = context.getServletContext();
      
              WebApplicationContext springContext = WebApplicationContextUtils.getWebApplicationContext(sc);
      
              MyDataProviderBean MyDataProviderBean = (MyDataProviderBean)springContext.getBean("myDataProviderBean");
      
              Map<String, Object> myDataMap = MyDataProviderBean.getDataMap();
      
              sc.setAttribute("myMap", myDataMap);
      
          }
      

      2- 在 web.xml 中添加以下条目

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

      3- 在我的控制器类中更新代码以首先检查 servletContext 中的 Map

          @RequestMapping(value = "/index", method = RequestMethod.GET)
              public String index(@ModelAttribute("model") ModelMap model) {
      
                  Map<String, Object> myDataMap = new HashMap<String, Object>();
                  if (context != null && context.getAttribute("myMap")!=null)
                  {
      
                      myDataMap=(Map<String, Object>)context.getAttribute("myMap");
                  }
      
                  else
                  {
      
                      myDataMap = myDataProviderBean.getDataMap();
                  }
      
                  for (String key : myDataMap.keySet())
                  {
                      model.addAttribute(key, myDataMap.get(key));
                  }
                  return "myWebPage";
      
              }
      

      当我启动我的 tomcat 时发生了这么大的变化,它会在 startTime 期间加载 dataMap 并将所有内容放在 servletContext 中,然后控制器类使用它从已经填充的 servletContext 中获取结果。

      【讨论】:

        【解决方案5】:

        请在应用程序上下文加载后按照以下步骤进行一些处理,即应用程序准备好服务。

        1. 在下面创建注释,即

          @Retention(RetentionPolicy.RUNTIME) @Target(value= {ElementType.METHOD, ElementType.TYPE}) 公共@interface AfterApplicationReady {}

        2.创建Below Class,它是一个监听器,在应用程序就绪状态时调用。

            @Component
            public class PostApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {
        
            public static final Logger LOGGER = LoggerFactory.getLogger(PostApplicationReadyListener.class);
            public static final String MODULE = PostApplicationReadyListener.class.getSimpleName();
        
            @Override
            public void onApplicationEvent(ApplicationReadyEvent event) {
                try {
                    ApplicationContext context = event.getApplicationContext();
                    String[] beans = context.getBeanNamesForAnnotation(AfterAppStarted.class);
        
                    LOGGER.info("bean found with AfterAppStarted annotation are : {}", Arrays.toString(beans));
        
                    for (String beanName : beans) {
                        Object bean = context.getBean(beanName);
                        Class<?> targetClass = AopUtils.getTargetClass(bean);
                        Method[] methods = targetClass.getMethods();
                        for (Method method : methods) {
                            if (method.isAnnotationPresent(AfterAppStartedComplete.class)) {
        
                                LOGGER.info("Method:[{} of Bean:{}] found with AfterAppStartedComplete Annotation.", method.getName(), beanName);
        
                                Method currentMethod = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
        
                                LOGGER.info("Going to invoke method:{} of bean:{}", method.getName(), beanName);
        
                                currentMethod.invoke(bean);
        
                                LOGGER.info("Invocation compeleted method:{} of bean:{}", method.getName(), beanName);
                            }
                        }
                    }
                } catch (Exception e) {
                    LOGGER.warn("Exception occured : ", e);
                }
            }
        }
        

        最后,当您在日志声明应用程序启动之前启动 Spring 应用程序时,您的侦听器将被调用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-08-11
          • 1970-01-01
          • 1970-01-01
          • 2010-09-23
          • 2013-11-18
          • 1970-01-01
          • 2012-01-19
          相关资源
          最近更新 更多