如何为应用程序上下文初始化事件添加钩子?
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.xml
或 applicationContext.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.xml
或 applicationContext.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<ContextRefreshedEvent>
来替换 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 应用程序时,您的侦听器将被调用。
【讨论】:
以上是关于如何为应用程序上下文初始化事件添加钩子?的主要内容,如果未能解决你的问题,请参考以下文章
如何为 UITableView 索引事件添加操作 - 对于每个字母