2

Several months ago, I made a simple application just to run every night to send emails to my users, if certain criteria in database are found. I used Spring 3.1.0 and Quartz 1.8.5 to accomplish this; so far, so good.

The app is deployed in a weblogic server.

Last week, I was asked to modify the app to allow it to be manually commanded to run through an URL.

I added a servlet to it and it worked fine: everytime someone invokes the URL, the app runs.

But one collateral effect appeared - each servlet invocation also provokes the reload of spring context and another instance of Quartz is created. So, if said servlet is called n times, my app will send n+1 repeated emails when Quartz fires my job, instead of only 1.

I would like to understand why this is happening and how to fix this. Could you help me, please?

The following are the relevant files to my question.

Log file excerpt, comprising the app's starting up:

28/11/2012 16:37:48 org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization started
28/11/2012 16:37:48 org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing Root WebApplicationContext: startup date [Wed Nov 28 16:37:48 BRST 2012]; root of context hierarchy
28/11/2012 16:37:48 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from URL [file:/C:/bea/user_projects/workspaces/app/target/app-1.0.0/WEB-INF/classes/app-context.xml]
28/11/2012 16:37:48 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-data-context.xml]
28/11/2012 16:37:48 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-scheduler.xml]
28/11/2012 16:37:48 org.springframework.core.io.support.PropertiesLoaderSupport loadProperties
INFO: Loading properties file from class path resource [app.properties]
28/11/2012 16:37:48 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@599bcd: defining beans [org.springframework.beans.factory.config.PropertyPlaceholderConfigurer#0,appDS,job,cronTrigger,org.springframework.scheduling.quartz.SchedulerFactoryBean#0,appDAO,appService,mailerService,appTask, (...)]; root of factory hierarchy
28/11/2012 16:37:50 org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup start
INFO: Starting beans in phase 2147483647

**28/11/2012 16:37:50 org.springframework.scheduling.quartz.SchedulerFactoryBean startScheduler
INFO: Starting Quartz Scheduler now**

28/11/2012 16:37:50 org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization completed in 2109 ms

Log file excerpt that occurs everytime the servlet is called:

<28/11/2012 16h37min50s BRST> <Notice> <WebLogicServer> <BEA-000360> <Server started in RUNNING mode> 
28/11/2012 16:39:06 org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@11f7458: startup date [Wed Nov 28 16:39:06 BRST 2012]; root of context hierarchy
28/11/2012 16:39:06 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-context.xml]
28/11/2012 16:39:06 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-data-context.xml]
28/11/2012 16:39:06 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-scheduler.xml]
28/11/2012 16:39:06 org.springframework.core.io.support.PropertiesLoaderSupport loadProperties
INFO: Loading properties file from class path resource [app.properties]
28/11/2012 16:39:06 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@10ca001: defining beans [(...)]; root of factory hierarchy
28/11/2012 16:39:06 org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup start
INFO: Starting beans in phase 2147483647
**28/11/2012 16:39:06 org.springframework.scheduling.quartz.SchedulerFactoryBean startScheduler
INFO: Starting Quartz Scheduler now**

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">

    <description>generic web.xml</description>

    <!-- ====================================== --> 
    <!--               SPRING                   -->
    <!-- ====================================== --> 
    <!-- Loading Application Bean's. -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:app-context.xml</param-value>
    </context-param>

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

   <servlet>
        <display-name>appServlet</display-name>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>br.com.app.publicInterface.appServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/appServlet</url-pattern>
    </servlet-mapping>

</web-app>

app-context.xml excerpt:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
        <property name="locations">
            <list>
               <value>classpath:app.properties</value>
            </list>
       </property>
    </bean>

    <import resource="classpath:app-data-context.xml"/>
    <import resource="classpath:app-scheduler.xml"/>

    <!-- DAOs -->
    <!-- Services -->   
    <!-- tasks -->

</beans>

