描述
我们在部署系统开启定时任务之后,当定时任务的业务代码模块出现异常,我们往往不能及时查看,还必须在服务器的log日志文件里查找,这对于运维来说极其不方便。故需要一个能及时捕捉定时任务异常的功能,并能在后台界面显示。参考若依系统定时任务模块代码
也会存在对一个集合的数据遍历处理,却因为该集合的一条数据异常导致整个集合数据事务回滚的情况,以下内容也针对这种情况进行改善。
工具类 AbstractQuartzJob
在之前的定时任务中,我们都是通过对每一个定时任务指定的类实现定时任务的接口去执行业务代码,这就显得定时任务很分散,无法集中管理,AbstractQuartzJob配置类就是对各个定时任务集中管理的工具类,从而实现监控各个定时任务。
主要方法
@Override public void execute(JobExecutionContext context) throws JobExecutionException { /** 获取定时任务相关信息**/ QuartzJob job = new QuartzJob(); BeanUtils.copyProperties(context.getMergedJobDataMap().get(JobConstants.TASK_PROPERTIES),job); try { before(context, job);//定时任务执行前 doExecute(context, job);//定时任务执行 // after(context, job, null);//定时任务执行后,记录成功日志,因主要记录异常日志故注释掉了,若有需要解开注释 } catch (InvocationTargetException e) { String errorMessage = e.getTargetException().getMessage();//获取异常信息 log.error("定时任务执行异常:{}",errorMessage); after(context, job, errorMessage);//定时任务执行后,记录异常日志 }catch (Exception ex) { String errorMessage = ex.getMessage();//获取异常信息 log.error("定时任务执行异常:{}", errorMessage); after(context, job, errorMessage);//定时任务执行后,记录异常日志 } }
定时任务实现工具类 QuartzJobExecution
public class QuartzJobExecution extends AbstractQuartzJob{ @Override protected void doExecute(JobExecutionContext context, QuartzJob job) throws Exception { JobInvokeUtils.invokeMethod(context,job); } }
描述:根据定时任务的相关信息,获取定时任务下的业务实现类,从而调用该类下的execute方法
实现方法 execute
例:
@Slf4j @Component public class AgvTaskAutoUpdateJob { /** * 任务是否正在执行标记 :false--未执行; true--正在执行; 默认未执行 */ private static volatile boolean isRun = false; public void execute(){ // 任务正在执行,跳过本次执行 if (isRun) { log.info("定时任务——agv任务表:前一次未执行完,跳过本次任务!"); return; } isRun = true; try{ log.info(String.format("定时任务——agv任务表! 时间:" + DateUtils.getTimestamp())); SpringUtils.getBean(StudentService.class).method1(); }finally { isRun = false; } } }
为什么在类中定义 isRun常量呢?
该常量的作用主要是类似于锁的作用。在实际情况中,会由于网络的原因导致业务代码的处理时间大于定时任务每次处理时间的间隔,这就会出现重复执行定时任务的情况,故而在这里定义一个可见性的常量,当上一条定时任务还未执行完成时会跳过本次定时任务。
为什么不用Lock和synchronized?
使用Lock最终解锁需要使用unLock,但是需要利用try,这样会捕捉到异常,无法将异常返回给AbstractQuartzJob工具类,从而会导致无法记录异常日志。使用synchronized会出现死锁的情况,会阻塞下一个定时任务。
异常抛出
1、普通使用(一条记录异常,整个任务异常抛出)
在业务代码异常处直接抛出异常即可
throw new PracticBootException("错误信息");//也可其它异常信息
2、针对使用(一条记录异常,只针对该条记录进行异常抛出,对其它记录不影响,包括事务回滚)
需要借用外层方法嵌套内层方法(会抛出异常的业务代码)
例:
@Override public void method1() { List<Student> studentList = studentService.list(); /**循环内需要处理每条记录的异常,以保证其它循环记录能正常执行*/ for(Student student : studentList){ String errorMsg = this.method2(); if(StringUtils.isNotEmpty(errorMsg)){ throw new PracticBootException(errorMsg);//若内层方法捕捉到的异常信息不为空,抛出异常,给QuartzJobExecution工具类捕捉,记录异常日志 } } } @DS("数据源")//用于开启一个新的事务,每条记录拥有不同的事务,互不影响 public Sting merhod2(){ String errorMsg = ""; try{ .............. }catch(Exception e){ if(StringUtils.isNotEmpty(e.getMessage())){ errorMsg = e.getMessage(); }else { errorMsg = "系统异常";//为防止出现异常信息为空的情况,故手动将这些信息为空的异常归类为系统异常 }//捕捉到异常,记录异常 }finally{ return errorMsg;//将异常信息返回给外层方法 } }
3、特殊使用(当单条记录异常次数大于N次后跳过,不再记录异常日志)
上面方法可以看出,每执行一次定时任务,若异常记录一直没有被修正,异常日志就会一直记录,从而使日志的数据记录过于重复显示,且浪费了数据库空间。故优化如下:
1、首先需要在实体类中加入一个字段,用来记录单条数据出现的异常次数
/** 异常次数 */ @ApiModelProperty(value = "异常次数") private Integer errorNumber;
2、以大于3次不记录异常日志为例,示例代码:
@Override public void method1() { List<Student> studentList = studentService.list(); /**循环内需要处理每条记录的异常,以保证其它循环记录能正常执行*/ for(Student student : studentList){ Student student = this.method2(student); //内层方法返回的对象里的异常次数大于3次直接跳出循环 if(student.getErrorNumber() >3){ continue; } if (StringUtils.isNotEmpty(student.getErrorMsg())){ throw new PracticBootException(student.getErrorMsg());//若内层方法捕捉到的异常信息不为空,抛出异常,给QuartzJobExecution工具类捕捉,记录异常日志 } } } @DS("数据源")//用于开启一个新的事务,每条记录拥有不同的事务,互不影响 public Student merhod2(Student student){ String errorMsg = ""; try{ .............. }catch(Exception e){ student.setErrorNumber(wmsAgvTask.getErrorNumber()+1); if(student.getErrorNumber() <= 3){ if(StringUtils.isNotEmpty(e.getMessage())){ errorMsg = e.getMessage(); }else { errorMsg = "系统异常";//为防止出现异常信息为空的情况,故手动将这些信息为空的异常归类为系统异常 }//捕捉到异常,记录异常 } }finally{ if(student.getErrorNumber() <= 3){ studentService.updateById(student);//更新异常次数 } student.setErrorMsg(errorMsg); return student;//对象包含异常信息一起返回给外层方法 } }
说明:PracticBootException为作者自定义的异常,他人可使用自己的异常