解決Spring定時計劃任務重複執行兩次(實例被構造兩次)問題的方法

今天在作一個項目的時候用到了Spring的定時計劃任務。這是Spring的特點功能,能夠根據設置在特定的時間或間隔時間作特定的事。 php

下面給出一個例子: html

[java]  view plain copy
  1. package net.csdn.blog.chaijunkukn;  
  2.   
  3. import java.text.SimpleDateFormat;  
  4. import java.util.Calendar;  
  5. import java.util.Locale;  
  6.   
  7. public class TimerTask {  
  8.     public void printTimeStamp(){  
  9.         Calendar ca= Calendar.getInstance();  
  10.         ca.setTimeInMillis(System.currentTimeMillis());  
  11.         SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ", Locale.CHINA);  
  12.         //顯示當前時間 精確到毫秒  
  13.         System.out.print(sdf.format(ca.getTime()));       
  14.     }  
  15.     public TimerTask(){  
  16.         this.printTimeStamp();  
  17.         System.out.println("計劃任務被初始化了");  
  18.     }  
  19.     public void doTask(){  
  20.         this.printTimeStamp();        
  21.         System.out.print("計劃任務被執行,線程id:");  
  22.         System.out.println(Thread.currentThread().getId());  
  23.     }  
  24. }  


根據Spring關於定時任務的規範,任務執行方法應爲無參數無返回的方法,所以按照規範上面的例子中聲明瞭doTask方法。上面的例子很簡單,Spring做爲IoC容器,構造TimerTask實例時會調用無參構造函數,此類會在實例化時在控制檯輸出當前時間和構造信息。當定時任務被觸發時,也會在控制檯顯示當前時間和任務被執行的提示信息。 java

下面是配置(須要聲明的是,本實例基於J2EE工程,使用了log4j,配置文件只是工程中的一部分):  web

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xsi:schemaLocation="http://www.springframework.org/schema/beans  
  5.        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">  
  6.     <!-- 註冊定時器 -->  
  7.     <bean id="timer"  
  8.         class="org.springframework.scheduling.quartz.SchedulerFactoryBean">  
  9.         <property name="triggers">  
  10.             <list>  
  11.                 <ref bean="timerTaskTrigger" />  
  12.             </list>  
  13.         </property>  
  14.     </bean>  
  15.     <!-- 指定什麼時候觸發定時任務 -->  
  16.     <bean id="timerTaskTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">  
  17.         <property name="jobDetail">  
  18.             <ref bean="timerTaskJobDetail" />  
  19.         </property>  
  20.         <property name="cronExpression">  
  21.             <!-- 每3秒鐘觸發一次 -->  
  22.             <value>0/3 * * * * ?</value>  
  23.         </property>  
  24.     </bean>  
  25.     <!-- 指定定時任務細節 調用哪一個類 哪一個方法 -->  
  26.     <bean id="timerTaskJobDetail"  
  27.         class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">  
  28.         <property name="targetObject">  
  29.             <ref bean="timerTaskInstance" />  
  30.         </property>  
  31.         <property name="targetMethod">  
  32.             <value>doTask</value>  
  33.         </property>  
  34.         <property name="concurrent" value="false" />  
  35.     </bean>  
  36.     <!-- 實例化定時任務類 -->  
  37.     <bean id="timerTaskInstance" class="net.csdn.blog.chaijunkukn.TimerTask" />  
  38. </beans>  

 

web.xml的配置:  spring

[html]  view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <web-app id="WebApp_ID" version="2.4"  
  3.     xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">  
  5.     <display-name>TaskTest</display-name>  
  6.     <servlet>  
  7.         <servlet-name>springapp</servlet-name>  
  8.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  9.         <init-param>  
  10.             <param-name>contextConfigLocation</param-name>  
  11.             <param-value>/WEB-INF/classes/applicationContext*.xml</param-value>  
  12.         </init-param>  
  13.         <load-on-startup>1</load-on-startup>  
  14.     </servlet>  
  15.   
  16.     <servlet-mapping>  
  17.         <servlet-name>springapp</servlet-name>  
  18.         <url-pattern>*.htm</url-pattern>  
  19.     </servlet-mapping>  
  20.   
  21.     <filter>  
  22.         <filter-name>EncodingFilter</filter-name>  
  23.         <filter-class>com.ku6.tech.wap.filter.EncodingFilter</filter-class>  
  24.         <init-param>  
  25.             <param-name>encoding</param-name>  
  26.             <param-value>utf-8</param-value>  
  27.         </init-param>  
  28.         <init-param>  
  29.             <param-name>forceEncoding</param-name>  
  30.             <param-value>true</param-value>  
  31.         </init-param>  
  32.     </filter>  
  33.       
  34.     <filter-mapping>  
  35.         <filter-name>EncodingFilter</filter-name>  
  36.         <url-pattern>*.htm</url-pattern>  
  37.     </filter-mapping>  
  38.   
  39.     <error-page>  
  40.         <error-code>404</error-code>  
  41.         <location>/error.jsp</location>  
  42.     </error-page>  
  43.   
  44.     <welcome-file-list>  
  45.         <welcome-file>index.jsp</welcome-file>  
  46.     </welcome-file-list>  
  47.   
  48.     <listener>  
  49.         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  50.     </listener>  
  51.   
  52.     <context-param>  
  53.         <param-name>contextConfigLocation</param-name>  
  54.         <param-value>/WEB-INF/classes/applicationContext*.xml</param-value>  
  55.     </context-param>  
  56. </web-app>  

 

