SpringAOP通过自定义注解实现日志管理

在最开始第一篇文章中我就给大家介绍了Spring中的AOP,大概都是一些基本概念和使用方法。附上地址:https://blog.csdn.net/bicheng4769/article/details/79501263
其实讲这么多,大家可能看一遍就会忘记,所以我习惯通过实战去加深对这个知识点的理解。现学现用嘛,所以在项目中就加上了日志管理。

为什么要使用AOP作为日志管理

想想如果我们不用AOP这种方式来处理日志,那么我们就要在每个需要加LOG的地方植入我们的代码。或许有人说,可以将重复的代码封装成方法,然后再去调用这个方法就可以了。但是如果我还要记录这个方法的参数呢?方法名呢?

需要了解的知识点

  1. 自定义注解
  2. java反射机制
  3. 注解形式使用AOP

开始准备

在着手写代码之前,我们可以想想可能会遇到什么样的问题:
1. 注解是否可以作为切点来使用:
切面、通知以及切点。切面、通知 其实都是一样的,唯一不同的是(切点是@Pointcut("execution(* com.storm.controller.*.*(..))"))这次采用的是注解来作为切点,并没有具体的切点位置。那么注解是否可以作为切点来使用呢?遇到这种问题,第一反应肯定先去Spring官网文档查看内容:
https://docs.spring.io/spring/docs/5.0.4.RELEASE/spring-framework-reference/core.html#aop
我们可以看到@Pointcut有几种类型(execution、@within、@annotation。。。。)其中关于@annotation是这么写的:

@annotation - limits matching to join points where the subject of the join point (method being executed in Spring AOP) has the given annotation

@within和@target针对类的注解,@annotation是针对方法的注解

所以切点的定义我们就可以使用@Pointcut(@annotation(com.test.annotation.log))这种形式。
2. 在通知中如何获取到方法的参数和方法名:
毫无疑问我们肯定是利用java的反射机制。

编写代码

1. Spring开启注解模式:

<aop:aspectj-autoproxy proxy-target-class="true"/>

2. 自定义注解:

package com.perf.annotation;

import java.lang.annotation.*;
/**
 * @author cj34920
 * Date: 2018/03/08
 */
 /**
 * 注解用于什么地方
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
/**
 * 注解是否将包含在JavaDoc中
 */
@Documented
/**
 * 定义该注解的生命周期
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemControllerLog {
    String description() default "";
}

3. 编写切面类

package com.perf.aop;

import com.perf.annotation.SystemControllerLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @author cj34920
 */
@Aspect
@Component
public class SystemLogAspect {
    @Pointcut("@annotation(com.perf.annotation.SystemControllerLog)")
    public void controllerAspect() {
        System.out.println("我是一个切入点");
    }

    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint) throws Exception {
        joinPoint.getArgs();
        for (int i = 0; i < joinPoint.getArgs().length; i++) {
            System.out.println(joinPoint.getArgs()[i]);
        }
        Map<String, Object> map = getAnnotationDescription(joinPoint);
        String userName = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession().getAttribute("username").toString();
        System.out.println("方法名" + joinPoint.getSignature().getName() + "登录人" + userName + "描述" + map.get("description"));
        System.out.println("方法开始");
    }

    @After("controllerAspect()")
    public void doAfter(JoinPoint joinPoint) throws Exception {
        System.out.println("方法结束");
    }

    @AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("方法出错了 " + e.getStackTrace()[0].getMethodName() + getErrorInfoFromException(e));
    }

    public String getErrorInfoFromException(Throwable e) {
        try {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            e.printStackTrace(pw);
            return "\r\n" + sw.toString() + "\r\n";
        } catch (Exception e2) {
            return "ErrorInfoFromException";
        }
    }

    /**
     * 获取注解内容
     *
     * @param joinPoint
     * @return
     * @throws ClassNotFoundException
     */
    public Map<String, Object> getAnnotationDescription(JoinPoint joinPoint) throws ClassNotFoundException {
        Map<String, Object> map = new HashMap<String, Object>();
        String targetName = joinPoint.getTarget().getClass().getName();
        String targetMethod = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        for (Method method : methods) {
            //判断是否是这个方法
            if (method.getName().equals(targetMethod)) {
                Class[] clazzs = method.getParameterTypes();
                //判断参数是否一样
                if (clazzs.length == arguments.length) {
                    map.put("description", method.getAnnotation(SystemControllerLog.class).description());
                    break;
                }
            }
        }
        return map;
    }
}

4.controller测试类

@RequestMapping("test/apm")
@SystemControllerLog(description = "获取apm数据")
public void testApm(HttpServletResponse response) {
try {
       int I = 1 / 0;
       response.getWriter().write("OK");
    } catch (IOException e) {
       e.printStackTrace();
    }
}

调试结果

结束语:

在学习一个新的知识点的时候,我希望是先具体后抽象,在实际应用中掌握了解这个知识点,在掌握的基础上在对原理进行分析和理解。

猜你喜欢

转载自blog.csdn.net/bicheng4769/article/details/79702778