AOP Maven依赖

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

编写目标类与方法

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user/api/v1/")
public class UserController { 

    @RequestMapping("/seckill")
    public String seckill(){
        return "OK";
    }
}

编写切面类,使得切面绑定目标类

import com.zanglikun.springdataredisdemo.aop.appendDescAop.annotation.AppendFieldDesc;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;

@Aspect // 声明切面类
@Component // 注册为Spring组件,不然切面类不生效,这里我是先关闭了,你使用请务必开启
@Slf4j
public class AopAdvice {


    /**
     * Pointcut 定义切入点,一般为方法级别。通过切点表达式体现。
     * A原表达式:execution(public * com.zanglikun.springdataredisdemo.controller..*.*(..))
     * A表达式是切入 com.zanglikun.springdataredisdemo.controller 下面所有的Public方法
     * <p>
     * 一般还有匹配注解的如:@annotation(com.zanglikun.springdataredisdemo.aop.fileLimitAop.FileLimit)
     * 就是切入被FileLimit的注解
     */
    @Pointcut("execution(public * com.zanglikun.springdataredisdemo.controller..*.*(..))")
    public void onePointCut() {
    }

    /**
     * 环绕通知
     * 理解点1:joinPoint.proceed()作用是:执行被拦截的方法。
     * 为什么叫环绕通知呢?我们一般在joinPoint.proceed()。上下位置放置我们要执行的代码,可以实现在方法前、后执行代码
     *
     * @param joinPoint 切点 注意Around的joinPoint 与其他的不一样
     * @return
     * @throws Throwable
     * @Around("onePointCut()") 这种定义切入点方式和直接在@Around里面声明具体得切入点表达式一样
     * 环绕通知,更灵活得自定义增强通知,可以注入ProceedingJoinPoint获取相关信息和执行方法
     */
    @Around("onePointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        Object object = null;
        log.info("环绕通知开始:我在方法前面执行了");
        object = joinPoint.proceed(); //调用被切入方法,并获取其返回值
        log.info("环绕通知结束:我在方法执行后再执行");

        return object;
    }

    /**
     * 用于在切入点的流程执行前生效,可以在方法中注入JoinPoint用于获取相关信息
     *
     * @param joinPoint 切点对象
     */
    @Before("onePointCut()")
    public void before(JoinPoint joinPoint) {
        log.info("我是前置通知:我在方法前被执行");
    }

    /**
     * finally通知,这就意味着无论如何都会执行这个通知(不论是否发生异常),可以在方法中注入JoinPoint用于获取相关信息
     *
     * @param joinPoint 切点对象
     */
    @After("onePointCut()")
    public void after(JoinPoint joinPoint) {
        log.info("我是finally通知:无论如何(只要不停电、强杀进程)我都会执行");
    }

    /**
     * 异常通知,出现异常时执行,可以在方法中注入JoinPoint和Throwable用于获取相关信息
     *
     * @param joinPoint 切点对象
     * @param e         异常对象。由throwing指定变量名!
     */
    @AfterThrowing(pointcut = "onePointCut()", throwing = "e")
    public void throwing(JoinPoint joinPoint, Exception e) {
        log.info("我是异常通知:仅在出现异常时触发,本地异常:{}", e.getMessage());
    }

    /**
     * 后置返回前通知 ,在finally通知之后,方法正常退出前执行,可以注入JoinPoint 和Object 获取相关信息和方法执行成功的返回结果
     * 注意:returning的内容必须要与变量保持一致
     *
     * @param joinPoint 切点对象
     * @param object    目标方法返回的对象,一般有returning执行变量名赋值
     */
    @AfterReturning(pointcut = "onePointCut()", returning = "object")
    public void returning(JoinPoint joinPoint, Object object) {
        getJoinPointInfo(joinPoint, object);
        log.info("我是后置通知,响应结果为:" + object);
    }
}

测试

我看别人说不同版本的Spring版本输出结果不一样!

