Spring AOP日志收集

最近的项目中需要实现日志收集的功能,项目中使用了Spring AOP的环绕通知(Around)和消息队列实现的,本篇是基于 Spring AOP实现后台管理系统日志管理(感谢作者)来进行记录的。

1、Spring AOP

切面(Aspect):切面用于组织多个Advice,Advice放在切面中定义。
连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出。在Spring AOP中,连接点总是方法的调用。
增强处理(Advice):AOP框架在特定的切入点执行的增强处理。处理有“around”、“before”和“after”等类型。
切入点(Pointcut):可以插入增强处理的连接点。

2、通知类型

1)Before:在一个切面类里使用 @Before 来修饰一个方法时,该方法将作为Before增强处理。使用@Before修饰时,通常需要指定一个value属性值,指定一个切入点表达式,用于指定该增强处理将被织入哪些切入点。
注:该增强处理在目标方法之前执行。

2)After:After增强处理不管目标方法是成功还是有异常,都会被织入。After增强处理必须准备处理正常返回和异常返回两种情况,这种增强处理通常用于释放资源。

3)AfterReturning:AfterReturning增强处理在目标方法正常完成后被织入。
注:1)AfterReturning增强处理可以访问目标方法的返回值,但不能改变。当发生异常时,不起作用。

4)AfterThrowing:AfterThrowing增强处理主要用于处理程序中未处理的异常。
5)Around:Around增强处理是功能比较强大的增强处理,它近似等于Before和AfterReturning增强处理的总和,Around增强处理既可在执行目标方法之前织入增强动作,也可在执行目标方法之后织入增强动作。
当定义一个Around增强处理方法时,该方法的第一个形参必须是roceedingJoinPoint类型。

3、自定义注解


import java.lang.annotation.*;

