1.情景展示
在实际项目开发过程中,往往会存在这样的需求:
定时执行某个任务,如何实现?
2.具体分析
定时任务,其实就是定时调用。
在代码中,我们可以通过定时运行某个类的某个方法来实现。
具体实现方式,有两种:
一种是通过java实现。
另一种是借助spring来实现。
本文只说java实现方式。
3.解决方案
通过java原生代码,有两种实现方式。
方式一:使用java.util.Timer
类
Timer
类用于在指定时间间隔后执行任务一次或重复执行。它不能保证任务在准确的时间执行,但能保证任务至少不会比指定的时间间隔提前执行。
这是百度AI给出的示例。
此定时类无法在指定时间运行的话,就显得十分鸡肋了。
故而,不推荐大家使用这种方法。
方式二:使用java.util.concurrent.ScheduledExecutorService
ScheduledExecutorService
接口用于在给定的延迟后运行命令,或者定期执行命令。它比Timer
类更强大,提供了更多的调度选项。
每天早上8点,执行任务(定时上传)。
UploadEveryDay.java
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import base.web.tools.DateUtils;
public class UploadEveryday {
private Logger log = Logger.getLogger(this.getClass());
/**
* 为Runnable填加一个方便其它类调用的壳子(方法)
*/
public void upload() {
Runnable task = new Runnable() {
/**
* 任务内容
* @explain 每天都会上传24小时以内的数据
* @description 将具体要运行的任务内容写在本方法里(run())
*/
public void run() {
// task to run goes here
log.info("任务执行时间为:" + DateUtils.getSysdateStr("yyyy-MM-dd HH:mm:ss"));
try {
UploadTask ut = new UploadTask();
// 上传24小时数据
ut.searchParamsCode("2");
} catch (Exception e) {
log.error("数据上传失败!");
log.error(e.getMessage());
e.printStackTrace();
}
}
};
/**
* 计算距离当前时间,还有多长时间执行任务! 如果不需要可以删除
*/
Calendar calendar = Calendar.getInstance();
// 获取当前日期
String curDateStr = DateUtils.getSysdateStr("yyyy-MM-dd");
// 系统当前时间所对应的毫秒数
long curMillis = calendar.getTimeInMillis();
// 获取系统当前是几点
int curHour = calendar.get(calendar.HOUR_OF_DAY);// 24小时制
// 指定要执行的时间(早上8点)
int specHour = 8;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH");
// 任务执行的年月日+小时
Date setDate = null;
// 任务执行时所对应的毫秒数
long setMillis = 0;
// 执行时间
long exeTime = 0;
try {
if (curHour <= specHour) {// 当前时间在specHour:00之前
// 将指定字符串转换成日期
setDate = sdf.parse(curDateStr + " " + specHour);
setMillis = setDate.getTime();
// 还差多长时间
exeTime = setMillis - curMillis;
} else {// 当前时间在specHour:00之后
// 后一天:当前日期+1
calendar.add(Calendar.DAY_OF_MONTH, 1);
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd");
String setDateStr = sdf2.format(calendar.getTime());
// 将指定字符串转换成日期
setDate = sdf.parse(setDateStr + " " + specHour);
setMillis = setDate.getTime();
// 还差多长时间
exeTime = setMillis - curMillis;
}
} catch (ParseException e) {
log.error("数据上传失败!");
log.error(e.getMessage());
e.printStackTrace();
return;// 结束方法运行
}
// ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
// 获取任务执行器
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
// 延迟5秒后执行一次任务
// executorService.schedule(task, 5, TimeUnit.SECONDS);
// 线程/延迟执行时间/多长时间执行一次/时间单位(第二个参数如果为负数,会立即执行)
executorService.scheduleAtFixedRate(task, exeTime, 24 * 60 * 60 * 1000, TimeUnit.MILLISECONDS);
// 时间单位
String timeUnit = "";
// 首次执行剩余时间
int remaTime = 0;
// Java整数间的除法运算,默认只保留整数位
double hours = exeTime / 1000 / 3600;
double minutes = exeTime / 1000 / 60;
double seconds = exeTime / 1000;
// hours,minutes,seconds的结果只可能为0.0/1.0/2.0等(即小数位永远为0),不可能为:0.1/1.1/2.1
if (hours > 0) {
remaTime = (int) hours;
timeUnit = "小时";
} else if (minutes > 0) {
remaTime = (int) minutes;
timeUnit = "分钟";
} else {
remaTime = (int) seconds;
timeUnit = "秒";
}
log.info("启动定时器...UploadEveryday...距离任务执行还有" + remaTime + timeUnit + "!");
}
}
运行的任务内容详情:(具体执行任务的类)
UploadTask.java
查看代码
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import com.sinosoft.ie.usecard.client.UploadClient;
import com.sinosoft.ie.usecard.client.UsingCardMessage;
import base.service.bo.dataUpload.IBoVIRTUAL_CARD;
import base.service.domain.exception.NoSuchBeanException;
import base.service.domain.tools.BeansHelp;
import base.web.actions.BaseAction;
public class UploadTask extends BaseAction {
private Logger logger = Logger.getLogger(this.getClass());
private IBoVIRTUAL_CARD boVIRTUAL_CARD;
public UploadTask() {
try {
// 虚拟卡系统
boVIRTUAL_CARD = (IBoVIRTUAL_CARD) BeansHelp.getBeanInstance("boVIRTUAL_CARD");
} catch (NoSuchBeanException e) {
logger.error(e.getMessage());
}
}
/**
* 数据上传
* @explain
*/
public void dataUpload() {
UploadAllData uad = new UploadAllData();
// 1.项目启动后,上传所有未上传的数据
boolean isOver = uad.upload();
// 2.上传结束,执行定时上传任务
if (isOver) {
UploadEveryday ue = new UploadEveryday();
ue.upload();
}
}
/**
* 查询条件配置
* @explain
* @param model 1-上传所有;2-上传24小时
* @throws Exception
*/
public void searchParamsCode(String model) throws Exception {
Map<String, String> paramsMap = new HashMap<>(5);
// 上传所有
paramsMap.put("model", model);
// 统计所有未上传的数据(计数)
Map<String, BigDecimal> resultMap = boVIRTUAL_CARD.getUploadVIRTUAL_CARDDataCount(paramsMap);
// 需要上传总数
long total = resultMap.get("TOTAL").longValue();
// 需要上传数据的id-最小
long minValue = resultMap.get("MINVALUE").longValue();
// 需要上传数据的id-最大
long maxValue = resultMap.get("MAXVALUE").longValue();
int targetSize = 100;
// 上升为double类型,供运算使用
double targetSize2 = targetSize;
// 需要从数据库读取数据的次数(非整数+1)每次从数据库查询100条数据
int databaseTimes = (int) Math.ceil(total / targetSize2);
// 查询分页数据
int start = 0;
int end = 0;
// orcle会自动将int-->String的转换
String s_start = "";
String s_end = "";
// 上传24小时数据(已经添加到了sql中,所以这里不用再配置时间条件)
log.info("总共需要上传:" + databaseTimes + "次!");
// 1.100条一读取,然后上传
for (int i = 1; i <= databaseTimes; i++) {
start = (i - 1) * targetSize + 1;
end = i * targetSize;
s_start = String.valueOf(start);
s_end = String.valueOf(end);
paramsMap.put("START", s_start);
paramsMap.put("END", s_end);
// 循环上传
uploadCommonCoreCode(paramsMap);
log.info("累计上传成功:" + end + "条!待上传数据剩余:" + (total - end) + "条!");
}
log.info("数据已上传完毕,共计:" + total + "条!");
// 2.数据上传完毕,批量更新
paramsMap.clear();
paramsMap.put("minValue", String.valueOf(minValue));
paramsMap.put("maxValue", String.valueOf(maxValue));
paramsMap.put("isUpload", "1");
// 批量更新:已上传标识
boVIRTUAL_CARD.updateVIRTUAL_CARDLOG(paramsMap);
log.info("数据已批量更新完毕,共计:" + total + "条!");
}
/**
* 数据上传核心代码
* @explain
* @param paramsMap
* @throws Exception
*/
public void uploadCommonCoreCode(Map<String, String> paramsMap) throws Exception {
log.info("开始执行上传....................");
// 最多有100条数据
List<Map<String, String>> dataList = boVIRTUAL_CARD.getUploadVIRTUAL_CARDData(paramsMap);
int num = 0;
List<UsingCardMessage> usingCardMessageList = new ArrayList<>();
UsingCardMessage msg = null;
// 循环插入
for (int i = 0; i < dataList.size(); i++) {
num++;
Map<String, String> cardMap = dataList.get(i);
msg = new UsingCardMessage();
// 用卡时间
msg.setTime(cardMap.get("TIME"));
// 电子健康卡
msg.setCardType("0");
// 身份证号
msg.setAtr(cardMap.get("ATR"));
// 发卡机构代码
msg.setIssueOrgCode(cardMap.get("ISSUEORGCODE"));
// 发卡机构名称
msg.setIssueOrgName(cardMap.get("ISSUEORGNAME"));
// 健康卡卡号
msg.setHcNumber(cardMap.get("HCNUMBER"));
// 扫码枪终端标识号
msg.setSam(cardMap.get("SAM"));
// 用卡城市代码
msg.setUseCityCode(cardMap.get("USECITYCODE"));
// 用卡城市名称
msg.setUseCityName(cardMap.get("USECITYNAME"));
// 民族代码
msg.setNation(cardMap.get("NATION"));
// 身份证号
msg.setIDCard(cardMap.get("IDCARD"));
// 医疗机构代码
msg.setHospitalCode(cardMap.get("HOSPITALCODE"));
// 医疗机构名称
msg.setHospitalName(cardMap.get("HOSPITALNAME"));
// 刷卡终端类型编号
msg.setChannelCode(cardMap.get("CHANNELCODE"));
// 刷卡终端类型
msg.setChanelName(cardMap.get("CHANELNAME"));
// 诊疗环节代码
msg.setMedStepCode(cardMap.get("MEDSTEPCODE"));
// 诊疗环节名称
msg.setMedStepName(cardMap.get("MEDSTEPNAME"));
usingCardMessageList.add(msg);
// 100条上传1次:不足100条,将不走该条件
if (num % 100 == 0) {
log.info("上传中......1");
// 批量执行预定义SQL
UploadClient.upload(usingCardMessageList);
usingCardMessageList.clear();
}
}
// 不足100条数据将走该条件
if (!usingCardMessageList.isEmpty()) {
log.info("上传中......2");
UploadClient.upload(usingCardMessageList);
}
}
}
UploadAllData.java
package base.web.actions.upload;
import org.apache.log4j.Logger;
/**
* 上传所有数据
* @explain 所有没有上传的数据
* @author Marydon
* @creationTime 2019年6月13日下午4:29:19
* @version 1.0
* @since
* @email [email protected]
*/
public class UploadAllData {
private Logger log = Logger.getLogger(this.getClass());
/**
* 上传所有数据
* @explain 上传所有未上传的数据
* @return 是否上传结束
*/
public boolean upload() {
// 是否结束
boolean isOver = false;
try {
UploadTask ut = new UploadTask();
// 上传所有数据
ut.searchParamsCode("1");
isOver = true;
} catch (Exception e) {
log.error("数据上传失败!");
log.error(e.getMessage());
e.printStackTrace();
}
return isOver;
}
}
4.定时任务启动方式
让定时任务生效的时机,也就是什么时候调此定时任务呢?
一般情况下,我们会在项目启动的时候就会让定时任务生效。
我们可以通过Servlet启动。
第一步:创建Servlet
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
/**
* web项目启动后,调用该类
* @explain
* @author Marydon
* @creationTime 2019年4月3日下午2:21:51
* @version 1.0
* @since
* @email [email protected]
*/
public class TaskExecutor extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* tomcat容器启动后,将会调用该方法
*/
public void init() throws ServletException {
super.init();
// 项目启动后,立即执行上传任务
UploadTask sb = new UploadTask();
// 数据上传
sb.dataUpload();
}
}
第二步:将其配置到web.xml当中
<!-- 服务器一启动,就执行Java类 -->
<!-- 将数据上传到国家数据中心 -->
<servlet>
<servlet-name>uploadServlet</servlet-name>
<servlet-class>base.web.actions.upload.TaskExecutor</servlet-class>
<!-- 被加载到servlet容器的优先级 -->
<load-on-startup>1</load-on-startup>
</servlet>
5.拓展
另外两种定时任务实现方式
方式三:使用Spring的@Scheduled
注解
更简单的定时任务实现方式和调用方式,自然是:
利用spring组件来实现,这也是我们目前使用的最佳方式。
使用@Scheduled
注解来创建定时任务。这种方式需要在Spring配置类中启用定时任务。
方式四:使用Quartz库
Quartz是一个开源的全功能的在Java环境中使用的作业调度服务。它允许你创建复杂的调度规则,如每周、每天、每小时或每分钟执行一次任务。Quartz使用起来比较复杂,但提供了丰富的调度功能。
写在最后
哪位大佬如若发现文章存在纰漏之处或需要补充更多内容,欢迎留言!!!