app-scheduler.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean name="job" class="org.springframework.scheduling.quartz.JobDetailBean">
        <property name="jobClass" value="br.com.app.job.QuartzJob" />
        <property name="jobDataAsMap">
            <map>
                <entry key="gerenciadorTasks">
                    <ref bean="gerenciadorTasks" />
                </entry>
            </map>
        </property>
    </bean>

    <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
        <property name="jobDetail" ref="job" />
        <property name="cronExpression" value="${quartz.cronExpression}" />
    </bean>

    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="quartzProperties">
            <props>
                <prop key="org.quartz.scheduler.skipUpdateCheck">true</prop>
                <prop key="org.quartz.jobStore.isClustered">true</prop> 
                <prop key="org.quartz.scheduler.instanceName">NotificadorQuartzScheduler</prop>  
                <prop key="org.quartz.scheduler.instanceId">AUTO</prop> 
            </props>
        </property>
        <property name="triggers">
            <list>
                <ref bean="cronTrigger" />
            </list>
        </property>
    </bean>

</beans>

AppServlet source code:

public class AppServlet extends HttpServlet   {

    private static final long serialVersionUID = 7213474106234238692L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        PrintWriter out = resp.getWriter();

        out.println("execução iniciada em: " + new Date());
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:app-context.xml");   
        GerenciadorTasks gt = (GerenciadorTasks) context.getBean(SpringNameBeans.GERENCIADOR_TASKS);
        gt.init();
        out.println("execução terminada em: " + new Date());

    }

}

Thank you very much for your time.

Solution

--- Following @Boris information, I altered my servlet a little. My context was already loaded, I just needed to access it:

public class AppServlet extends HttpServlet   {

    private static final long serialVersionUID = 7213474106234238692L;

    ApplicationContext applicationContext = null;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        PrintWriter out = resp.getWriter();

        out.println("execução iniciada em: " + new Date());
         if (applicationContext == null){
                applicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        }
        GerenciadorTasks gt = (GerenciadorTasks) applicationContext.getBean(SpringNameBeans.GERENCIADOR_TASKS);
        gt.init();
        out.println("execução terminada em: " + new Date());

    }

}
Quaestor Lucem
  • 607
  • 9
  • 21

1 Answers1

2

In your code you're creating a new application context on every GET method, you should either create your context in the servlet init() mehod, or use Web App root contet instead:

public class AppServlet extends HttpServlet   {

@Autowired
ApplicationContext applicationContext;

@Override
public void init(ServletConfig arg0) throws ServletException {
     WebApplicationContextUtils.getWebApplicationContext(arg0.getServletContext())
        .getAutowireCapableBeanFactory().autowireBean(this);
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        PrintWriter out = resp.getWriter();

        out.println("execução iniciada em: " + new Date());

        GerenciadorTasks gt = (GerenciadorTasks) this.applicationContext.getBean(SpringNameBeans.GERENCIADOR_TASKS);
        gt.init();
        out.println("execução terminada em: " + new Date());

    }
}

It's not clear if your GerenciadorTasks is singleton or not so in my answer I used getbean() instead of autowiring. Calling init() on every GET request is very unlikely what you want to do. Also please note that the root web app context is already created in your configuration with

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:app-context.xml</param-value>
    </context-param>

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

If you want to use scheduling in spring consider using the respective API.

So I think that using only the root web application context only with the scheduling api is the way to go. Also take a look at Spring lifecyle beans.

Community
  • 1
  • 1
Boris Treukhov
  • 17,493
  • 9
  • 70
  • 91
  • thank you for your awesome and complete reply - it showed me my error. Also, thanks for the links, they contained valuable information. I solved the question in a way similiar to your proposition. I will put the new servlet code in my question for the benefit of others and close the question. – Quaestor Lucem Nov 29 '12 at 16:30
  • Note: the GerenciadorTasks' init method is a method of mine, not one of some API, like Servlet's one - just the name that is misleading outside the app context :) – Quaestor Lucem Nov 29 '12 at 16:36