Spring实现后台的任务调度TimerTask和Quartz

标签: spring 后台 任务 | 发表时间:2013-05-09 22:54 | 作者:Nono_Love_Lilith
出处:http://blog.csdn.net


最近整后台,涉及到两个后台调度的问题。

一是以时间间隔为条件的轮询调度;

运用场景:每隔5分钟抓取数据;


二是一某个时间点为条件的轮询调度;

运用场景:后台日志货报表生成上传,每个周一生成上一周的,每个月初生成上一月。

其实按周来执行调度,用前面一个场景也可以实现,但是按月生成,因为每月时间不固定,必须动态判断和执行。


后台实现调度的思路,我一开始考虑的是,web启动时通过一个入口方法,启动一个while线程(假设我需要每隔5分钟发送某个请求task)一直监控。

task执行一次后,sleep(5分钟),或是设置一个哨兵时间点latestTime,获取当前时间currentTime,相减判断是否大于5分钟来判断是否再次执行task。

后一种方法虽然只是不停的执行2-3条指令,理论上觉得sleep来挂起线程应该更加节省资源,猜测。

简短流程:

任务入口类executor,执行exe方法来初始化启动loopThread,然后一直跑。

即Executor——》LoopThread——》Task;


后来发现spring配合Quartz或是timetask可以更加简单的实现,可以把线程生命周期等一些问题都交给Spring来管理。

通过配置后,直接到上面的—》TASK

下面根据场景1,实现简单的后台任务调度。

第一步:定义一个任务类Task。

然后把Task注入到spring管理的bean中,一般会有几种方法,最简单的一般我们本身都会配置一个bean.xml来

<!-- 1:配置注解的自动扫描的范围  -->

<context:component-scanbase-package="com.yourpro"/>

就是只需在类上加上

@Repository @Component

等自动注入该类了.

或是:

<beanid="task"class="com.xx.xx.Task">

第二步配置:

	<!-- step3 -->
	<bean id="targetTask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
		<property name="targetObject">
			<ref bean="task"/><--!这个bean引用的就是我们前面注入的bean,Task累,id为task-->
		</property>
		<property name="targetMethod">
			<value>taskExe</value><!--指点目标类中的目标方法,即,需要执行Task中额taskExe方法-->
		</property>
	</bean>
	<bean id="taskTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
		<property name="jobDetail">
			<ref bean="targetTask"/><!--执行详情task为上面那个配置的bean-->
		</property>
  		<property name="startDelay"><!--开始延时时间-->
		    <value>0</value>
  		</property>
		<property name="repeatInterval">
			<!-- 轮询时间间隔 -->		    
		    <value>300</value>
		</property>
	</bean>
	
	<!--step1:下面这个可以比作web启动后调度的启动器-->
	<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<property name="triggers">
			<list>  
              
               <ref local="taskTrigger"/>
			</list>
		</property>
	</bean>

我们可以看到基本启动流程是 step1启动了一个定时器taskTrigger,定时器设置了轮询时间参数,以及指定执行的任务类,

最上面的bean申明了任务类和任务方法。

按时间间隔的后台任务调度基本就这样简单实现了。


场景二实现:根据某个时间点时间点

只是将第二个bean,也就四定时器设置改成可以配置时间点。

<bean id="taskTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
  <property name="jobDetail">
   <ref bean="targetTask"/>
  </property>
  <property name="cronExpression">
  <value>0 0 0 1 * ?</value><该设置表示每月1号0点0分0秒执行,具体可以CronTrigger获取更多资料>
  </property>
 </bean>
上面就可以简单实现固定时间的task任务。


对于task类,也就是step1中配置的那个任务类,还可以用一个更加简单的继承实现。

使用spring+Quartz,task类继承QuartzJobBean;

然后step1配置成:

<bean id="targetTask" class="org.springframework.scheduling.quartz.JobDetailBean">
  <property name="jobClass">    

<ref bean="task"/>

</property> </bean>

注意注入内容,一般就这样,无需指定方法,应为继承了QuartzJobBean后默认会让你实现一个

executeInternal(.....)方法;


最后说下如何实现一个多任务的线程,从上面我们也看到,实现的都是一个简单的任务;

