JAVA编写AOP切面打印日志和自定义AOP注解

14 篇文章 0 订阅

来个简单粗暴的  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注解定义,简单记录下~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值