文章目录

  • 创建自定义注解类
  • Spring AOP的基础知识
  • AOP实现日志记录功能
  • 日志记录演示


创建自定义注解类

  • 首先创建自定义注解类EnableLog
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableLog {
    String value() default  "";
}
  • 注解类上的三个注解称为元注解(元注解 是负责对其它注解进行说明的注解),其分别代表的含义如下:
  • @Documented:注解信息会被添加到Java文档中
  • @Retention:注解的生命周期,表示注解会被保留到什么阶段,可以选择编译阶段、类加载阶段,或运行阶段(RetentionPolicy.RUNTIME).
  • @Target:注解作用的位置,ElementType.METHOD表示该注解仅能作用于方法上
  • 目前注解可以加到方法上,但是并没有任何作用。因为仅仅是声明了注解,下面通过Spring AOP去完善注解的作用。

Spring AOP的基础知识

AOP(Aspect Oriented Programming),即面向切面编程。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

AOP 术语

  • 切面(aspect): 类是对物体特征的抽象,切面就是对横切关注点的抽象
  • 连接点(joinpoint): 被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
  • 切入点(pointcut): 对连接点进行拦截的定义
  • 通知(advice): 所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类

Spring切面可以应用5种类型的通知

  • 前置通知(Before):在目标方法被调用之前调用通知
  • 后置通知(After):在目标方法完成之后调用通知(无论是正常还是异常退出都调用)
  • 返回通知(After-returning):在目标方法成功执行之后调用通知
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

AOP实现日志记录功能

/**
 * @Component 该注解表示把类加入Spring容器
 * @Aspect 该注解表示切面
 */
@Aspect
@Component
@Slf4j
public class OperationLogAspect {

    ThreadLocal<Long> startTime = new ThreadLocal<>();

	 /**
     * 设置操作日志切入点,这里介绍两种方式:
     * 1、基于注解切入(也就是打了自定义注解的方法才会切入)
     *    @Pointcut("@annotation( 包名.EnableLog)")
     * 2、基于包扫描切入
     *    @Pointcut("execution(public * 包名.controller..*.*(..))")
     */
	// @Pointcut 声明了一个公共切点(这里的切点是我们上面自定义的注解类)
    @Pointcut("@annotation( 包名.EnableLog)")
    public void operLog() {
    }
    
    public HttpServletRequest getRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        return request;
    }

	// @Around  环绕通知:通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
    @Around("operLog()")
    public Result doAround(ProceedingJoinPoint pjp) throws Throwable {
        startTime.set(System.currentTimeMillis());
        // AOP的JoinPoint类: 用来获取代理类和被代理类的信息。
        String className = pjp.getSignature().getDeclaringTypeName();  
        String methodName = pjp.getSignature().getName();
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();
        EnableLog enableLog = method.getAnnotation(EnableLog.class);

        Object[] args = pjp.getArgs();
        String[] argNames = methodSignature.getParameterNames();
        Map<String, Object> paramMap = new HashMap<>();
        for (int i = 0; i < args.length; i++) {
            paramMap.put(argNames[i], args[i]);
        }
        HttpServletRequest request = getRequest();
        log.info("*****************************************************");
        log.info("用户:{}", UserUtils.getUserAccount());
        log.info("URL:{}", request.getRequestURL());
        log.info("类名:{}", className);
        log.info("方法:{}", methodName);
        log.info("IP地址:{}", request.getRemoteHost());
        log.info("操作:{}", enableLog.value());
        log.info("参数:{}", JSONObject.toJSONString(paramMap));

        try {
            Object process = pjp.proceed();
            Result result = (Result) process;
            log.info("结果:{}", result.getMsg());
            return result;
        } catch (Exception e) {
            log.info("异常:{}", e.getMessage());
            throw e;
        } finally {
            log.info("耗时:{}", System.currentTimeMillis() - startTime.get());
            log.info("*****************************************************");
            startTime.remove();
        }
    }
}

日志记录演示

  • 在需要获取日志的方法上加上 @EnableLog 注解
  • 调用该方法,输出一下日志:

你知道的越多,你不知道的越多。