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

SpringModel View-ControllerApplicationcontext

Spring Problem Overview


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?

Spring Solutions


Solution 1 - Spring

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

Solution 2 - Spring

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

Solution 3 - Spring

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

Solution 4 - Spring

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 .

Solution 5 - Spring

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionteddy teddyView Question on Stackoverflow
Solution 1 - SpringBogdanView Answer on Stackoverflow
Solution 2 - SpringDavid GroomesView Answer on Stackoverflow
Solution 3 - SpringAdeptiusView Answer on Stackoverflow
Solution 4 - SpringAmit SinghView Answer on Stackoverflow
Solution 5 - SpringDilipView Answer on Stackoverflow