配置的部分就是這樣,而後我使用MyEclipse 9.1關聯上Tomcat服務器。一切都是默認的設置,而後將本引用部署並啓動Tomcat服務器。 apache

這時候問題來了,個人任務類竟然被建立了兩次,下面是截取的部分日誌數據: tomcat

[html]  view plain copy
  1. 2011-11-01 19:09:02,568 INFO [main] - org.springframework.orm.hibernate3.HibernateTransactionManager.afterPropertiesSet(421) | Using DataSource [org.apache.commons.dbcp.BasicDataSource@f2ff9b] of Hibernate SessionFactory for HibernateTransactionManager  
  2. 2011-11-01 19:09:02,756 INFO [main] - org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer.postProcessTemplateLoaders(124) | ClassTemplateLoader for Spring macros added to FreeMarker configuration  
  3. 2011-11-01 19:09:03.878 計劃任務被初始化了  
  4. 2011-11-01 19:09:03,987 INFO [main] - org.quartz.core.SchedulerSignalerImpl.<init>(63) | Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl  
  5. 2011-11-01 19:09:03,987 INFO [main] - org.quartz.core.QuartzScheduler.<init>(214) | Quartz Scheduler v.1.6.1-RC1 created.  
  6. ...  
  7. 2011-11-01 19:09:05,140 WARN [main] - org.hibernate.cache.EhCacheProvider.buildCache(86) | Could not find configuration [org.hibernate.cache.StandardQueryCache]; using defaults.  
  8. 2011-11-01 19:09:05,218 INFO [main] - org.springframework.orm.hibernate3.HibernateTransactionManager.afterPropertiesSet(421) | Using DataSource [org.apache.commons.dbcp.BasicDataSource@85b4c5] of Hibernate SessionFactory for HibernateTransactionManager  
  9. 2011-11-01 19:09:05,218 INFO [main] - org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer.postProcessTemplateLoaders(124) | ClassTemplateLoader for Spring macros added to FreeMarker configuration  
  10. 2011-11-01 19:09:05.249 計劃任務被初始化了  
  11. 2011-11-01 19:09:05,249 INFO [main] - org.quartz.core.SchedulerSignalerImpl.<init>(63) | Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl  
  12. 2011-11-01 19:09:05,249 INFO [main] - org.quartz.core.QuartzScheduler.<init>(214) | Quartz Scheduler v.1.6.1-RC1 created.  
  13. ...  
  14. 2011-11-1 19:09:05 org.apache.catalina.startup.Catalina start  
  15. 信息: Server startup in 9451 ms  
  16. 2011-11-01 19:09:06.013 計劃任務被執行,線程id:17  
  17. 2011-11-01 19:09:06.013 計劃任務被執行,線程id:39  
  18. 2011-11-01 19:09:09.021 計劃任務被執行,線程id:19  
  19. 2011-11-01 19:09:09.021 計劃任務被執行,線程id:40  


從上面的日誌中能夠看出, 服務器

在2011-11-01 19:09:03.878 定時計劃任務類產生了一個實例  app

在2011-11-01 19:09:05.249 定時 計劃任務類又產生了一個實例 框架

