SpringCloud整合AOP做日志管理

1、前置知识

  • 切面(Aspect):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”,在本例中,“切面”就是类LogAspect所关注的具体行为,例如,TestServiceImp.update()的调用就是切面LogAspect所关注的行为之一。“切面”在ApplicationContext中aop:aspect来配置,此项目中spring-boot-starter-aop已包含配置。
  • 连接点(Joinpoint) :程序执行过程中的某一行为,例如,ILogService.insert的调用或者ILogService.delete抛出异常等行为。
  • 通知(Advice) :“切面”对于某个“连接点”所产生的动作,例如,TestAspect中对com.spring.service包下所有类的方法进行日志新增的动作就是一个Advice。其中,一个“切面”可以包含多个“Advice”,新增,修改,删除等。
  • 切入点(Pointcut) :匹配连接点的断言,在AOP中通知和一个切入点表达式关联。大部分做法都由切入点表达式execution(* com.spring.service..(…))来决定,本例是通过@Pointcut("@annotation(com.lyy.yingwudemo.yingwu_auth.annotion.OptionLogger.UserOptLogger) ")注解的方式。
    其实我感觉,切入点就是将切面类和注解绑定起来(如果是基于注解的AOP的话)。
  • 目标对象(Target Object) :被一个或者多个切面所通知的对象。例如,AServcieImpl和BServiceImpl,当然在实际运行时,Spring AOP采用代理实现,实际AOP操作的是TargetObject的代理对象。
  • AOP代理(AOP Proxy) :在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理①。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,例如,AServiceImpl;反之,采用CGLIB代理,例如,BServiceImpl。强制使用CGLIB代理需要将 aop:config的 proxy-target-class属性设为true。
  • 通知(Advice)类型
    前置通知(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在aop:aspect里面使用aop:before元素进行声明。例如,LogAspect中的before方法。
    后置通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在aop:aspect里面使用aop:after元素进行声明。例如,LogAspect中的after方法,所以调用doError抛出异常时,after方法仍然执行。
    返回后通知(After return advice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在aop:aspect里面使用元素进行声明。
    环绕通知(Around advice):包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在aop:aspect里面使用aop:around元素进行声明。例如,LogAspect中的handleAround方法。
    抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。ApplicationContext中在aop:aspect里面使用aop:after-throwing元素进行声明。例如,LogAspect中的doAfterThrowing方法。

2、步骤

2.1、依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.2、自定义注解,用于注解式AOP

我们要自定义一个注解,干什么用呢?用于标注在需要进行AOP管理的对象上,这个对象可以是方法(ElementType.METHOD),也可以是其他的。但既然是注解式AOP,就都需要标注我们自定义的注解才能告诉spring,咱们要对它进行AOP管理。

package com.lyy.yingwudemo.yingwu_auth.annotion;

import com.lyy.yingwuDemo.yingwu_common.enums.LogType;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author :lyy
 * @DESCRIPTION : 自定义注解,注解式AOP。用于用户在认证服务器的行为日志记录,比如登录登出注册等等。
 * @date : 04-18-21:06
 */
// 定义注解作用的范围,这里是方法
@Target(ElementType.METHOD)
// 定义注解生命周期,这里是运行时
@Retention(RetentionPolicy.RUNTIME)
public @interface UserOptLogger {
    /**
     * 业务名称
     */
    public String operation() default "";

    /**
     * 日志级别
     *
     * @return
     */
    public LogType level() default LogType.INFO;

    /**
     * 是否将当前日志记录到数据库中
     */
    public boolean save() default true;
}

package com.lyy.yingwuDemo.yingwu_common.enums;

/**
 * @author :lyy
 * @DESCRIPTION : 日志级别枚举类
 * @date : 04-18-21:11
 */
public enum LogType {
    INFO,WARN,ERROR;
}

2.3、定制切面类

  • 使用@Aspect注解将一个java类定义为切面类。干什么用呢?切面类其实就是用于定制我们需要在操作执行前后做的事情。相当于对多个业务功能横着切开做了统一的管理。有点抽象,看下代码就了然了。
package com.lyy.yingwudemo.yingwu_auth.annotion.OptionLogger;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author :lyy
 * @DESCRIPTION : UserOptLogger切面类
 * @date : 04-20-20:41
 */
@Aspect
@Component
public class UserOptLoggerAspect {
    private static final Logger log = LoggerFactory.getLogger(UserOptLoggerAspect.class);
    /**
     * 切入点
     */
    @Pointcut("@annotation(com.lyy.yingwudemo.yingwu_auth.annotion.OptionLogger.UserOptLogger)")
    public void entryPoint() {
        // 无需内容
    }

    @Before("entryPoint()")
    public void before(JoinPoint joinPoint) {

        log.info("=====================开始执行前置通知==================");
        try {
            String targetName = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            Object[] arguments = joinPoint.getArgs();
            Class<?> targetClass = Class.forName(targetName);
            Method[] methods = targetClass.getMethods();
            String operation = "";
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class<?>[] clazzs = method.getParameterTypes();
                    if (clazzs.length == arguments.length) {
                        operation = method.getAnnotation(UserOptLogger.class).operation();// 操作人
                        break;
                    }
                }
            }
            StringBuilder paramsBuf = new StringBuilder();
            for (Object arg : arguments) {
                paramsBuf.append(arg);
                paramsBuf.append("&");
            }

            // *========控制台输出=========*//
            log.info("[X用户]执行了[" + operation + "],类:" + targetName + ",方法名:" + methodName + ",参数:"
                    + paramsBuf.toString());
            log.info("=====================执行前置通知结束==================");
        } catch (Throwable e) {
            log.info("around " + joinPoint + " with exception : " + e.getMessage());
        }

    }

    @After("entryPoint()")
    public void after(JoinPoint joinPoint) {

        log.info("=====================开始执行后置通知==================");
        try {
            String targetName = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            Object[] arguments = joinPoint.getArgs();
            Class<?> targetClass = Class.forName(targetName);
            Method[] methods = targetClass.getMethods();
            String operation = "";
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class<?>[] clazzs = method.getParameterTypes();
                    if (clazzs.length == arguments.length) {
                        operation = method.getAnnotation(UserOptLogger.class).operation();// 操作人
                        break;
                    }
                }
            }
            StringBuilder paramsBuf = new StringBuilder();
            for (Object arg : arguments) {
                paramsBuf.append(arg);
                paramsBuf.append("&");
            }

            // *========控制台输出=========*//
            log.info("[X用户]执行了[" + operation + "],类:" + targetName + ",方法名:" + methodName + ",参数:"
                    + paramsBuf.toString());
            log.info("=====================执行后置通知结束==================");
        } catch (Throwable e) {
            log.info("around " + joinPoint + " with exception : " + e.getMessage());
        }

    }
    /**
     * 环绕通知处理处理
     *
     * @param point
     * @throws Throwable
     */
    @Around("entryPoint()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        // 先执行业务,注意:业务这样写业务发生异常不会拦截日志。
        Object result = point.proceed();
        try {
            handleAround(point);// 处理日志
        } catch (Exception e) {
            log.error("日志记录异常", e);
        }
        return result;
    }

    /**
     * around日志记录
     *
     * @param point
     * @throws SecurityException
     * @throws NoSuchMethodException
     */
    public void handleAround(ProceedingJoinPoint point) throws Exception {
        Signature sig = point.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        msig = (MethodSignature) sig;
        Object target = point.getTarget();
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        // 方法名称
        String methodName = currentMethod.getName();
        // 获取注解对象
        UserOptLogger aLog = currentMethod.getAnnotation(UserOptLogger.class);
        // 类名
        String className = point.getTarget().getClass().getName();
        // 方法的参数
        Object[] params = point.getArgs();

        StringBuilder paramsBuf = new StringBuilder();
        for (Object arg : params) {
            paramsBuf.append(arg);
            paramsBuf.append("&");
        }
        // 处理log。。。。
        log.info("[X用户]执行了[" + aLog.operation() + "],类:" + className + ",方法名:" + methodName + ",参数:"
                + paramsBuf.toString());

    }

    @AfterThrowing(pointcut = "entryPoint()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        // 通过request获取登陆用户信息
        // HttpServletRequest request = ((ServletRequestAttributes)
        // RequestContextHolder.getRequestAttributes()).getRequest();
        try {
            String targetName = joinPoint.getTarget().getClass().getName();
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            Object[] arguments = joinPoint.getArgs();
            Class<?> targetClass = Class.forName(targetName);
            Method[] methods = targetClass.getMethods();
            String operation = "";
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class<?>[] clazzs = method.getParameterTypes();
                    if (clazzs.length == arguments.length) {
                        operation = method.getAnnotation(UserOptLogger.class).operation();
                        break;
                    }
                }
            }

            StringBuilder paramsBuf = new StringBuilder();
            for (Object arg : arguments) {
                paramsBuf.append(arg);
                paramsBuf.append("&");
            }

            log.info("异常方法:" + className + "." + methodName + "();参数:" + paramsBuf.toString() + ",处理了:" + operation);
            log.info("异常信息:" + e.getMessage());
        } catch (Exception ex) {
            log.error("异常信息:{}", ex.getMessage());
        }
    }
}

2.4、测试

只要在需要测试的方法上加上咱们自定义的注解就好了~,会在该方法执行前后执行切面中相应的方法。

@Controller
public class UserController {
    @UserOptLogger(operation = "用户登录")
    @RequestMapping("/tologin")
    public String tologin(){
        return "login";
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CtrlZ1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值