JAVAweb开发技术-------(六)技术点-定时任务实现的三种方式

在项目开发中,时常会遇到想要定时做些事情,比如电脑可以定时开关机,那么我们的代码也需要定时去执行一些任务,那么实现的方式又有那些了?目前我这里有三种,第一个是java自带的timer,非常简单。第二个是spring提供的定时任务可配置化也是非常简单。
第三个是quartz实现的定时任务可与spring集成。你们可以自即选择一种去根据自己项目需要。
一、java的timer
1、在应用开发中,经常需要一些周期性的操作,比如每5分钟执行某一操作等。对于这样的操作最方便、高效的实现方式就是使用java.util.Timer工具类。

private java.util.Timer timer;
timer = new Timer(true);
timer.schedule(
new java.util.TimerTask() { public void run() { //server.checkNewMail(); 要操作的方法 } }, 0, 5*60*1000);
第一个参数是要操作的方法,第二个参数是要设定延迟的时间,第三个参数是周期的设定,每隔多长时间执行该操作。

使用这几行代码之后,Timer本身会每隔5分钟调用一遍server.checkNewMail()方法,不需要自己启动线程。Timer本身也是多线程同步的,多个线程可以共用一个Timer,不需要外部的同步代码。

第一个参数,是 TimerTask 类,在包:import java.util.TimerTask .使用者要继承该类,并实现public void run() 方法,因为 TimerTask 类 实现了 Runnable 接口。

第二个参数的意思是,当你调用该方法后,该方法必然会调用 TimerTask 类 TimerTask 类 中的 run()方法,这个参数就是这两者之间的差值,转换成汉语的意思就是说,用户调用 schedule() 方法后,要等待这么长的时间才可以第一次执行run() 方法。

第三个参数的意思就是,第一次调用之后,从第二次开始每隔多长的时间调用一次 run() 方法。

2、
(1)Timer.schedule(TimerTask task,Date time)安排在制定的时间执行指定的任务。
(2)Timer.schedule(TimerTask task,Date firstTime ,long period)安排指定的任务在指定的时间开始进行重复的固定延迟执行.
(3)Timer.schedule(TimerTask task,long delay)安排在指定延迟后执行指定的任务.
(4)Timer.schedule(TimerTask task,long delay,long period)安排指定的任务从指定的延迟后开始进行重复的固定延迟执行.
(5)Timer.scheduleAtFixedRate(TimerTask task,Date firstTime,long period)安排指定的任务在指定的时间开始进行重复的固定速率执行.
(6)Timer.scheduleAtFixedRate(TimerTask task,long delay,long period)安排指定的任务在指定的延迟后开始进行重复的固定速率执行.
二、使用spring完成定时任务
spring文件

    <beans xmlns="http://www.springframework.org/schema/beans"  
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
        xmlns:p="http://www.springframework.org/schema/p"  
        xmlns:task="http://www.springframework.org/schema/task"  
        xmlns:context="http://www.springframework.org/schema/context"  
        xmlns:aop="http://www.springframework.org/schema/aop"   
        xsi:schemaLocation="http://www.springframework.org/schema/beans   
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd    
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd    
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd    
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd    
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">  

        <task:annotation-driven /> <!-- 定时器开关-->  


       <!-- 基于xml注解实现的定时配置 start --> 
    <bean id="mySchedulerXml" class="com.tianrong.springScheduler.MySchedulerXml"></bean>  

    <task:scheduled-tasks>  
        <!--这里表示的是每隔五秒(*/5 * * * * ?)做测试用 执行一次 如果有需要在下面依次添加task就可以-->  
        <task:scheduled ref="mySchedulerXml" method="closeOrder" cron="0 0 1 * * *" /> <!--每天凌晨一点执行  (0 0 1 * * *)--> 
    </task:scheduled-tasks> 
    <task:scheduled-tasks>  
        <!--这里表示的是每隔五秒(*/5 * * * * ?)做测试用 执行一次 如果有需要在下面依次添加task就可以-->  
        <task:scheduled ref="mySchedulerXml" method="acceptReissue" cron="*/30 * * * * ?" /> <!--每隔一个小时执行  (0 0 0/1 * * ?)--> 
    </task:scheduled-tasks>
    <!-- 自动扫描的包名 -->    
    <context:component-scan base-package="com.tianrong.springScheduler" />   
    <!-- 基于xml注解实现的定时配置 end -->   

    </beans>  

基于xml配置

/**
 * 
 */
package com.tianrong.springScheduler;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.collect.Maps;
import com.tianrong.common.util.DateUtil;
import com.tianrong.insurance.util.DateUtils;
import com.tianrong.order.action.OrderInterfaceAction;
import com.tianrong.order.entity.Order;
import com.tianrong.order.service.OrderService;
import com.tianrong.order.util.OrderStateEnum;
import com.tianrong.order.util.OrderSysStateEnum;
import com.tianrong.product.dao.AcceptReissueDao;
import com.tianrong.product.entity.AcceptReissue;
import com.tianrong.product.entity.BatchOrder;
import com.tianrong.product.service.BatchOrderService;
import com.tianrong.systemInterface.common.util.InterfaceConstant;
import com.tianrong.systemInterface.order.service.OrderApplyServer;
import com.tianrong.systemInterface.order.vo.OrderQueryRequest;
import com.tianrong.systemInterface.order.vo.OrderQueryResponse;
import com.tianrong.systemInterface.pay.vo.FileOrderInfo;
import com.tianrong.systemInterface.pay.vo.PayResponse;
import com.tianrong.utils.SortListUtil;

/**
 * @author Wolf
 *基于spring xml的定时器 
 */ 
@Component
public class MySchedulerXml { 
    private final static Logger logger = Logger.getLogger(MySchedulerXml.class);

    @Autowired(required = false) // 自动注入,不需要生成set方法了,required=false表示没有实现类,也不会报错。
    private OrderService orderService; 
    @Autowired(required = false) // 自动注入,不需要生成set方法了,required=false表示没有实现类,也不会报错。
    private BatchOrderService batchOrderService;
    @Autowired
    private AcceptReissueDao acceptReissueDao;

    /**
     * 赠险承保补发
     */
    public void acceptReissue() {
        logger.info("定时任务二A:同步订单承保失败单子,若成触发赠险,若败继续执行start");
        List<AcceptReissue> reList1 = acceptReissueDao.queryByList_status(InterfaceConstant.ins_Status4,
                InterfaceConstant.ins_Status3);
        if (reList1 != null && reList1.size() > 0) {
            for (int i = 0; i < reList1.size(); i++) {
                AcceptReissue acceptReissue = new AcceptReissue();
                acceptReissue = reList1.get(i);
                // 处理批量个人单和团体单
                Order order = new Order();
                order.setBatchNo(acceptReissue.getBussNo());
                List<Order> orderList = orderService.queryByList(order);
                if (orderList != null && orderList.size() > 0) {
                    for (Order bOrder : orderList) {
                        // 查询用户保单信息
                        String orderNo = bOrder.getOrderNo();
                        OrderQueryResponse respQuery = OrderApplyServer
                                .orderStatusQuery(new OrderQueryRequest(orderNo));
                        if (StringUtils.equals("10", respQuery.getHeader().getResultCode())
                                && StringUtils.isNotEmpty(respQuery.getOrderState())) {
                            // 同步订单数据状态
                            orderService.changeOrderStatus(orderNo, respQuery.getOrderBussStatus(),
                                    respQuery.getOrderState());
                            // 更新订单详情
                            orderService.changeOrderDetailStatus(orderNo, respQuery);
                            if ("4".equals(respQuery.getOrderBussStatus()) && "6".equals(respQuery.getOrderState())) {
                                batchOrderService.presendInsure(acceptReissue.getBussNo(), "10");
                            }
                        }
                    }

                }
            }

        }
        logger.info("定时任务二A:同步订单承保失败单子,若成触发赠险,若败继续执行end");

        logger.info("定时任务二B:同步订单承保成功赠险承保失败单子,若成触发赠险,若败继续执行start");
        try {
            List<AcceptReissue> reList = acceptReissueDao.queryByList_status(InterfaceConstant.ins_Status0,
                    InterfaceConstant.ins_Status3);

            if (reList == null || reList.size() == 0) {
                logger.info("定时任务二B:待承保订单的没有,定时任务");
                return;
            }
            for (AcceptReissue acceptReissue2 : reList) {
                batchOrderService.presendInsure(acceptReissue2.getBussNo(), "10");
                acceptReissue2.setCount(acceptReissue2.getCount() + 1);
                acceptReissueDao.update(acceptReissue2);
            }
        } catch (Exception e) {
            logger.info("定时任务二B:待承保订单定时任务出现异常");
        }
        logger.info("定时任务二B:同步订单订单承保成功赠险承保失败单子,若成触发赠险,若败继续执行end");
    }  
    public void closeOrder(){ 
        logger.info("定时任务一:********关闭所有定单开始");

        //同步远程数据库数据,修改本地数据库订单状态
        boolean ifture=synchronous_RemoteMI_DB();
        if(!ifture){
            logger.info("定时任务一:同步数据失败,请检查原因");
            return;
        }
        Order order=new Order();
        order.setOrderBussStatus(OrderStateEnum.FAIL.getValue());//
        order.setOrderStatus(OrderSysStateEnum.FAIL.getValue());
        order.setCreateDate(DateUtil.getFontDate(InterfaceConstant.yMdHms));
        logger.info("定时任务一:需要关闭的订单时间"+DateUtil.getFontDate(InterfaceConstant.yMdHms));
        List<Order> olist=orderService.queryNeedCloseOrderByList(order);
        if(olist==null||olist.size()==0){
            logger.info("定时任务一:没有可关闭的订单,定时任务已经关闭");
            return;
        }
        for (Order order2 : olist) {
            order2.setOrderBussStatus(OrderStateEnum.FAIL.getValue());
            order2.setOrderStatus(OrderSysStateEnum.FAIL.getValue());
            order2.setOrderInfo(InterfaceConstant.closeOrder_Status_0);
            orderService.update(order2);
        }
        logger.info("定时任务一:********关闭所有定单已经完成");
    }
    /**
     *以下方法支持的是批量处理批次表对应的订单表(业务订单单个承保)
     */
    private boolean synchronous_RemoteMI_DB() {

        //查出所有订单
        List<Order> aList=orderService.queryAll_Order_By_BatchNo_Realtive_BatchOrder();
        if(aList==null||aList.size()==0){
            return false;
        }
        //调用远程数据库同步数据状态
        for (int i=0;i<aList.size();i++) {
            // 查询用户保单信息
            String orderNo = aList.get(i).getOrderNo();
            OrderQueryResponse respQuery = OrderApplyServer.orderStatusQuery(new OrderQueryRequest(aList.get(i).getOrderNo()));
            if (respQuery == null || !StringUtils.equals("10", respQuery.getHeader().getResultCode())||StringUtils.isEmpty(respQuery.getOrderState())) {
                logger.info("查询用户保单信息,响应信息:" + respQuery.toString());
                continue;
            }
            //同步订单数据状态
            orderService.changeOrderStatus(orderNo, respQuery.getOrderBussStatus(), respQuery.getOrderState());
            // 更新订单详情
            orderService.changeOrderDetailStatus(orderNo, respQuery);
        }

        return true;
    }  
}  

注解配置

/**
 * 
 */
package com.tianrong.springScheduler;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;

/**
 * @author Wolf
 *基于注解的定时器 时间配置
 */

import org.springframework.scheduling.annotation.Scheduled;  
import org.springframework.stereotype.Component;

import com.tianrong.order.entity.Order;
import com.tianrong.order.service.OrderService;  

@Component  
public class MyTaskAnnotation {  
    @Autowired(required = false) // 自动注入,不需要生成set方法了,required=false表示没有实现类,也不会报错。
    private OrderService orderService; 
    /**  
     * 定时计算。每天凌晨 01:00 执行一次  
     */    
    @Scheduled(cron = "*/15 * * * * ?")   
    public void show(){  
        System.out.println("Annotation:is show run");
        Order order=new Order();
        List<Order> olist=orderService.queryByList(order);
        System.out.println("olist.size()"+olist.size());
    }  

    /**  
     * 启动时执行一次,之后每隔2秒执行一次  
     */    
    @Scheduled(fixedRate = 1000*2)   
    public void print(){  
        System.out.println("启动时执行一次,之后每隔2秒执行一次  ");  
    }  
} 


三、使用quartz实现


引用到的资料相关接口API
         JobStore 接口的 API 可归纳为下面几类: 
                Job 相关的 API 
                Trigger 相关的 API 
                Calendar 相关的 API 
                Scheduler 相关的 API 
调度工厂涉及到的对象
            一对象实例化配置唯一性
            二线程池配置
            三JobStore配置
            四集群配置
            五QuartzScheduler启动时间
            六设置自动启动
            七注册触发器
            八注册Jobdetail
            九引用监听器
热身概念图有个抽象了解
     ![这里写图片描述](https://img-blog.csdn.net/20160912235242806)                   
*********************1节较为健全详细配置**************************************
基于Spring的集群配置:
<!-- 调度工厂 --><bean id="quartzScheduler"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource"ref="dataSource" />
<property name="quartzProperties">
<props>
<prop key="org.quartz.scheduler.instanceName">CRMscheduler</prop>
<prop key="org.quartz.scheduler.instanceId">AUTO</prop>
<!-- 线程池配置 -->
<prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
<prop key="org.quartz.threadPool.threadCount">20</prop>
<prop key="org.quartz.threadPool.threadPriority">5</prop>
<!-- JobStore 配置 -->
<prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop>
<!-- 集群配置 -->
<prop key="org.quartz.jobStore.isClustered">true</prop>
<prop key="org.quartz.jobStore.clusterCheckinInterval">15000</prop>
<prop key="org.quartz.jobStore.maxMisfiresToHandleAtATime">1</prop>
<prop key="org.quartz.jobStore.misfireThreshold">120000</prop>
<prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop>
</props>
</property>
<property name="schedulerName"value="CRMscheduler" />
<!--必须的,QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动 -->
<property name="startupDelay"value="30" />
<property name="applicationContextSchedulerContextKey"value="applicationContextKey" />
<!--可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 -->
<property name="overwriteExistingJobs"value="true" />
<!-- 设置自动启动 -->
<property name="autoStartup"value="true" />
<!-- 注册触发器 -->
<property name="triggers">
<list>
<ref bean="userSyncScannerTrigger" />
           ......

</list></property>
<!-- 注册jobDetail -->
<property name="jobDetails">
<list></list></property>
<property name="schedulerListeners">
<list>
<ref bean="quartzExceptionSchedulerListener" />
</list>
</property>
</bean>


org.quartz.jobStore.class属性为JobStoreTX,将任务持久化到数据中。因为集群中节点依赖于数据库来传播Scheduler实例的状态,你只能在使用JDBC JobStore时应用Quartz集群。

org.quartz.jobStore.isClustered属性为true,通知Scheduler实例要它参与到一个集群当中。

org.quartz.jobStore.clusterCheckinInterval属性定义了Scheduler实例检入到数据库中的频率(单位:毫秒)。Scheduler检查是否其他的实例到了它们应当检入的时候未检入;这能指出一个失败的Scheduler实例,且当前 Scheduler会以此来接管任何执行失败并可恢复的Job。通过检入操作,Scheduler 也会更新自身的状态记录。clusterChedkinInterval越小,Scheduler节点检查失败的Scheduler实例就越频繁。默认值是 15000 (即15 秒)。

其余参数在后文将会详细介绍。
*********************2节结合当前项目详细配置说明**************************** 
   一对象实例化配置唯一性

图析:
![这里写图片描述](https://img-blog.csdn.net/20160912235343041)
每两个小时
0 */2 * * * 
Job被创建后,可以保存在Scheduler中,与Trigger是独立的,同一个Job可以有多个Trigger;附录解释1如下
一个trigger只能触发一个job
同一个触发器组下面的不同的触发器名字必须是唯一的如下图
![这里写图片描述](https://img-blog.csdn.net/20160912235424932)
将Job和Trigger注册到Scheduler时,可以为它们设置key,配置其身份属性。Job和Trigger的key,同一个分组下的Job或Trigger 的名称必须唯一,即一个Job或Trigger的key由名称(name)和分组(group)组成。
这种松耦合的另一个好处是,当与 Scheduler中的Job关联的trigger都过期时,可以配置Job稍后被重新调度,而不用重新定义Job;
还有,可以修改或者替换 Trigger,而不用重新定义与之关联的Job。

    二线程池配置

线程池的相关属性的意义附录解释如下:
1、线程池里至少有一个线程2、线程池里最多有10个意味着为了提高最多有10个线程附录说明@A.
3、固定大小线程池4、线程的时间()
@A
使用大量线程时要小心,因为JVM,操作系统和CPU处理大量线程时很耗时,并且,线程管理也会导致性能下降。在多数情况下,性能开始下降是因为使用多达数百个线程。如果在应用服务器中运行程序,要留心应用服务器本身已经创建了不少的线程。 除了这些因素外,还同要执行的任务有关,如果任务需要花费很长时间才能完成它们的工作,并且(或者)它们的工作很消耗CPU,那么很明显不要同时运行多个任务,也不要在一个时间段内运行过多这样的任务。
数据库dB配置以mySql为例

      ![这里写图片描述](https://img-blog.csdn.net/20160912235621166)          
    三JobStore配置
     四集群配置
       ![这里写图片描述](https://img-blog.csdn.net/20160912235533041)             
图析
常见的就不一一例举了特殊的附录如下:
1-1
总结:就是将每个任务错开时间执行,造成每次实际执行时间与安排时间
错位,misfireThreshold只有当job任务被阻塞时才有效,如果线程池里线程很多,该参数没有意义。所以大部分时候只对有状态的job才有意义。
1-2  通过设置"org.quartz.jobStore.isClustered"属性为"true"来激活集群特性
![这里写图片描述](https://img-blog.csdn.net/20160912235652292)
1-3:

            五QuartzScheduler启动时间
            六设置自动启动
                ![这里写图片描述](https://img-blog.csdn.net/20160912235729683)           
            七注册触发器
            八注册Jobdetail
            九引用监听器
            ![这里写图片描述](https://img-blog.csdn.net/20160912235753965)
其实归纳起来就是一个scheduler容器里面放置了线程池,每一个线程都会去调度一个触发器,而这个触发器对应着一个特定功能的job任务,如果这个job为了保险起见可以多配置一个触发器(1个job对应多个触发器).然后线程是多个的所以触发器可以多个,触发器多个,所以job是多个。抽象概念图如下,加深记忆。
这次再看理解的就差不多了。
![这里写图片描述](https://img-blog.csdn.net/20160912235821043)

关于cron表达式(来自网络):
Cron 表达式包括以下 7 个字段:


小时
月内日期

周内日期
年(可选字段)
特殊字符
Cron 触发器利用一系列特殊字符,如下所示:
反斜线(/)字符表示增量值。例如,在秒字段中“5/15”代表从第 5 秒开始,每 15 秒一次。

问号(?)字符和字母 L 字符只有在月内日期和周内日期字段中可用。问号表示这个字段不包含具体值。所以,如果指定月内日期,可以在周内日期字段中插入“?”,表示周内日期值无关紧要。字母 L 字符是 last 的缩写。放在月内日期字段中,表示安排在当月最后一天执行。在周内日期字段中,如果“L”单独存在,就等于“7”,否则代表当月内周内日期的最后一个实例。所以“0L”表示安排在当月的最后一个星期日执行。

在月内日期字段中的字母(W)字符把执行安排在最靠近指定值的工作日。把“1W”放在月内日期字段中,表示把执行安排在当月的第一个工作日内。

井号(#)字符为给定月份指定具体的工作日实例。把“MON#2”放在周内日期字段中,表示把任务安排在当月的第二个星期一。

星号(*)字符是通配字符,表示该字段可以接受任何可能的值。
字段 允许值 允许的特殊字符
秒 0-59 , - * /
分 0-59 , - * /
小时 0-23 , - * /
日期 1-31 , - * ? / L W C
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * ? / L C #
年(可选) 留空, 1970-2099 , - * /
表达式意义
“0 0 12 * * ?” 每天中午12点触发
“0 15 10 ? * *” 每天上午10:15触发
“0 15 10 * * ?” 每天上午10:15触发
“0 15 10 * * ? *” 每天上午10:15触发
“0 15 10 * * ? 2005” 2005年的每天上午10:15触发
“0 * 14 * * ?” 在每天下午2点到下午2:59期间的每1分钟触发
“0 0/5 14 * * ?” 在每天下午2点到下午2:55期间的每5分钟触发
“0 0/5 14,18 * * ?” 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
“0 0-5 14 * * ?” 在每天下午2点到下午2:05期间的每1分钟触发
“0 10,44 14 ? 3 WED” 每年三月的星期三的下午2:10和2:44触发
“0 15 10 ? * MON-FRI” 周一至周五的上午10:15触发
“0 15 10 15 * ?” 每月15日上午10:15触发
“0 15 10 L * ?” 每月最后一日的上午10:15触发
“0 15 10 ? * 6L” 每月的最后一个星期五上午10:15触发
“0 15 10 ? * 6L 2002-2005” 2002年至2005年的每月的最后一个星期五上午10:15触发
“0 15 10 ? * 6#3” 每月的第三个星期五上午10:15触发
每天早上6点
0 6 * * *
每两个小时
0 /2 * *
晚上11点到早上8点之间每两个小时,早上八点
0 23-7/2,8 * * *
每个月的4号和每个礼拜的礼拜一到礼拜三的早上11点
0 11 4 * 1-3
1月1日早上4点
0 4 1 1 *
代码直接下载吧到github
加群:300458205

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小诚信驿站

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值