起初我對它並不關心,可是下面的問題倒是不可接受的,計劃任務確實是差很少每隔3秒鐘被調度的,可是每次調度執行了任務方法兩次。設想一下,這僅僅是個開銷很小的例子,可是若是這個方法執行的是一個很是耗時耗資源的任務,好不容易執行完一次後又要執行一次,這是對計算資源的極大浪費。因而查找了一天的緣由,最後在國外的一個論壇上找到了解決的辦法(http://forum.springsource.org/showthread.php?33311-IoC-Container-initializes-my-app-twice)。

 

樓主roncox和我遇到了一樣的問題,他和個人配置差很少,一樣也貼出了配置文件。雖然其餘人沒有解決問題,可是他本身解決了,並提供了最後的解決方法:

 

解決辦法就是將web.xml配置文件中的以下節點刪掉:

[html]  view plain copy
  1. <listener>  
  2.     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  3. </listener>  
  4.   
  5. <context-param>  
  6.     <param-name>contextConfigLocation</param-name>  
  7.     <param-value>/WEB-INF/classes/applicationContext*.xml</param-value>  
  8. </context-param>  


修改以後程序運行一切正常。我的推測,因爲org.springframework.web.context.ContextLoaderListener和org.springframework.web.servlet.DispatcherServlet都可以加載applicationContext*.xml(「*」是通配符,表示全部以「applicationContext」開頭的xml文件)。而兩個類異曲同工,最終都將這些配置文件交給了Spring框架的Ioc容器進行實例化。所以每一個類都會被實例化兩次。


2012年1月10日補充:今天作項目自習研究了一下spring的配置文件,發現以前說的不徹底正確,不該該刪除web.xml中的以下節點

[html]  view plain copy
  1. <listener>  
  2.     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  3. </listener>  
  4.   
  5. <context-param>  
  6.     <param-name>contextConfigLocation</param-name>  
  7.     <param-value>/WEB-INF/classes/applicationContext*.xml</param-value>  
  8. </context-param>  

由於該節點指派的applicationContext*.xml是用於實例化除servlet以外的全部對象的,能夠說項目中絕大多數的service和dao層操做都由ContextLoaderListener傳遞給Spring來進行實例化。

在web應用中,web.xml還常常出現以下的配置:

[html]  view plain copy
  1. <!--全局Servlet調度配置 -->  
  2.     <servlet>  
  3.         <!--若設置 servlet-name爲[name] -->  
  4.         <!--則DispatcherServlet在實例化後會自動加載[name]-servlet.xml -->  
  5.         <servlet-name>spring</servlet-name>  
  6.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  7.         <init-param>  
  8.             <param-name>contextConfigLocation</param-name>  
  9.             <param-value>classpath:servletContext.xml</param-value>  
  10.         </init-param>  
  11.         <!--隨服務器一同啓動 -->  
  12.         <load-on-startup>1</load-on-startup>  
  13.     </servlet>  
  14.     <servlet-mapping>  
  15.         <servlet-name>spring</servlet-name>  
  16.         <url-pattern>*.do</url-pattern>  
  17.     </servlet-mapping>  
這個是用來處理全部servlet的,沒有它就沒法經過請求地址來調用相應的Controller。
這裏我明確地指示了要加載類路徑下的servletContext.xml,若是不指定,則會按照註釋中所描述地那樣自動加載spring-servlet.xml
不管是servletContext.xml仍是applicationContext*.xml均可以按照<beans>...<bean id="XXX" class="XXX" />...</beans>這樣的形式來配置。
問題來了,有時候不注重對象初始化的分類,尤爲是使用<context:component-scan base-package="controller" />這樣的包掃描形式統一初始化,
很容易形成知足條件的對象被初始化兩次,那麼在計劃任務的時候被執行兩次也就不奇怪了。其實說來講去,仍是要提醒你們,不一樣的配置文件其做用是不同的,
不要將全部的初始化操做都放到一個配置文件中,更不要重複配置。不只浪費資源,還很容易致使莫名其妙的故障。

 

另外,有相關文章還提到過是Tomcat服務器的問題,修改conf目錄下的server.xml。修改節點Host,將appBase屬性由默認的「webapps」設置爲空("")便可,以下所示:

[html]  view plain copy
  1. <Host name="localhost" appBase="" unpackWARs="true" autoDeploy="true"  
  2.     xmlValidation="false" xmlNamespaceAware="false">  
  3.   
  4.     <Context docBase="/usr/local/apache-tomcat-6.0.29/webapps/semwinner"  
  5.         path="" reloadable="true"></Context>  
  6.     <Context docBase="/usr/local/apache-tomcat-6.0.29/webapps/emarboxmanager"  
  7.         path="/admin" reloadable="true"></Context>  
  8. </Host>    

可是本人嘗試以後並無起做用。可能不適用於我遇到的這個問題。寫出上面解決方法的做者認爲web應用程序默認都是放在webapps這個目錄下的,若是不把「webapps「去掉,這裏會調用一次quartz的任務調度,在接下來的「<Context path」中又會調用一次quartz的任務調度,因此就重複了2次。兩個方法都寫出來,供朋友們參考。

第二種解決方法來自http://nkliuliu.iteye.com/blog/816335

相關文章
相關標籤/搜索