7

我有一个每小时运行一次的 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>

应用程序上下文.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
    • 网络应用
      • 站点1
      • 站点2
      • 站点3
      • 站点4
      • 站点5

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

服务器.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>
4

2 回答 2

2

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

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

幸运的是,Spring 为您提供了一种定义共享父应用程序上下文的方法,以便在同一容器中运行的 WAR 之间共享 bean。它在网上有很好的记录。

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

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

  • 将应用程序合二为一<Host>
  • <Alias>标签与 single一起使用<Host>以支持两个单独的 URL
  • 使用上述技术确保存在共享的应用程序上下文
于 2013-05-14T01:49:38.700 回答
0

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

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

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

于 2013-05-14T14:47:32.977 回答