AOP 自定义注解 动态参数 日志功能

1 理解AOP

1.1 什么是AOP

AOP(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一(两外两个:IOC-控制反转、DI-依赖注入)。

那么AOP为何那么重要呢?在我们的程序中,经常存在一些系统性的需求,比如权限校验、日志记录、统计等,这些代码会散落穿插在各个业务逻辑中,非常冗余且不利于维护。 例如日志模块 我们想把核心业务代码跟日志实现解耦
让日志代码修改 和核心业务模块代码修改互不影响 实现有序升级 。 这时候就得用到我们aop 而且aop的主要用途之一 也是日志模块的实现

Spring AOP里的名词概念

翻阅Spring AOP相关的文档,发现里边有好多概念性的东西,有很多名词,有很多概念都写的很玄乎,读好几遍都读不懂,我个人认为下边的几个名词比较关键,是必须知道和掌握的

切面类:就是要执行的增强的方法所在的类, 而切点往往是在你的切面类下
大白话的话: 你的切点在哪(需要增强的**

方法

**) 你包含这个方法的类就叫切面类

通知:切面类里的增强方法, (在你切点上要需要加入功能 实现的方法)

目标方法:要执行的目标方法,

织入:把通知和目标方法进行结合,形成代理对象的过程就叫织入

代理对象的方法=通知(增强方法)+目标方法

在我的理解: 切面 = 切点(pointcut) + 通知(advice)
切点 决定了你要额外附加的功能实现在哪个目标上 通知是在这个目标如何执行(执行目标前 通知先执行 - before 通知后执行-- after 还是around 环绕式增强 你自己根据业务需求决定)

aop的本质 是动态代理 在设计模式种 动态代理和静态代理都是对我们理解架构设计有着巨大的帮助 但在此篇不展开 有兴趣可以自行观看

本文注重的是实践 并非原理:
此例子为自定义注解 aop实现日志功能
不多bb 上代码

自定义注解 包含两个属性 value spelValue

package com.example.RasTest.bussinessLog.annotation;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {

    //普通的操作说明
    String value() default "";

    //spel表达式的操作说明 这是我们获取动态参数的入口
    String spelValue() default "";
}

aop 日志功能

package com.example.RasTest.bussinessLog.bussinessAspect;

import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSONObject;
import com.example.RasTest.bussinessLog.annotation.SysLog;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

@Component  //注入到ioc容器中
@Aspect //标注这是一个aop
public class DynamicLogAspect {

    @Autowired
    private HttpServletRequest request;
    /**
     * 用于SpEL表达式解析.
     */
    private static SpelExpressionParser parser = new SpelExpressionParser();
    /**
     * 用于获取方法参数定义名字.  能根据你传入的方法 获取该方法的参数名
     */
    private static DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
//切点 表明我们要对哪个目标进行增加  附加功能  本例对注解附加
    @Pointcut("@annotation(com.example.RasTest.bussinessLog.annotation.SysLog)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public void around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        //执行方法
        Object result = point.proceed();
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        System.out.println(result);
        //保存日志
        saveSysLog(point, time);


    }
    private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {
    //根据切点获取我们的切面类的方法  目标方法 target 被增强的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 在此可以定义我们的日志实体类 用于插入数据库 如果只是单纯写入文件或者输出到控制台 不必定义
        /*Log sysLog = new Log();
        sysLog.setTime(time);*/
        SysLog syslog = method.getAnnotation(SysLog.class);
        //判断是否获取得到我们在目标方法上使用的
        if (syslog != null) {
            //注解上的描述
            if (!StringUtils.isEmpty(syslog.value())) {
                System.out.println(syslog.value());
            }
            if (!StringUtils.isEmpty(syslog.spelValue())) {
                String spelValue = generateKeyBySpEL(syslog.spelValue(), joinPoint);
                System.out.println(spelValue);
            }
        }
        //请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        //sysLog.setMethod(className + "." + methodName + "()");
        System.out.println(className + "." + methodName + "()");
        //请求的参数  从切点获取目标方法的参数	
        Object[] args = joinPoint.getArgs();
        try {
            System.out.println(JSONObject.toJSONString(args));
        } catch (Exception e) {

        }

        //设置IP地址
        /*sysLog.setIp(ServletUtil.getClientIP(request));
        UserAgent ua = UserAgentUtil.parse(request.getHeader("User-Agent"));
        sysLog.setBrowser(ua.getBrowser().toString());*/
        System.out.println("successful");

        //保存系统日志
       // logService.create(sysLog);
    }


    public static String generateKeyBySpEL(String spELString, ProceedingJoinPoint joinPoint) {
        // 通过joinPoint获取被注解方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
        String[] paramNames = nameDiscoverer.getParameterNames(method);
        // 解析过后的Spring表达式对象
        Expression expression = parser.parseExpression(spELString);
        // spring的表达式上下文对象
        EvaluationContext context = new StandardEvaluationContext();
        // 通过joinPoint获取被注解方法的形参
        Object[] args = joinPoint.getArgs();
        // 给上下文赋值
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        // 表达式从上下文中计算出实际参数值
        /*如:
            @annotation(key="#student.name")
             method(Student student)
             那么就可以解析出方法形参的某属性值,return “xiaoming”;
          */
        return expression.getValue(context).toString();
    }

}

入参

@Data
public class SearchVo {
    String keyWord;

    String name;
}

controller

@PostMapping(value = "/test/aopLog")
    @SysLog(value = "用户登录",spelValue =  "'高级搜索' + #searchVo.keyWord + #id")
    public String test(@RequestBody SearchVo searchVo){

        String id = UUID.randomUUID().toString();
        return id;
    }

如果想实现日志数据入库 就实现日志数据的service 和dao层 在增强方法@Around中调用

在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值