Quartz介绍
Quartz 是一个完全由 Java 编写的开源作业调度框架,不要让作业调度这个术语吓着你,其实不难。尽管 Quartz 框架整合了许多额外功能,但就我们使用来说,你会发现它易用得简直让人受不了!
简单来说,任务调度就是在指定时间做指定的事,我们用Quartz执行任务必须的几个条件,一个是调度器,二是执行的任务,三是触发器(可以理解为设置闹钟)。连在一起就是我们使用调度器将任务和触发器绑定在一起执行,以达到在指定的时间点执行或循环执行任务。
接下来直接上代码,我是小白,所以代码会有点粗糙,如果有什么错误或者不当之处,请大家批评指正!
开发场景
公司之前交给我一个需求:根据不同的下载协议(SFTP,FTP,HTTPS),去下载道琼斯名单数据。要求一个协议绑定一个线程去下载,并且可以并发执行。
代码
//这是我绑定线程的方法
public static void bindThread(Constance c) throws Exception{
// 定义一个 Schedule,用于绑定任务和触发器
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
//下面就是判断是哪些文件传输协议
if (c.getProtocol().equalsIgnoreCase("sftp")) {
JobDetail sftpJobDetail = JobBuilder.newJob(SFTPDownloadJob.class).withIdentity("job1", "group1").build();
Trigger sftptrigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(0)).build();
sftpJobDetail.getJobDataMap().put("data",c);
scheduler.scheduleJob(sftpJobDetail, sftptrigger);
scheduler.start();
} else if (c.getProtocol().equalsIgnoreCase("ftp")) {
JobDetail FTPJobDetail = JobBuilder.newJob(FTPDownloadJob.class).withIdentity("job2", "group2").build();
Trigger FTPtrigger = TriggerBuilder.newTrigger().withIdentity("trigger2", "group2").startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(0)).build();
FTPJobDetail.getJobDataMap().put("data",c);
scheduler.scheduleJob(FTPJobDetail, FTPtrigger);
scheduler.start();
}else if (c.getProtocol().equalsIgnoreCase("https")) {
JobDetail HTTPSJobDetail = JobBuilder.newJob(HTTPSDownloadJob.class).withIdentity("job3", "group3").build();
Trigger HTTPStrigger = TriggerBuilder.newTrigger().withIdentity("trigger3", "group3").startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(0)).build();
HTTPSJobDetail.getJobDataMap().put("data",c);
scheduler.scheduleJob(HTTPSJobDetail, HTTPStrigger);
scheduler.start();
} else {
throw new Exception("未知的第三方协议!");
}
}
接下来对上面的代码进行分析:
JobDetail sftpJobDetail = JobBuilder.newJob(SFTPDownloadJob.class).withIdentity("job1", "group1").build();
1、JobDetail,为什么需要它呢,都写在脸上了,工作详情嘛!我们执行任务需要知道任务的名称啊,分组之类的,这样打出来的 log 也好分辨。所以会在withIdentity中给它命名分组。
这个 JobDetail 是和哪个 Job 绑定的呢?我们还要知道 Class 信息,所以我们需要传入SFTPDownloadJob.class,根据不同的协议,创建不同的Job(这里下面会有说),然后把**Job.class传入到newJob中就可以啦。在任务执行的时候可能还需要传参,这些都会封装在 JobDetail 中。
Trigger sftptrigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(0)).build();
2、同样的创建风格也用在触发器身上,想想看我们要设置一个闹钟,我们要设置开始执行的时间,执行的频率,执行的次数,这些一次性搞定,若是你英文还可以,看这些方法应该都明白了。
上面是设置一个简单的任务调度,如果我们想灵活的设置时间该怎么办呢?可以用Cron 表达式去编写你的规则,如:
CronTrigger cronTrigger = (CronTrigger)TriggerBuilder.newTrigger().withIdentity("trigger1", "group").startNow()
.withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *")).build();
至于Cron 表达式怎么写,这里不多说,因为不需要自己写,我们可以用在线生成工具来生成他们。你只要百度搜索Cron 表达式在线生成器即可。当然,你如果对Cron表达式感兴趣或者你喜欢钻研,你可以去网上了解它的更多信息。
sftpJobDetail.getJobDataMap().put("data",c);
scheduler.scheduleJob(sftpJobDetail, sftptrigger);
scheduler.start();
这三行代码,第一行是向Job(这个下面会说)里面put数据。第二、第三行是调度器绑定任务和触发器并开始执行。
到这里为止我们就已经搞定了最牛逼的定时任务了。然后下面我们来看一下我们Job的创建。下面我以SFTP为例,其他两种文件下载协议在这里其实也都是异曲同工。
//创建一个**Job需要实现Job接口
public class SFTPDownloadJob implements Job {
//这是日志,忽略忽略
private Logger log = null;
//构造方法
public SFTPDownloadJob(){
}
//这里就是必须要实现的方法了,我们在这里写逻辑
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap data = context.getJobDetail().getJobDataMap();
Constance c = (Constance) data.get("data");
PropertyConfigurator.configure(c.getLOG4J_CONFIGFILE_PATH());
log = Logger.getLogger(FTPDownloadThread.class);
log.debug("定时任务执行......");
SFTPDownloadThread sftp = new SFTPDownloadThread(c);
new Thread(sftp).start();
}
}
继续分析、继续分析!
JobDataMap data = context.getJobDetail().getJobDataMap();
Constance c = (Constance) data.get("data");
这两行代码主要是get从JobDetail put过来的Map数据,然后put过来时是什么类型接收就得是什么类型。
PropertyConfigurator.configure(c.getLOG4J_CONFIGFILE_PATH());
log = Logger.getLogger(FTPDownloadThread.class);
log.debug("定时任务执行......");
这里是写日志嗷,不用管不用管。๑乛◡乛๑
SFTPDownloadThread sftp = new SFTPDownloadThread(c);
new Thread(sftp).start();
这里是自己写的文件下载线程,把它放在Job类中启动是因为要给它设置定时规则。
然后这里重要介绍的就是Quartz,至于这个文件下载线程我下面把代码放出来,就不做详细解释了,嗷对了,使用Quartz之前记得将依赖导入项目哦。
public class SFTPDownloadThread implements Runnable {
private Logger log = null;
private Constance c = null;
public SFTPDownloadThread(Constance constance) {
PropertyConfigurator.configure(constance.getLOG4J_CONFIGFILE_PATH());
log = Logger.getLogger(FTPDownloadThread.class);
this.c = constance;
}
/**
* 根据文件名称校验文件是否在目录中存在
* 当文件存在且可读时返回true
* @param fileName
* @param saveDir
* @return Boolean
*/
private boolean fileNameCheck(String fileName, String saveDir) {
File dir = new File(saveDir);
if (!dir.exists()) {
return false;
}
File file = new File(dir, fileName);
if (file.exists() && file.canRead()) {
return true;
}
return false;
}
/**
* 根据配置获取本地存储文件名称
* 目前配置支持的表达式:{DATE}==>yyyyMMddHHmmss;{FILENAME}==>远程文件原始名称
* @param remoteFileName
* @param config
* @return
*/
private String getSaveFileName(String remoteFileName) {
if (StringUtils.isBlank(c.getSaveFileNameFormat())) {
return remoteFileName;
}
String saveFileName = c.getSaveFileNameFormat();
saveFileName = saveFileName.replace("{FILENAME}", remoteFileName.substring(0, remoteFileName.lastIndexOf(".")));
saveFileName = saveFileName.replace("{DATE}", new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()));
return saveFileName;
}
@SuppressWarnings("unchecked")
@Override
public void run() {
Channel channel = null;
ChannelSftp chSftp = null;
Session session = null;
try {
JSch jsch = new JSch();
session = jsch.getSession(c.getFTP_USERID(), c.getHost(), c.getFTP_PORT());
session.setPassword(c.getFTP_PASSWD());
session.setTimeout(c.getFTP_TIMEOUT());
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
channel = session.openChannel("sftp");
channel.connect();
chSftp = (ChannelSftp) channel;
// 获取文件列表
Vector<LsEntry> entrys = chSftp.ls(c.getFTP_REMOTEIR());
// 切换工作目录
if (StringUtils.isNotEmpty(c.getFTP_REMOTEIR())) {
chSftp.cd(c.getFTP_REMOTEIR());
}
for (Iterator<LsEntry> it = entrys.iterator(); it.hasNext();) {
LsEntry entry = it.next();
// 抛弃目录
if (entry.getAttrs().isDir()) {
continue;
}
// 抛弃链接
if (entry.getAttrs().isLink()) {
continue;
}
String remoteFileName = entry.getFilename();
// 文件名称过滤
if (StringUtils.equals(FILTERTYPE_FILENAME, c.getFileFilterType())) {
if (!StringUtils.equals(remoteFileName, c.getFilterStr())) {
log.info("根据文件名称过滤文件:"+ remoteFileName);
continue;
}
} else if (StringUtils.equals(FILTERTYPE_REGEX, c.getFileFilterType())) {
if (!remoteFileName.matches(c.getFilterStr())) {
log.info("根据文件名称正则过滤文件:"+remoteFileName);
continue;
}
}
// 文件验重
if (StringUtils.equalsIgnoreCase(CHECKTYPE_FILENAME, c.getCheckType())) {
if (fileNameCheck(remoteFileName, c.getFTP_LOCALDIR())) {
log.info("根据文件名过滤文件:"+ remoteFileName);
continue;
}
} else if (StringUtils.equals(CHECKTYPE_CHANGEDATE, c.getCheckType())) {
log.debug("时间字符串......"+MyMethods.fileRead(c.getFTP_TYPE()));
String mTime = String.valueOf(entry.getAttrs().getMTime());
if (StringUtils.equals(mTime, MyMethods.fileRead(c.getFTP_TYPE()))) {
log.info("根据文件更新时间过滤文件:"+ remoteFileName);
continue;
}
//写入本地checkStr
MyMethods.fileWrite(c.getFTP_TYPE(), mTime);
log.debug("时间字符串2......"+MyMethods.fileRead(c.getFTP_TYPE()));
}
// 获取存储文件名称
String saveFileName = getSaveFileName(remoteFileName);
boolean downloadStatus = false;
// 下载
OutputStream os = null;
try {
log.debug(String.format("==========开始下载文件:【%s】【%s】==========", remoteFileName, saveFileName));
os = new FileOutputStream(new File(c.getFTP_LOCALDIR(), saveFileName));
chSftp.get(remoteFileName, os);
// 下载完成后创建OK标识文件
File okFile = new File(c.getFTP_LOCALDIR(), saveFileName + ".OK");
if (!okFile.exists()) {
okFile.createNewFile();
}
// 更新下载状态及校验串
//utils.saveConfigStatus(config);
downloadStatus = true;
log.debug(String.format("==========文件下载成功:【%s】【%s】==========", remoteFileName, saveFileName));
} catch (SftpException e) {
throw e;
} finally {
if (null != os) {
os.close();
}
// 下载逻辑未成功时,删除临时文件及相应的标识文件
if (!downloadStatus) {
File dataFile = new File(c.getFTP_LOCALDIR(), saveFileName);
if (dataFile.exists()) {
dataFile.delete();
}
File okFile = new File(c.getFTP_LOCALDIR(), saveFileName + ".OK");
if (okFile.exists()) {
okFile.deleteOnExit();
}
}
}
}
}catch(Exception e){
log.error("数据文件下载异常", e);
e.printStackTrace();
}finally{
if (null != chSftp) {
chSftp.quit();
}
if (null != channel) {
channel.disconnect();
}
if (null != session) {
session.disconnect();
}
}
}
}
好了,到这我的分享就结束了。然后可能因为水平有限会被大佬们发现一些错误,请大家批评指正!如果有觉得这篇文章能帮到你或者感觉还不错的麻烦点个小赞!感谢感谢。
如有侵权,联系立删!