今天在作一个项目的时候用到了Spring的定时计划任务。这是Spring的特点功能,能够根据设置在特定的时间或间隔时间作特定的事。 php
下面给出一个例子: html
- package net.csdn.blog.chaijunkukn;
-
- import java.text.SimpleDateFormat;
- import java.util.Calendar;
- import java.util.Locale;
-
- public class TimerTask {
- public void printTimeStamp(){
- Calendar ca= Calendar.getInstance();
- ca.setTimeInMillis(System.currentTimeMillis());
- SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ", Locale.CHINA);
- //显示当前时间 精确到毫秒
- System.out.print(sdf.format(ca.getTime()));
- }
- public TimerTask(){
- this.printTimeStamp();
- System.out.println("计划任务被初始化了");
- }
- public void doTask(){
- this.printTimeStamp();
- System.out.print("计划任务被执行,线程id:");
- System.out.println(Thread.currentThread().getId());
- }
- }
根据Spring关于定时任务的规范,任务执行方法应为无参数无返回的方法,所以按照规范上面的例子中声明了doTask方法。上面的例子很简单,Spring做为IoC容器,构造TimerTask实例时会调用无参构造函数,此类会在实例化时在控制台输出当前时间和构造信息。当定时任务被触发时,也会在控制台显示当前时间和任务被执行的提示信息。 java
下面是配置(须要声明的是,本实例基于J2EE工程,使用了log4j,配置文件只是工程中的一部分): web
- <?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.5.xsd">
- <!-- 注册定时器 -->
- <bean id="timer"
- class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
- <property name="triggers">
- <list>
- <ref bean="timerTaskTrigger" />
- </list>
- </property>
- </bean>
- <!-- 指定什么时候触发定时任务 -->
- <bean id="timerTaskTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
- <property name="jobDetail">
- <ref bean="timerTaskJobDetail" />
- </property>
- <property name="cronExpression">
- <!-- 每3秒钟触发一次 -->
- <value>0/3 * * * * ?</value>
- </property>
- </bean>
- <!-- 指定定时任务细节 调用哪一个类 哪一个方法 -->
- <bean id="timerTaskJobDetail"
- class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
- <property name="targetObject">
- <ref bean="timerTaskInstance" />
- </property>
- <property name="targetMethod">
- <value>doTask</value>
- </property>
- <property name="concurrent" value="false" />
- </bean>
- <!-- 实例化定时任务类 -->
- <bean id="timerTaskInstance" class="net.csdn.blog.chaijunkukn.TimerTask" />
- </beans>
web.xml的配置: spring
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app id="WebApp_ID" version="2.4"
- xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
- <display-name>TaskTest</display-name>
- <servlet>
- <servlet-name>springapp</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>/WEB-INF/classes/applicationContext*.xml</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
-
- <servlet-mapping>
- <servlet-name>springapp</servlet-name>
- <url-pattern>*.htm</url-pattern>
- </servlet-mapping>
-
- <filter>
- <filter-name>EncodingFilter</filter-name>
- <filter-class>com.ku6.tech.wap.filter.EncodingFilter</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-mapping>
- <filter-name>EncodingFilter</filter-name>
- <url-pattern>*.htm</url-pattern>
- </filter-mapping>
-
- <error-page>
- <error-code>404</error-code>
- <location>/error.jsp</location>
- </error-page>
-
- <welcome-file-list>
- <welcome-file>index.jsp</welcome-file>
- </welcome-file-list>
-
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
-
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>/WEB-INF/classes/applicationContext*.xml</param-value>
- </context-param>
- </web-app>
配置的部分就是这样,而后我使用MyEclipse 9.1关联上Tomcat服务器。一切都是默认的设置,而后将本引用部署并启动Tomcat服务器。 apache
这时候问题来了,个人任务类竟然被建立了两次,下面是截取的部分日志数据: tomcat
- 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
- 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
- 2011-11-01 19:09:03.878 计划任务被初始化了
- 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
- 2011-11-01 19:09:03,987 INFO [main] - org.quartz.core.QuartzScheduler.<init>(214) | Quartz Scheduler v.1.6.1-RC1 created.
- ...
- 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.
- 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
- 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
- 2011-11-01 19:09:05.249 计划任务被初始化了
- 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
- 2011-11-01 19:09:05,249 INFO [main] - org.quartz.core.QuartzScheduler.<init>(214) | Quartz Scheduler v.1.6.1-RC1 created.
- ...
- 2011-11-1 19:09:05 org.apache.catalina.startup.Catalina start
- 信息: Server startup in 9451 ms
- 2011-11-01 19:09:06.013 计划任务被执行,线程id:17
- 2011-11-01 19:09:06.013 计划任务被执行,线程id:39
- 2011-11-01 19:09:09.021 计划任务被执行,线程id: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配置文件中的以下节点删掉:
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
-
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>/WEB-INF/classes/applicationContext*.xml</param-value>
- </context-param>
修改以后程序运行一切正常。我的推测,因为org.springframework.web.context.ContextLoaderListener和org.springframework.web.servlet.DispatcherServlet都可以加载applicationContext*.xml(“*”是通配符,表示全部以“applicationContext”开头的xml文件)。而两个类异曲同工,最终都将这些配置文件交给了Spring框架的Ioc容器进行实例化。所以每一个类都会被实例化两次。
2012年1月10日补充:今天作项目自习研究了一下spring的配置文件,发现以前说的不彻底正确,不该该删除web.xml中的以下节点
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
-
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>/WEB-INF/classes/applicationContext*.xml</param-value>
- </context-param>
由于该节点指派的applicationContext*.xml是用于实例化除servlet以外的全部对象的,能够说项目中绝大多数的service和dao层操做都由ContextLoaderListener传递给Spring来进行实例化。
在web应用中,web.xml还常常出现以下的配置:
- <!--全局Servlet调度配置 -->
- <servlet>
- <!--若设置 servlet-name为[name] -->
- <!--则DispatcherServlet在实例化后会自动加载[name]-servlet.xml -->
- <servlet-name>spring</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:servletContext.xml</param-value>
- </init-param>
- <!--随服务器一同启动 -->
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>spring</servlet-name>
- <url-pattern>*.do</url-pattern>
- </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”设置为空("")便可,以下所示:
- <Host name="localhost" appBase="" unpackWARs="true" autoDeploy="true"
- xmlValidation="false" xmlNamespaceAware="false">
-
- <Context docBase="/usr/local/apache-tomcat-6.0.29/webapps/semwinner"
- path="" reloadable="true"></Context>
- <Context docBase="/usr/local/apache-tomcat-6.0.29/webapps/emarboxmanager"
- path="/admin" reloadable="true"></Context>
- </Host>
可是本人尝试以后并无起做用。可能不适用于我遇到的这个问题。写出上面解决方法的做者认为web应用程序默认都是放在webapps这个目录下的,若是不把“webapps“去掉,这里会调用一次quartz的任务调度,在接下来的“<Context path”中又会调用一次quartz的任务调度,因此就重复了2次。两个方法都写出来,供朋友们参考。
第二种解决方法来自http://nkliuliu.iteye.com/blog/816335