2023-06-07 00:42:08.075  INFO 88128 --- [nio-8089-exec-5] c.z.s.config.AopAdvice                   : 环绕通知开始:我在方法前面执行了
2023-06-07 00:42:08.075  INFO 88128 --- [nio-8089-exec-5] c.z.s.config.AopAdvice                   : 我是前置通知:我在方法前被执行
2023-06-07 00:42:08.075  INFO 88128 --- [nio-8089-exec-5] c.z.s.config.AopAdvice                   : 我是后置通知,响应结果为:OK
2023-06-07 00:42:08.075  INFO 88128 --- [nio-8089-exec-5] c.z.s.config.AopAdvice                   : 我是finally通知:无论如何(只要不停电、强杀进程)我都会执行
2023-06-07 00:42:08.075  INFO 88128 --- [nio-8089-exec-5] c.z.s.config.AopAdvice                   : 环绕通知结束:我在方法执行后再执行

如果你要拓展JoinPoint属性,请参考如下代码:

这代码的意思是给一个接口返回后,处理其返回对象的字段,如果有@AppendFieldDesc就会触发替换字典值的非生产代码。当然有些RequestBody在此处意义不大,仅仅做更加健全的demo使用哈。

    /**
     * 后置返回前通知 ,在finally通知之后,方法正常退出前执行,可以注入JoinPoint 和Object 获取相关信息和方法执行成功的返回结果
     * 注意:returning的内容必须要与变量保持一致
     *
     * @param joinPoint 切点对象
     * @param object    目标方法返回的对象,一般有returning执行变量名赋值
     */
    @AfterReturning(pointcut = "onePointCut()", returning = "object")
    public void returning(JoinPoint joinPoint, Object object) {
        getJoinPointInfo(joinPoint, object);
        log.info("我是后置通知,响应结果为:" + object);
    }

    /**
     * @param joinPoint
     * @return
     */
    public Object getJoinPointInfo(JoinPoint joinPoint, Object returnRes) {
        // 这串代码的含义是获取HttpServletRequest、HttpServletResponse获取的是本线程的哦
        {
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = requestAttributes.getRequest();
            HttpServletResponse response = requestAttributes.getResponse();
        }

        Object[] args = joinPoint.getArgs(); // 获取方法的所有入参值,但是无法准确获取入参类型,但是要去修改属性值,必须得获取对象
        for (Object arg : args) {
            System.out.println(arg);
        }

        // 获取AOP签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod(); // 获取被调用的方法
        Class<?> returnType = method.getReturnType(); // 获取返回值的类型
        Parameter[] parameters = method.getParameters(); // 获取方法的入参
        for (Parameter parameter : parameters) {
            parameter.getName(); // 获取参数名称例如:User user 获取的是user
            Annotation[] classAnnotations = parameter.getAnnotations(); // 获取该入参的所有注解
            Class<?> type = parameter.getType(); // 获取入参的字节码文件以便于快速获得变量的属性
            if (parameter.isAnnotationPresent(RequestBody.class)) { // 如果该变量被RequestBody修饰了,才会执行
                Field[] declaredFields = type.getDeclaredFields(); // 获取该变量的所有属性列表
                for (Field declaredField : declaredFields) {
                    if (declaredField.isAnnotationPresent(AppendFieldDesc.class)) { // 如果变量被AppendFieldDesc修饰后,才执行
                        // 开始替换为字典值
                        try {
                            declaredField.setAccessible(true);
                            Object o = declaredField.get(returnRes);
                            if (o instanceof String) {
                                getDictionary(); // 初始化数据
                                HashMap<String, Object> stringObjectHashMap = returnMap.get(declaredField.getName());
                                declaredField.set(returnRes, stringObjectHashMap.get(o));
                            }
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e);
                        } finally {
                            declaredField.setAccessible(false);
                        }
                    }
                }
            }
        }
        return returnRes;
    }

    HashMap<String, HashMap<String, Object>> returnMap = new HashMap<>();

    // 自己主动去缓存Cache Code=Name oldValue NewValue
    HashMap<String, HashMap<String, Object>> getDictionary() {
        HashMap<String, Object> dbReturn = new HashMap<>();
        dbReturn.put("1", "张三");
        dbReturn.put("2", "李四");
        returnMap.put("name", dbReturn);
        return returnMap;
    }
特殊说明:
上述文章均是作者实际操作后产出。烦请各位,请勿直接盗用!转载记得标注原文链接:www.zanglikun.com
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取全部资料 ❤