Spring Scheduled Jobs在不同的池中执行3次

时间:2013-05-06 19:31:27

标签: java spring scheduled-tasks

我有一个每小时运行的Spring @Scheduled作业,但我发现它实际上每小时运行3次。这是显示此问题的日志输出:

2013-05-06 12:00:27,656 [pool-2-thread-1] INFO  src.jobs.NotifyUsersWhenVideoAvailableJob - Emails sent from NotifyUsersWhenVideoAvailableJob: 1
2013-05-06 12:00:27,750 [pool-1-thread-1] INFO  src.jobs.NotifyUsersWhenVideoAvailableJob - Emails sent from NotifyUsersWhenVideoAvailableJob: 1
2013-05-06 12:00:27,796 [pool-4-thread-1] INFO  src.jobs.NotifyUsersWhenVideoAvailableJob - Emails sent from NotifyUsersWhenVideoAvailableJob: 1

这显然非常令人讨厌,因为每次此作业运行时都会发送同一封电子邮件的三个副本。

我正在使用Spring 3.1

这是我的配置:

WEB.XML

<?xml version="1.0" encoding="ISO-8859-1"?>
<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">
  <display-name>site2</display-name>
  <description>Roo generated site2 application</description>
  <context-param>
    <param-name>defaultHtmlEscape</param-name>
    <param-value>true</param-value>
  </context-param>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:META-INF/spring/applicationContext*.xml</param-value>
  </context-param>
  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter>
    <filter-name>HttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  </filter>
  <filter>
    <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <filter-mapping>
    <filter-name>HttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <filter-mapping>
    <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <servlet>
    <servlet-name>site2</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>WEB-INF/spring/webmvc-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>site2</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>120</session-timeout>
  </session-config>
  <error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/error</location>
  </error-page>
</web-app>

的applicationContext.xml

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

  <context:property-placeholder
    location="classpath*:META-INF/spring/*.properties" />

  <context:spring-configured />

  <context:component-scan base-package="src">
    <context:exclude-filter expression=".*_Roo_.*"
      type="regex" />
    <context:exclude-filter expression="org.springframework.stereotype.Controller"
      type="annotation" />
  </context:component-scan>

  <task:annotation-driven/>

  <bean id="sessionFactory"
    class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="packagesToScan" value="src.domain" />
    <property name="mappingDirectoryLocations">
      <list>
        <value>classpath*:**/src.domain</value>
      </list>
    </property>
    <property name="hibernateProperties">
      <props>
        <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
        <prop key="hibernate.show_sql">true</prop>
        <prop key="format_sql">true</prop>
        <prop key="hibernate.use_sql_comments">true</prop>
      </props>
    </property>
  </bean>

  <bean id="webexpressionHandler" class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler" />
  <security:http pattern="/index.html" security="none" />
  <security:http pattern="/about.html" security="none" />
  <security:http pattern="/pricing.html" security="none" />
  <security:http pattern="/signup.html" security="none" />
  <security:http pattern="/forgotPassword.htm" security="none" />
  <security:http pattern="/**.json" security="none" />

  <security:http auto-config="true">
    <security:intercept-url pattern="/**.htm"
      access="ROLE_FREE" />
      <security:intercept-url pattern="/test/**.htm"
      access="ROLE_FREE" />
      <security:intercept-url pattern="/admin.htm"
      access="ROLE_SUPERUSER" />
      <security:intercept-url pattern="/exerciseFiles/**.zip"
      access="ROLE_RECOMMENDED" />
    <security:form-login login-page="/login.html"
      authentication-failure-handler-ref="failedLoginService"
      authentication-success-handler-ref="successfulLoginService" />
      <security:logout logout-success-url="/index.html"/>
  </security:http>

  <security:authentication-manager>
    <security:authentication-provider
      user-service-ref="userDetailsService" />
  </security:authentication-manager>
</beans>

webmvc-config.xml中

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p" 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-3.1.xsd                 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd                 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">

    <context:component-scan base-package="src" use-default-filters="false">
        <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
    </context:component-scan>

    <mvc:annotation-driven conversion-service="applicationConversionService"/>

    <mvc:resources location="/, classpath:/META-INF/web-resources/" mapping="/resources/**"/>

    <mvc:default-servlet-handler/>

    <mvc:interceptors>
        <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" p:paramName="lang"/>
    </mvc:interceptors>

    <mvc:view-controller path="/" view-name="index"/>
    <mvc:view-controller path="/uncaughtException"/>
    <mvc:view-controller path="/resourceNotFound"/>
    <mvc:view-controller path="/dataAccessFailure"/>

    <bean class="org.springframework.context.support.ReloadableResourceBundleMessageSource" id="messageSource" p:basenames="WEB-INF/i18n/messages,WEB-INF/i18n/application" p:fallbackToSystemLocale="false"/>

    <bean class="org.springframework.web.servlet.i18n.CookieLocaleResolver" id="localeResolver" p:cookieName="locale"/>

    <bean class="org.springframework.ui.context.support.ResourceBundleThemeSource" id="themeSource"/>

    <bean class="org.springframework.web.servlet.theme.CookieThemeResolver" id="themeResolver" p:cookieName="theme" p:defaultThemeName="standard"/>

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" p:defaultErrorView="uncaughtException">
        <property name="exceptionMappings">
            <props>
                <prop key=".DataAccessException">dataAccessFailure</prop>
                <prop key=".NoSuchRequestHandlingMethodException">resourceNotFound</prop>
                <prop key=".TypeMismatchException">resourceNotFound</prop>
                <prop key=".MissingServletRequestParameterException">resourceNotFound</prop>
            </props>
        </property>
    </bean>

    <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"/>

    <bean class="org.springframework.web.servlet.view.UrlBasedViewResolver" id="viewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <bean class="src.web.ApplicationConversionServiceFactoryBean" id="applicationConversionService"/>

