springBoot使用aop+注解方式实现可由切点(Poincut)向增强(Advice)传递参数的日志管理

老规矩先抛出需求:

在原有的业务上新增日志管理,要求日志记录指定的信息(包含业务所属模块;客户端传入的参数;业务处理时产生的数据,如结算后的余额等;业务执行的结果

1.添加依赖

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

2.日志表 略

3.自定义日志注解

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

/**
 * @Description:日志注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnnotation {
    String module() default "未指定";
}

4.日志增强类

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * @Description:日志增强类
 */
@Component
@Aspect
@Lazy(value = false)
public class LogAspect {
    @Pointcut("@annotation(com.****.LogAnnotation)")
    private void cutMethod() {}

    @Around(value = "cutMethod()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //从注解中获取业务模块
        String module = getModule(joinPoint);

        //从切点的方法参数中获取传入业务的参数
        JSONObject jsonParams = getBusinessParams(joinPoint);

        //执行切点方法
        Object proceed = joinPoint.proceed();

        //从固定的getLogParams()方法中获取需要打印出来的数据
        Map logParamsMap = getParams(joinPoint);

        //输出日志 实际应用将控制台输出改为插入日志表即可
        System.out.println("打印业务日志----业务模块:" + module + ",传入参数:" + jsonParams.toJSONString() +
                ",指定记录参数:" + JSON.toJSONString(logParamsMap) + ",业务调用结果:" + JSON.toJSONString(proceed));
        return proceed;
    }

    /**
     * 从注解中获取业务模块
     *
     * @param joinPoint
     * @return
     */
    private String getModule(ProceedingJoinPoint joinPoint) throws Exception{
        Object target = joinPoint.getTarget();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
        LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);
        return annotation.module();
    }

    /**
     * 从切点的方法参数中获取传入业务的参数
     *
     * @param joinPoint
     * @return
     */
    private JSONObject getBusinessParams(ProceedingJoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        JSONObject jsonParams = new JSONObject();
        for (int i = 0; i < args.length; i++) {
            if (null != parameterNames[i]) {
                jsonParams.put(parameterNames[i], null == args[i] ? "" : args[i].toString());
            }
        }
        return jsonParams;
    }

    /**
     * 从固定的getLogParams()方法中获取需要打印出来的数据
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    private Map getParams(ProceedingJoinPoint joinPoint) throws Throwable {
        Object target = joinPoint.getTarget();
        Method logParams = target.getClass().getDeclaredMethod("getLogParams");
        logParams.setAccessible(true);//注意:由于getLogParams方法为private方法,此处必须关闭安全检查,否则报异常
        Map logParamsMap = (Map) logParams.invoke(target);
        return logParamsMap;
    }


}

5.写个controller类测试一下

import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description: 测试controller
 */
@Controller
@RequestMapping("/test1")
public class Test1 {
    private static ThreadLocal<Map> threadLocal = new ThreadLocal<>();

    private Map getThreadLocalMap() {
        if (null == threadLocal.get()) {
            threadLocal.set(new HashMap(2));
        }
        return threadLocal.get();
    }


    @RequestMapping("/add")
    @ResponseBody
    @LogAnnotation(module = "用户管理")
    public Object addUser(String name, String email) {
        Map map = getThreadLocalMap();
        map.put("切点中经过业务处理产生的参数", "新增用户的名字和邮箱是:" + name + "," + email + ",分配的id是:" + 123456);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("status", "200");
        jsonObject.put("message", "操作成功");
        return jsonObject;
    }

    private Map getLogParams() {
        return this.threadLocal.get();
    }
}

6.输入地址测试一下

打印业务日志----业务模块:用户管理,传入参数:{"name":"tonny","email":"tonny@qq.com"},指定记录参数:{"切点中经过业务处理产生的参数":"新增用户的名字和邮箱是:tonny,tonny@qq.com,分配的id是:123456"},业务调用结果:{"message":"操作成功","status":"200"}

测试OK!

 

 

7.相关知识点:

  1. 通知(有的地方叫增强)(Advice),需要完成的工作叫做通知,就是你写的业务逻辑中需要比如事务、日志等先定义好,然后需要的地方再去用
  2. 连接点(Join point),就是spring中允许使用通知的地方,基本上每个方法前后抛异常时都可以是连接点
  3. 切点(Poincut),其实就是筛选出的连接点,一个类中的所有方法都是连接点,但又不全需要,会筛选出某些作为连接点做为切点。如果说通知定义了切面的动作或者执行时机的话,切点则定义了执行的地点
  4. 切面(Aspect),其实就是通知和切点的结合,通知和切点共同定义了切面的全部内容,它是干什么的,什么时候在哪执行
  5. 引入(Introduction),在不改变一个现有类代码的情况下,为该类添加属性和方法,可以在无需修改现有类的前提下,让它们具有新的行为和状态。其实就是把切面(也就是新方法属性:通知定义的)用到目标类中去
  6. 目标(target),被通知的对象。也就是需要加入额外代码的对象,也就是真正的业务逻辑被组织织入切面。
  7. 织入(Weaving),把切面加入程序代码的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入:
  • 编译期:切面在目标类编译时被织入,这种方式需要特殊的编译器
  • 类加载期:切面在目标类加载到JVM时被织入,这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码
  • 运行期:切面在应用运行的某个时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,Spring AOP就是以这种方式织入切面的。

 

常用AOP通知(增强)类型

  1. before(前置通知): 在方法开始执行前执行
  2. after(后置通知): 在方法执行后执行
  3. afterReturning(返回后通知): 在方法返回后执行
  4. afterThrowing(异常通知): 在抛出异常时执行
  5. around(环绕通知): 在方法执行前和执行后都会执行

       执行顺序:around > before > around > after > afterReturning

 

知识点来源:https://www.jianshu.com/p/4d22ea402d14

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值