spring 定時任務執行兩次問題及解決

摘自:https://blog.csdn.net/tengdazhang770960436/article/details/41246469

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

下面給出一個例子:

  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實例時會調用無參構造函數,此類會在實例化時在控制檯輸出當前時間和構造信息。當定時任務被觸發時,也會在控制檯顯示當前時間和任務被執行的提示信息。

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

 


 
  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的配置: 

 

 

 


 
  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服務器。

這時候問題來了,我的任務類居然被創建了兩次,下面是截取的部分日誌數據:

 

 


 
  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 定時計劃任務類產生了一個實例 

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

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

 

樓主roncox和我遇到了同樣的問題,他和我的配置差不多,同樣也貼出了配置文件。雖然其他人沒有解決問題,但是他自己解決了,並提供了最後的解決方法:

 

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

 


 
  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中的如下節點

 


 
  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還經常出現如下的配置:

 


 
  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”設置爲空("")即可,如下所示:

 

 


 
  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

 


 
  1. <listener>

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

  3. </listener>

  4. <context-param>

  5. <param-name>contextConfigLocation</param-name>

  6. <param-value>classpath:applicationContext*.xml</param-value>

  7. </context-param>

  8. <servlet>

  9. <servlet-name>spring</servlet-name>

  10. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

  11. <init-param>

  12. <param-name>contextConfigLocation</param-name>

  13. <param-value>classpath:spring-servlet.xml</param-value>

  14. </init-param>

  15. <load-on-startup>1</load-on-startup>

  16. </servlet>


listener--context-param這段配置是負責依賴注入的,配置文件名稱支持正則匹配。這裏是關鍵,你要看看所有匹配規則的配置文件中是否存在重複注入bean的現象;
servlet這段是負責請求URL請求處理轉發到哪個Bean上的。
希望對你有幫助。

 

 

關鍵:spring-servlet.xml 文件負責的是url 的轉發,而 applicationContext.xml 負責的是bean 的注入,兩者負責的工作不一樣,所以在 web.xml 文件中配置的時候,就需要注意,DispatcherServlet 的初始化參數是spring-servlet.xml,而ContextLoaderListener的初始化參數纔是applicationContext.xml 文件呢。

 

 

經過本人測試發現第二種:修改 server.xml 文件的方式是可以的,反而第一種方式不起作用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章