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

问题描述 投票:0回答:6

对于常规 Servlet,我想您可以声明一个 context Listener,但是对于 Spring MVC Spring 会让这变得更容易吗?

此外,如果我定义了一个上下文侦听器,然后需要访问我的

servlet.xml
applicationContext.xml
中定义的bean,我将如何访问它们?

spring model-view-controller applicationcontext
6个回答
115
投票

Spring 有一些您可以处理的标准事件。

为此,您必须创建并注册一个实现

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.xml
applicationContext.xml
文件中注册该 bean:

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

当应用程序上下文初始化时,Spring 会通知它。

在 Spring 3 中(如果您使用的是这个版本),

ApplicationListener
类是通用的,您可以声明您感兴趣的事件类型,并且事件将被相应地过滤。你可以像这样简化你的 bean 代码:

public class ApplicationListenerBean implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        // now you can do applicationContext.getBean(...)
        // ...
    }
}

99
投票

从 Spring 4.2 开始,您可以使用

@EventListener
文档

@Component
class MyClassWithEventListeners {

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

6
投票

创建您的注释

  @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() {}

0
投票

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

  1. 创建以下注释,即

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

2.创建下面的类,它是一个侦听器,在应用程序就绪状态时调用。

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


0
投票

我有一个单页应用程序,在输入 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 中获取结果。


0
投票

您的 bean 还可以实现 SmartInitializingSingleton.afterSingletonsInstantiated()。这是 Spring 文档 (v 6.0.12) 中列出的可能性之一: https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html#beans-factory-lifecycle-initializingbean

请注意,@PostConstruct 和初始化方法通常是在容器的单例创建锁内执行的。 bean 实例仅在从 @PostConstruct 方法返回后才被视为已完全初始化并准备好发布给其他人。这些单独的初始化方法仅用于验证配置状态并可能根据给定配置准备一些数据结构,但不涉及外部 bean 访问的进一步活动。否则存在初始化死锁的风险。

对于要触发昂贵的初始化后活动的场景,例如异步数据库准备步骤,您的 bean 应该实现 SmartInitializingSingleton.afterSingletonsInstantiated() 或依赖上下文刷新事件:实现 ApplicationListener 或声明其注释等效 @EventListener(ContextRefreshedEvent.class)。这些变体出现在所有常规单例初始化之后,因此位于任何单例创建锁之外。

或者,您可以实现(智能)Lifecycle 接口并与容器的整体生命周期管理集成,包括自动启动机制、预销毁停止步骤和潜在的停止/重新启动回调(见下文)。

© www.soinside.com 2019 - 2024. All rights reserved.