当然其实实现多线程任务也很简单,就是在将原本直接启动Task那步改成,启动一个MultipTaskEngine;

该多重任务引擎在生成多个子线程来执行;直接一个for循环将Task改造成线程就行了,后来一作后台的同事Spring直接可以

配置一个多线程池来实现;好处还是Spring带我们管理了线程生命周期,线程性能优化等。

具体怎么实现:

如上面所说,现在需要两个类了,一个是MultipTaskEngine类,会被配置到bean.xml中作为启动引擎类;该类继承TimerTask

另一个类就是Task任务线程类;

配置文件:

 <!-- 6: 异步线程池 -->  
    <bean id="threadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">  
	    <!-- 核心线程数  -->  
	    <property name="corePoolSize" value="10" />  
	    <!-- 最大线程数 -->  
	    <property name="maxPoolSize" value="100" />  
	    <!-- 队列最大长度 >=mainExecutor.maxSize -->  
	    <property name="queueCapacity" value="1000" />  
	    <!-- 线程池维护线程所允许的空闲时间 -->  
	    <property name="keepAliveSeconds" value="300" />  
	    <!-- 线程池对拒绝任务(无线程可用)的处理策略 -->  
	    <property name="rejectedExecutionHandler">  
	        <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />  
	    </property>
    </bean>
    <!-- 主任务 负责扫描任务 将任务分配给线程完成 -->  
    <bean id="multipTaskEngine" class="com.xxx.MultipTaskEngine">  
        <property name="threadPool" ref="threadPool" />
    </bean>
    
    <bean id="taskTrigger" class="org.springframework.scheduling.concurrent.ScheduledExecutorTask">  
        <property name="runnable" ref="multipTaskEngine" /><!-- 线程方法 -->
        <!-- 容器加载10秒后开始执行 -->  
       	<property name="delay" value="5000" />  
        <!-- 每次任务间隔 1分钟--> 
        <property name="period" value="60000"/>
        <!-- 固定间隔,否则默认fixDelay会等到前一个任务完成后才开始计时. --> 
        <property name="fixedRate" value="true" /> 
    </bean>
	
    <!-- 多线程调度 -->
    <bean id="springScheduledExecutorFactoryBean" class="org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean">  
        <property name="scheduledExecutorTasks" >  
        <list>  
            <ref bean="taskTrigger" />
        </list>
        </property>  
    </bean>

在MutipTaskEngine类中手动注入


 private ThreadPoolTaskExecutor threadPool;
    public void setThreadPool(ThreadPoolTaskExecutor threadPool) {  
        this.threadPool = threadPool;  
    }

生成字线程Task直接

for (int i = 0; i < n; i++) {
	threadPool.execute(new UserRegisterReportThread(i));
		}


这边要注意一点的是,如果新起的task子线程中有注入的bean,会产生

spring 子线程中注入bean为空,一开始我就犯了这个问题

后来网上找到一个答案:

【转】web后台线程中获取spring容器内的bean

有时候需要启动一个后台守护线程,做一些别的事情。这时候怎么获取spring里的Service、Dao、Action等对象?(注意自己new一个是不行的,因为脱离了spring的管理,其中IoC资源都没有被注入)。

一个解决办法是,重新弄一个Spring:


   XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource(
     "applicationContext.xml"));

   // 从配置文件中获取对象
   IService hello = (IService) factory.getBean("service");
   hello.service("Helloween");

   factory.destroySingletons();

这是最笨的办法,也是不科学的。这样的话,web中会有两个spring容器,两套service、dao等。

---------------------我是分割线----------------------------------

比较科学的办法,是用Spring的方法获取到当前的容器(也是当前应用下唯一的spring容器),并从中获取资源。代码为:


   WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
   IService service = (IService) context.getBean("service");

--------------------我是分割线-------------------------

ContextLoader.getCurrentWebApplicationContext()方法 可以从spring的加载listener中追踪到:

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


我 用:WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
   IService service = (IService) context.getBean("service");

类方法来获取注入的bean,解决了以上问题。



作者:Nono_Love_Lilith 发表于2013-5-9 22:54:31 原文链接
阅读:69 评论:0 查看评论

相关 [spring 后台 任务] 推荐:

Spring实现后台的任务调度TimerTask和Quartz

- - CSDN博客互联网推荐文章
最近整后台,涉及到两个后台调度的问题. 一是以时间间隔为条件的轮询调度;. 运用场景:每隔5分钟抓取数据;. 二是一某个时间点为条件的轮询调度;. 运用场景:后台日志货报表生成上传,每个周一生成上一周的,每个月初生成上一月. 其实按周来执行调度,用前面一个场景也可以实现,但是按月生成,因为每月时间不固定,必须动态判断和执行.

Spring定时任务的几种实现

- - ITeye博客
Spring定时任务的几种实现. 近日项目开发中需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息,借此机会整理了一下定时任务的几种实现方式,由于项目采用spring框架,所以我都将结合. 从实现的技术上来分类,目前主要有三种技术(或者说有三种产品):. Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务.

quartz spring 实现动态定时任务

- - 企业架构 - ITeye博客
在实际项目应用中经常会用到定时任务,可以通过quartz和spring的简单配置即可完成,但如果要改变任务的执行时间、频率,废弃任务等就需要改变配置甚至代码需要重启服务器,这里介绍一下如何通过quartz与spring的组合实现动态的改变定时任务的状态的一个实现. 参考文章: http://www.meiriyouke.net/?p=82.

Spring+quartz 实现动态管理任务

- - 寒江孤影
在实际项目应用中经常会用到定时任务,可以通过quartz和spring的简单配置即可完成,但如果要改变任务的执行时间、频率,废弃任务等就需要改变配置甚至代码需要重启服务器,这里介绍一下如何通过quartz与spring的组合实现动态的改变定时任务的状态的一个实现. 本文章适合对quartz和spring有一定了解的读者.

Java Spring注解任务调度并实现AOP监控任务执行情况

- - 极客521 | 极客521
本文讲的是通过Spring注解的方式实现任务调度. 只要引入了spring-context包就能够在项目中使用注解方式的任务调度. 需要在Spring配置文件中加入task的schema. 然后在代码中就可以直接用了,要定时执行的方法必须是void的,并且没有任何参数的. cron表达式请自行问百度,下面只列出几个从网上找的例子.

Spring 任务调度Quartz的cron表达式

- - ITeye博客
Spring支持基于Quartz的任务调度,那么其cron表达式类似于Linux的crontab,有7个字符构成,详情如下:. 表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五. 表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即等同于10,11,12.

Spring+Quartz实现动态添加定时任务

- - 编程语言 - ITeye博客
   0 0 0 * * ?. //如果全部定时任务都要动态生成,可以只配置这一个即可. * Description: 计时器工具类. private static Scheduler scheduler;// 调度器.   * Description: 启动一个自定义的job.

Spring Boot实现定时任务的动态增删启停

- - 程序猿DD
在Spring Boot项目中,可以通过 @EnableScheduling注解和 @Scheduled注解实现定时任务(在DD的Spring Boot教程中就有介绍: 使用@Scheduled实现定时任务. ),也可以通过 SchedulingConfigurer接口来实现定时任务. 但是这两种方式不能动态添加、删除、启动、停止任务.

Spring Boot应用的后台运行配置

- - 程序猿DD
酱油一篇,整理一下关于Spring Boot后台运行的一些配置方式. 在介绍后台运行配置之前,我们先回顾一下Spring Boot应用的几种运行方式:. 运行Spring Boot的应用主类. 使用Maven的Spring Boot插件 mvn spring-boot:run来运行. 打成jar包后,使用 java -jar运行.

定时任务莫名停止,Spring 定时任务存在 Bug?? - 楼下小黑哥 - 博客园

- -
这里楼下小黑哥给大家拜个年,祝大家蒸蒸日上烫烫烫,年年有余屯屯屯. 春节放假,小黑哥坐上高铁回家,突然想到一次生产问题. 那是小黑哥参加工作第一年,那一年国庆假期,小黑哥提前一天请假回家办个护照. 那时候刚开始负责一个生产系统,所以工作日请假,还是有点担心,就怕. 问题看小黑哥不在,悄然上门. 高铁开到一半的时候,同事反馈系统不能获取最新的流水信息(流水信息通过.