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

140,045

Solution 1

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

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

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

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 .

Share:
140,045
teddy teddy
Author by

teddy teddy

Updated on October 30, 2020

Comments

  • teddy teddy
    teddy teddy over 3 years

    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?

  • teddy teddy
    teddy teddy over 12 years
    ok, thanks . it's good to know that spring3 filters the events. I did notice the applicationlistener class before. but its hooks would also be called for RequestHandledEvent.
  • mjs
    mjs about 12 years
    Any idea what will happen if you use the annotation stuff and you declare two classes ? Non-annotation ( XML ) and two ? Will they fire in the order declared? thanks ;)
  • Kumar Sambhav
    Kumar Sambhav almost 11 years
    Just for info, event for context started is ContextStartedEvent Docs :- docs.spring.io/spring/docs/3.2.x/javadoc-api/org/…
  • Bogdan
    Bogdan almost 11 years
    @Kumar Sambhav: that is correct but the differences must also be mentioned. See here: stackoverflow.com/questions/5728376/… and here: forum.spring.io/forum/spring-projects/container/…
  • Ahmad Y. Saleh
    Ahmad Y. Saleh over 10 years
    from spring documentation: "As of Spring 3.0, an ApplicationListener can generically declare the event type that it is interested in. When registered with a Spring ApplicationContext, events will be filtered accordingly, with the listener getting invoked for matching event objects only." hence, you can replace the instanceof checking by implementing ApplicationListener<ContextRefreshedEvent>
  • Kalpesh Soni
    Kalpesh Soni over 6 years
    how do I print properties etc, this method seems to take no params?
  • deamon
    deamon almost 6 years
  • tgkprog
    tgkprog about 5 years
    Yes needs to be implements ApplicationListener<ContextRefreshedEvent>
  • Daud
    Daud almost 5 years
    @Component needs to be added above the class
  • Gwaptiva
    Gwaptiva over 4 years
    Simply add ContextRefreshedEvent as argument instead of annotation param