一个SpringBoot项目中使用了 Scheduled Task ,如果任务出现异常,需要发送邮件给管理员,让管理员进行处理。而使用 AOP 对所有任务里的一些方法进行拦截处理时,由于方法经过封装(需要被拦截的),在 task 内部调用该方法时 AOP 不能直接拦截,所以我们需要进行一些特殊处理。
在 task 中定义了两个定时 job 方法,一个是导入 yri
的文件,一个是导入 usk
的。在导入(从ftp下载文件,复制文件到数据库服务器,导入)的过程中如果有任何的异常,都需要在 AOP 中进行处理,发送邮件。因此,我们的 AOP 的切面方法需要直接定位到 importFeedFiles
,而根据实际情况,我们是在内部进行调用的 importFeedFiles
。所以我们需要在 AOP 中添加 @EnableAspectJAutoProxy(exposeProxy = true)
实现,然后在 FeedFileScheduleTasks
中通过 AopContext.currentProxy()
获取 FeedFileScheduleTasks
对象,然后使用该方法返回的对象调用 importFeedFiles
时,AOP 就可以拦截到。
@Component
@EnableScheduling
@Data
@Slf4j
public class FeedFileScheduleTasks {
@Scheduled(cron = "${feedfile.schedule.yri.download.cron}")
public CommonResponse importYriFeedFiles() {
CommonResponse response = this.currentScheduledTask().importFeedFiles(FEED_FILE_TYPE_YRI);
this.yriScheduledResult = response.isSuccess();
return response;
}
@Scheduled(cron = "${feedfile.schedule.usk.download.cron}")
public CommonResponse importUskFeedFiles() {
CommonResponse response = this.currentScheduledTask().importFeedFiles(FEED_FILE_TYPE_USK);
// uskScheduledResult 类型为 boolean
this.uskScheduledResult = response.isSuccess();
return response;
}
@ScheduledMethodLog
public CommonResponse importFeedFiles(String feedFileType) {
return this.importFeedFiles(feedFileType, DateUtils.getToday(), true);
}
/**
* 具体的业务方法.
*/
public CommonResponse importFeedFiles(String feedFileType, String dateStr, boolean feedFileDownloaded) {
// 1. download: 下载 feed file
// 2. copy: 到数据库服务器
// 3. import: 到数据库
return response;
}
private FeedFileScheduleTasks currentScheduledTask() {
if(isExposeProxy()){
return (FeedFileScheduleTasks) AopContext.currentProxy();
} else {
return this;
}
}
/**
* AOP切面类的 {@linkplain EnableAspectJAutoProxy} 注释的 exposeProxy 设置。
*
* @return 返回 true 如果 exposeProxy 设置为 true
*/
private boolean isExposeProxy() {
try {
AopContext.currentProxy();
} catch (IllegalStateException e) {
return false;
}
return true;
}
}
AOP 类
@Aspect
@Component
@Slf4j
@EnableAspectJAutoProxy(exposeProxy = true)
public class YumCommonAop {
/**
* 拦截处理 task 的方法。
**/
@Around("@annotation(com.tts.commons.annotation.ScheduledMethodLog)")
public Object handlerTaskMethod(ProceedingJoinPoint pjp) throws Throwable {
String method = pjp.getSignature().getName();
StringBuilder target = new StringBuilder();
if (!StringUtils.isEmpty(pjp.getArgs()) && pjp.getArgs().length > 0) {
target.append(pjp.getArgs()[0]);
}
target.append(" ").append(method);
log.info("{} task start...", target.toString());
Object returning = null;
try {
returning = pjp.proceed();
} catch (Throwable ex) {
returning = handlerException(target.toString(), ex);
}
log.info("{} task end...", target.toString());
return returning;
}
/**
* 处理异常,发送邮件.
* @param target feedFileType + 目标方法
* @param ex 异常
* @return {@linkplain CommonResponse#SIMPLE_FAILURE}
*/
private CommonResponse handlerException(String target, Throwable ex) {
log.error("The {} execute error!", target, ex);
// 发送邮件
return CommonResponse.SIMPLE_FAILURE;
}
}
自定义切面注释类:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ScheduledMethodLog {
String description() default "";
}
参考:
http://blog.csdn.net/nieyanshun_me/article/details/74898401
https://www.cnblogs.com/intsmaze/p/5206584.html