</beans>

这是正在执行作业的类文件:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import src.jobs.NotifyUsersWhenVideoAvailableJob;
import src.jobs.PayAsYouGoReminderJob;
import src.jobs.RemindUsersToActivateJob;

@Service
public class ScheduledJobsService
{
  @Autowired
  @Qualifier("videoJob")
  private NotifyUsersWhenVideoAvailableJob videoJob;
  @Autowired
  @Qualifier("activateJob")
  private RemindUsersToActivateJob activateJob; 
  @Autowired
  private PayAsYouGoReminderJob payAsYouGoReminderJob;

  //This cron just should be set to 1 second past the hour
  // as the videoJob has dates set to be ON the hour exactly
  // example of good setting: @Scheduled(cron="1 0 * * * *")
  @Scheduled(cron="1 0 * * * *")
  public void doHourlyJobs() 
  {
    videoJob.run();
  }

  @Scheduled(cron="0 0 12 * * *")
  public void doDailyJobs() 
  {
    try
    {
        activateJob.run();
    }
    catch (Exception e)
    {
      EmailService.sendError(e, null);
    }

    try
    {
      payAsYouGoReminderJob.run();
    }
    catch (Exception e)
    {
      EmailService.sendError(e, null);
    }
  }
}

* 编辑 *

在做了一些更多的讨论后,我已经缩小了(多一点)问题可能发生的地方。我无法在DEV环境中重现此问题,因此我的PROD盒子上必须有某种配置。

我的PROD框在webapps文件夹中有5个不同的Web应用程序:

  • tomcat 6.0
    • web应用
      • 站点1
      • 站点2
      • site3
      • site4
      • site5

我对server.xml文件进行了一些更改,现在它似乎只执行了两次而不是三次。这是新配置:

server.xml中

<Server port="8005" shutdown="SHUTDOWN">

  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JasperListener" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">
    <Connector port="80" protocol="HTTP/1.1" 
               connectionTimeout="20000" 
               redirectPort="8443" />
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">
      <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
             resourceName="UserDatabase"/>
      <Host name="site1.net"  appBase="webapps"
            unpackWARs="true" autoDeploy="true"
            xmlValidation="false" xmlNamespaceAware="false">
        <Alias>www.site1.net</Alias>
      </Host>

        <Host name="site2.net"  appBase="webapps"
            unpackWARs="true" autoDeploy="false" deployOnStartup="false"
            xmlValidation="false" xmlNamespaceAware="false">
            <Alias>www.site2.net</Alias>
            <Context path="" docBase="./site2"/>
        </Host>

    </Engine>
  </Service>
</Server>

2 个答案:

答案 0 :(得分:2)

如果你在server.xml中定义了两个web应用程序,并且你不小心(见下文),那么你将在servlet容器中运行两个完全独立的实例。

context.xml文件为您提供了一种区分共享内容和特定于应用程序的内容的方法。可以共享的事物的最常见示例是数据源或连接池。但这并不能真正帮助你使用Spring bean。

幸运的是,Spring为您提供了一种定义共享父应用程序上下文的方法,以便在同一容器中运行的WAR之间共享bean。这是pretty well documented在线。

不幸的是,我认为这不适用于<Host>元素。虚拟主机允许您隔离同一物理计算机上的资源,特别是可以独立管理资源。因此,如果两个应用程序都通过<task:annotation-driven/>定义任务,那么最终会有两个单独的实例。

因此,总而言之,如果您想要每个servlet容器的每个任务的单个实例,那么您需要一个共享的应用程序上下文。你可以:

  • 将应用程序移动到一个<Host>
  • <Alias>标记与单个<Host>一起使用,以支持两个单独的网址
  • 使用上述技术确保存在共享应用程序上下文

答案 1 :(得分:0)

由于在应用程序启动时多次加载ScheduledJobsService,可能会发生这种情况。

您可以通过将@PostConstruct方法添加到ScheduledJobsService并使用日志消息来检查此假设。

如果是这样,请检查您通过上下文在webmvc-config.xml和ApplicationContext.xml中加载的bean(或包):component-scan不相交。