如何为应用程序上下文初始化事件添加钩子?

Posted

技术标签:

【中文标题】如何为应用程序上下文初始化事件添加钩子?【英文标题】: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,我将如何访问它们?

【问题讨论】:

【参考方案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:这是正确的,但也必须提及差异。见这里:***.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】:

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

    在下面创建注释,即

    @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 应用程序时,您的侦听器将被调用。

【讨论】:

以上是关于如何为应用程序上下文初始化事件添加钩子?的主要内容,如果未能解决你的问题,请参考以下文章

如何为 Node.js 的 require 函数添加钩子?

如何为 MFC 中的线程完成添加事件处理程序?

如何为 UITableView 索引事件添加操作 - 对于每个字母

如何为 3rd 方渲染提供空 QML 项的 OpenGL 上下文?

如何为回收站添加上下文菜单

如何为 .NET Core 3.0 Worker 服务设置事件日志