/**
 * 定义Log收集注解
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableLog {
    String desc() default "";//描述业务操作
}

1)@Target({ElementType.METHOD,ElementType.TYPE}) :

用于描述注解的使用范围(即:被描述的注解可以用在什么地方),其取值有:

CONSTRUCTOR: 用于描述构造器。
FIELD: 用于描述域。
LOCAL_VARIABLE: 用于描述局部变量。
METHOD: 用于描述方法。
PACKAGE: 用于描述包。
PARAMETER: 用于描述参数。
TYPE: 用于描述类或接口(甚至 enum )。

2)@Retention(RetentionPolicy.RUNTIME):

用于描述注解的生命周期(即:被描述的注解在什么范围内有效),其取值有:

SOURCE: 在源文件中有效(即源文件保留)。
CLASS: 在 class 文件中有效(即 class 保留)。
RUNTIME: 在运行时有效(即运行时保留)。

3)@Documented 在默认的情况下javadoc命令不会将我们的annotation生成再doc中去的,所以使用该标记就是告诉jdk让它也将annotation生成到doc中去

4、定义切面

@Component
@Aspect
public class LogAspect {

    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    private static final ThreadLocal<User> userThreadLocal = new NamedThreadLocal<>("threadlocal_user");
    private static final ThreadLocal<MyLog> myLogThreadLocal = new NamedThreadLocal<>("threadlocal_myLog");

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private LogService logService;

    /**
     * 在controller的切点
     */
    @Pointcut(value = "@annotation(cn.hhm.worklogcollect.aop.EnableLog)")
    public void controllerPointCut() {
    }

    /**
     * 前置通知
     *
     * @param point
     */
    @Before(value = "controllerPointCut()")
    public void doBefore(JoinPoint point) throws Exception {
        logger.error("前置通知...执行");
        Date beginTime = new Date();//操作时间
        MyLog myLog = new MyLog();
        myLog.setTime(beginTime);

        //获取用户
        HttpServletRequest request = getRequest();
        User user = (User) request.getSession().getAttribute("user");
        myLogThreadLocal.set(myLog);
        userThreadLocal.set(user);
    }

    /**
     * 后置通知
     *
     * @param point
     */
    @After(value = "controllerPointCut()")
    public void doAfter(JoinPoint point) {
        logger.error("后置通知...执行");
        User user = userThreadLocal.get();
        if (user != null) {
            String title = "";

            HttpServletRequest request = getRequest();
            String ip = request.getRemoteAddr();
            String uri = request.getRequestURI();
            String requestType = request.getMethod();

            try {
                title = getDesc(point);
            } catch (Exception e) {
                e.printStackTrace();
            }
            MyLog myLog = myLogThreadLocal.get();
            myLog.setType("info");
            myLog.setTitle(title);
            myLog.setIpAddress(ip);
            myLog.setUriStr(uri);
            myLog.setRequestType(requestType);
            myLog.setUserId(user.getId().toString());
            myLog.setCreateTime(new Date());
            myLogThreadLocal.set(myLog);
            //通过线程池保存日志
            threadPoolTaskExecutor.execute(new InsertLogThread(myLog, logService));
            userThreadLocal.remove();
        }
    }

    /**
     *在 @After之后执行,如果没有异常的话,最终执行本方法
     * @param point
     */
    @AfterReturning("controllerPointCut()")
    public void doAfterReturning(JoinPoint point){
        logger.error("AfterReturning...........执行");
        myLogThreadLocal.remove();

    }

    /**
     * 异常通知
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "controllerPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        logger.error("AfterThrowing通知...执行");
        MyLog myLog = myLogThreadLocal.get();
        if (myLog != null) {
            myLog.setType("error");
            myLog.setErrorInfo(e.toString());
            threadPoolTaskExecutor.execute(new UpdateLogThread(myLog, logService));
            myLogThreadLocal.remove();
        }
    }

    /**
     * 日志的保存
     */
    private class InsertLogThread implements Runnable {

        private MyLog myLog;
        private LogService logService;

        public InsertLogThread(MyLog myLog, LogService logService) {
            this.myLog = myLog;
            this.logService = logService;
        }

        @Override
        public void run() {
            logService.insert(myLog);
        }
    }

    /**
     * 日志的更新
     */
    private class UpdateLogThread implements Runnable {

        private MyLog myLog;
        private LogService logService;

        public UpdateLogThread(MyLog myLog, LogService logService) {
            this.myLog = myLog;
            this.logService = logService;
        }

        @Override
        public void run() {
            logService.update(myLog);
        }
    }

    public String getDesc(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        EnableLog enableLog = method.getAnnotation(EnableLog.class);
        String desc = enableLog.desc();
        return desc;
    }

    public HttpServletRequest getRequest() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        return request;
    }
}

由于在切面中使用了ThreadLocal,为了避免内存泄漏,每次使用完ThreadLocal后,都调用它的remove()方法,清除数据。

5、Spring AOP 切入点指示符

1)execution:用于匹配执行方法的连接点。
//匹配任意 public 方法的执行
@Around(value = “execution(public * * (..))”)
//匹配任何方法名以“set”开始的方法的执行
@Around(value = “execution(* set* (..))”)

//匹配service中任意方法的执行
@Around(value = “execution(* com.example.service.* (..))”)

//匹配com.exampleservice.impl包下任意类的任意方法的执行
@Around(“execution(* com.example.service.impl..(..))”)

2)within:用于限定匹配特定类型的连接点,当使用Spring AOP的时候,只能匹配方法执行的连接点。

//在com.example.service包中的任意连接点(在 spring aop 中只是方法执行的连接点)
@Around(“within(com.example.service.*)”)

//在com.example.service包或其子包中的任意连接点
@Around(“within(com.example.service..*)”)

3)this/target:用于限定AOP代理必须是指定类型的实例,匹配该对象的所有连接点。

//匹配实现了com.example.service.impl.UserServiceImpl类的AOP代理的所以连接点(方法)
@Around(“this(com.example.service.impl.UserServiceImpl)”)

6、在Controller中

 @EnableLog(desc = "用户模块-查询列表")
    @GetMapping(value = "/user/list")
    public List<User> userList(){

        List<User> userList = userService.selectUserList();
        return userList;
    }

演示结果:

这里写图片描述

项目链接:https://github.com/hhmings/mytest.git

猜你喜欢

转载自blog.csdn.net/HAIMING157/article/details/79857546
今日推荐