通过 AOP(面向切面编程)技术来拦截所有使用 @Scheduled 注解的方法,并在这些方法执行前后进行日志记录。具体记录了定时任务的开始和结束时间,以及是否成功执行。如果任务执行失败,它还会记录异常信息。
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.lang.reflect.Method;
import java.util.Objects;
@Aspect
@Component
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ScheduleAspect extends AbstractPointcutAdvisor {
@Override
public Pointcut getPointcut() {
return Objects.requireNonNull(AnnotationMatchingPointcut.forMethodAnnotation(Scheduled.class));
}
@Override
public Advice getAdvice() {
return new ScheduleAdvice();
}
public static class ScheduleAdvice implements Advice, MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
TimeWatcher timeWatcher = new TimeWatcher();
boolean success = true;
String traceId = StringUtilsCustom.uuidStr();
Class<?> targetClass = invocation.getThis() != null ? invocation.getThis().getClass() : null;
Logger logger = targetClass != null ? LoggerFactory.getLogger(targetClass) : log;
try {
logStart(logger, method, targetClass);
timeWatcher.begin();
return invocation.proceed();
} catch (Throwable th) {
success = false;
logError(logger, method, targetClass, th);
throw th; // Re-throw the exception to ensure it's properly handled
} finally {
logEnd(logger, method, targetClass, timeWatcher, success);
}
}
private void logStart(Logger logger, Method method, Class<?> targetClass) {
if (targetClass != null) {
logger.info("开始执行定时任务:{}#{}", targetClass.getName(), method.getName());
}
}
private void logError(Logger logger, Method method, Class<?> targetClass, Throwable th) {
if (targetClass != null) {
logger.error("定时任务执行失败:{}#{}", targetClass.getName(), method.getName(), th);
}
}
private void logEnd(Logger logger, Method method, Class<?> targetClass, TimeWatcher timeWatcher, boolean success) {
if (targetClass != null) {
String status = success ? "成功" : "失败";
double duration = timeWatcher.end() / 1000.0;
logger.info("结束定时任务{}:{}#{},用时:{}s", status, targetClass.getName(), method.getName(), duration);
}
}
}
}
class TimeWatcher {
private long startTime;
public void begin() {
this.startTime = System.currentTimeMillis();
}
public long end() {
return System.currentTimeMillis() - startTime;
}
}
class StringUtilsCustom {
public static String uuidStr() {
return java.util.UUID.randomUUID().toString();
}
}
主要作用
- 日志记录:在定时任务开始和结束时记录日志,包含任务名称、执行时间和执行状态(成功或失败)。
- 异常处理:在任务执行失败时记录错误信息。
- 性能监控:记录定时任务的执行时间。
详细解释
-
定义切面(Aspect):
@Component
:将ScheduleAspect
类声明为 Spring 容器中的一个 Bean。
-
继承
AbstractPointcutAdvisor
:- 继承
AbstractPointcutAdvisor
类,创建一个自定义的切面 Advisor。
- 继承
-
定义切入点(Pointcut):
getPointcut()
方法返回一个切入点,拦截所有使用@Scheduled
注解的方法。- 使用
AnnotationMatchingPointcut.forMethodAnnotation(Scheduled.class)
创建切入点。
-
定义通知(Advice):
getAdvice()
方法返回一个自定义的ScheduleAdvice
对象,该对象实现了MethodInterceptor
接口。
-
方法拦截逻辑(Method Interceptor Logic):
ScheduleAdvice
类实现了MethodInterceptor
接口,通过invoke
方法拦截方法调用。invoke(MethodInvocation invocation)
方法在方法执行前后执行自定义逻辑。
-
日志记录和异常处理:
- 使用
Logger
记录定时任务的开始和结束信息。 - 记录任务执行时间,使用
TimeWatcher
对象测量执行时间。 - 记录任务执行成功或失败的状态。
- 使用
代码的意义
这段代码对于监控和调试定时任务非常有用:
- 监控定时任务的执行情况:通过日志记录任务的开始和结束时间,可以监控定时任务的执行频率和时长。
- 调试定时任务:在任务执行失败时记录错误信息,帮助开发人员快速定位问题。
- 性能分析:记录任务的执行时间,便于分析和优化定时任务的性能。
关键点总结
- 切面(Aspect):拦截定时任务方法。
- 通知(Advice):在方法执行前后进行日志记录和异常处理。
- 日志记录:记录方法的开始、结束时间和执行状态。
- 异常处理:在方法执行失败时记录异常信息。