来个简单粗暴的 AOP切面打印方法执行日志
LogAopAspect.java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogAopAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAopAspect.class);
//指定包路径
@Around("execution(* student.controller.*.*(..))")
public Object process(ProceedingJoinPoint pjp) throws Throwable {
Class<?> currentClass = pjp.getTarget().getClass();
MethodSignature signature = (MethodSignature) (pjp.getSignature());
String ClassName = currentClass.getSimpleName();
String methodName = currentClass.getMethod(signature.getName(), signature.getParameterTypes()).getName();
logger.info("======= 开始执行:" + ClassName + " — " + methodName + " ========");
Object obj = pjp.proceed();
logger.info("======= 执行结束:" + ClassName + " — " + methodName + " ========");
return obj;
}
}
当然也可以自定义注解,这里的应用场景为记录系统日志
这里建议线程池 手动创建,原因后边说~
@Component
@Aspect
@Slf4j
public class SysLogAspect {
/**
* 线程池 异步记录日志 手动创建线程池
*/
private static ExecutorService logExecutorService = new ThreadPoolExecutor(5,10,
1L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//定义切点 @Pointcut
//在注解的位置切入代码
// 所以这里的自定义注解是 @OperatorLog
@Pointcut("@annotation( com.ancun.hubchain.common.core.annotation.OperatorLog)")
public void logPointCut() {
}
//配置后置通知,使用在方法aspect()上注册的切入点
@AfterReturning("logPointCut()")
public void afterReturn(JoinPoint joinPoint){
String ip = getUserIp();
CurrentUserDTO user = getUser();
// 执行保存日志的线程
SaveSystemLogThread saveSystemLogThread = new SaveSystemLogThread(joinPoint, ip, user);
logExecutorService.execute(saveSystemLogThread);
}
// 获取当前连接客户端ip
private String getUserIp(){
//获取请求的ip
String ip = "";
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
// 操作人ip地址
if (requestAttributes != null) {
HttpServletRequest request = requestAttributes.getRequest();
ip = IpUtils.getIP(request);
}
return ip;
}
private CurrentUserDTO getUser(){
CurrentUserDTO user;
//获取当前登录用户
return user;
}
}
保存日志的service方法可以在切面自动注入后,作为参数传入线程类中使用
SaveSystemLogThread.java
@Slf4j
public class SaveSystemLogThread implements Runnable{
private JoinPoint joinPoint;
private String ip;
private CurrentUserDTO user;
public SaveSystemLogThread(JoinPoint joinPoint, String ip, CurrentUserDTO user) {
this.joinPoint = joinPoint;
this.ip = ip;
this.user = user;
}
@Override
public void run() {
saveOperateLog(joinPoint, ip, user);
}
/**
* 异步存储日志
* @param joinPoint
*/
private void saveOperateLog(JoinPoint joinPoint, String ip, CurrentUserDTO user){
log.info("======aop保存日志======");
MethodSignature methodName = (MethodSignature)joinPoint.getSignature();
Method method = methodName.getMethod();
String operatedObjectType = method.getAnnotation(OperatorLog.class).operatedObjectType();
String operatorContent = method.getAnnotation(OperatorLog.class).operatorContent();
String operatedId = null;
JSONObject params = new JSONObject();
if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
// 获取参数
JSONObject parseObject = JSONObject.parseObject(JSON.toJSONString(joinPoint.getArgs()[i]));
params.putAll(parseObject);
}
OperateLogDO operateLogDO = new OperateLogDO();
//......此处省略构造日志对象的过程
// 插入日志
operateLogService.save(operateLogDO);
}
/**
* 获取参数类型
* @param o 参数对象
* @return
*/
private String getValueType(Object o){
String valueType = o.getClass().toString();
valueType = valueType.substring(valueType.lastIndexOf('.') + 1);
return valueType;
}
}
自定义注解
OperatorLog.java
有需要传入的特定参数,可以在此定义
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperatorLog {
/**
* 操作的数据库表名称
* @return
*/
String operatedObjectType() default "";
/**
* 操作内容
* @return
*/
String operatorContent() default "";
/** 是否忽略参数
* @return
*/
boolean ignoreParams() default false;
}
好了,最后来说说线程池的问题,最开始用的简单粗暴的线程池创建方式
在比较正常的情况下,这样执行了好像也没啥毛病
但是在后续的执行过程中出现了系统日志丢失的情况,就是有时候,没用保存到操作日志。
但是一看后台日志把,也没有报错,只是执行到某一步后没有继续保存日志了。
后来经过各种排查,发现,在saveOperateLog过程中,其实发生了一个空指针异常,但是程序没有任何报错提示
但是如果换成这样的创建线程池方式去执行线程,方法中出现的异常会被正常抛出
并且,阿里规约中也建议不要用 Executors.newFixedThreadPool(10);来创建线程池
一次简单的自定义aop注解定义,简单记录下~