How to add a hook to the application context initialization event?

For a regular Servlet, I guess you could declare a context listener, but for Spring MVC would Spring make this any easier?

Furthermore, if I define a context listener and then would need to access the beans defined in my servlet.xml or applicationContext.xml, how would I get access to them?

146675 次浏览

Spring has some standard events which you can handle.

To do that, you must create and register a bean that implements the ApplicationListener interface, something like this:

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(...)
// ...
}
}
}

You then register this bean within your servlet.xml or applicationContext.xml file:

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

and Spring will notify it when the application context is initialized.

In Spring 3 (if you are using this version), the ApplicationListener class is generic and you can declare the event type that you are interested in, and the event will be filtered accordingly. You can simplify a bit your bean code like this:

public class ApplicationListenerBean implements ApplicationListener<ContextRefreshedEvent> {


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

Since Spring 4.2 you can use @EventListener (documentation)

@Component
class MyClassWithEventListeners {


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

I had a single page application on entering URL it was creating a HashMap (used by my webpage) which contained data from multiple databases. I did following things to load everything during server start time-

1- Created 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- Added below entry in web.xml

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

3- In my Controller Class updated code to first check for Map in servletContext

    @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";


}

With this much change when I start my tomcat it loads dataMap during startTime and puts everything in servletContext which is then used by Controller Class to get results from already populated servletContext .

Create your annotation

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

Create class

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

Register this class by @Component annotation or in xml

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

and use annotation where you wan on any method that you want to run after context initialized, like:

   @AfterSpringLoadComplete
public void init() {}

Please follow below step to do some processing after Application Context get loaded i.e application is ready to serve.

  1. Create below annotation i.e

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

2.Create Below Class which is a listener which get call on application ready state.

    @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);
}
}
}

Finally when you start your Spring application just before log stating application started your listener will be called.