4

个人学习系列 - Spring Boot 自定义注解实现

 2 years ago
source link: https://segmentfault.com/a/1190000040254283
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

研究一下自定义注解实现。

新建Spring Boot项目

1. pom.xml

引入相关依赖

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

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

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

2. OnePrint.java

import java.lang.annotation.*;

/**
 * 功能: 一个注解
 * @author : zhouzhaodong
 * @date : 2021/6/28 16:02
 * @version : 1.0
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnePrint {

//    @Documented  注解表明制作javadoc时,是否将注解信息加入文档。如果注解在声明时使用了@Documented,则在制作javadoc时注解信息会加入javadoc
//    @Target(ElementType.TYPE)  接口、类、枚举、注解
//    @Target(ElementType.FIELD)  字段、枚举的常量
//    @Target(ElementType.METHOD)  方法
//    @Target(ElementType.PARAMETER)  方法参数
//    @Target(ElementType.CONSTRUCTOR)  构造函数
//    @Target(ElementType.LOCAL_VARIABLE)  局部变量
//    @Target(ElementType.ANNOTATION_TYPE)  注解
//    @Target(ElementType.PACKAGE)  包
//    @Retention(RetentionPolicy.SOURCE)  这种类型的Annotations只在源代码级别保留,编译时就会被忽略
//    @Retention(RetentionPolicy.CLASS)  这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
//    @Retention(RetentionPolicy.RUNTIME)  这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用

}

3. AspectJ.java

注解实现类

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * 功能: 注解实现类
 *
 * @author : zhouzhaodong
 * @version : 1.0
 * @date : 2021/6/28 16:09
 */
@Component
@Aspect
@Slf4j
public class AspectJ {

    @Pointcut("@annotation(com.example.annotation.util.OnePrint)")
    public void annotationPointCut() {
    }

    /**
     * 前置通知,方法调用前被调用
     *
     * @param joinPoint/null
     */
    @Before(value = "annotationPointCut()")
    public void before(JoinPoint joinPoint) {
        //获取目标方法的参数信息
        Object[] obj = joinPoint.getArgs();
        log.info("前置通知获取方法名称为:" + joinPoint.getSignature().getName());
        log.info("前置通知获取的请求参数为:" + obj);
        //AOP代理类的信息
        joinPoint.getThis();
        //代理的目标对象
        joinPoint.getTarget();
        //用的最多 通知的签名
        Signature signature = joinPoint.getSignature();
        //代理的是哪一个方法
        log.info("前置通知代理的是哪一个方法" + signature.getName());
        //AOP代理类的名字
        log.info("前置通知AOP代理类的名字" + signature.getDeclaringTypeName());
        //AOP代理类的类(class)信息
        signature.getDeclaringType();
        //获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        //如果要获取Session信息的话,可以这样写:
        //HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
        //获取请求参数
        Enumeration<String> enumeration = request.getParameterNames();
        Map<String, String> parameterMap = new HashMap<>();
        while (enumeration.hasMoreElements()) {
            String parameter = enumeration.nextElement();
            parameterMap.put(parameter, request.getParameter(parameter));
        }
        String str = String.valueOf(parameterMap);
        if (obj.length > 0) {
            log.info("前置通知请求从request获取的参数信息为:" + str);
        }
    }

    /**
     * 后置返回通知
     * 这里需要注意的是:
     * 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
     * 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
     * returning:限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,
     * 对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
     *
     * @param joinPoint
     * @param keys
     */
    @AfterReturning(value = "annotationPointCut()", returning = "keys")
    public void doAfterReturningAdvice1(JoinPoint joinPoint, Object keys) {
        log.info("第一个后置返回获取方法名称为:" + joinPoint.getSignature().getName());
        log.info("第一个后置返回获取的请求参数为:" + Arrays.toString(joinPoint.getArgs()));
        log.info("第一个后置返回通知的返回值:" + keys);
    }

    @AfterReturning(value = "annotationPointCut()", returning = "keys", argNames = "keys")
    public void doAfterReturningAdvice2(String keys) {
        log.info("第二个后置返回通知的返回值:" + keys);
    }

    /**
     * 后置异常通知
     * 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
     * throwing:限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
     * 对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
     *
     * @param joinPoint
     * @param exception
     */
    @AfterThrowing(value = "annotationPointCut()", throwing = "exception")
    public void doAfterThrowingAdvice(JoinPoint joinPoint, Throwable exception) {
        //目标方法名:  
        log.info("后置异常通知获取方法名称为:" + joinPoint.getSignature().getName());
        log.info("后置异常通知获取的请求参数为:" + Arrays.toString(joinPoint.getArgs()));
        if (exception instanceof NullPointerException) {
            log.info("发生了空指针异常!!!!!");
        }
    }

    /**
     * 后置最终通知(目标方法只要执行完了就会执行后置通知方法)
     *
     * @param joinPoint
     */
    @After(value = "annotationPointCut()")
    public void doAfterAdvice(JoinPoint joinPoint) {
        //目标方法名:
        log.info("后置最终通知获取方法名称为:" + joinPoint.getSignature().getName());
        log.info("后置最终通知获取方法参数为:" + Arrays.toString(joinPoint.getArgs()));
        log.info("后置最终通知执行了!!!!");
    }

    /**
     * 环绕通知:
     * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
     * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     */
    @Around(value = "annotationPointCut()")
    public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
        log.info("环绕通知开始了!!!!");
        try {
            // Object target = point.getTarget(); 获取的实体类
            // String methodName = point.getSignature().getName(); 获取的方法名称
            // Object[] args = point.getArgs(); 获取的方法参数
            log.info("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
            log.info("环绕通知获取参数为:" + Arrays.toString(proceedingJoinPoint.getArgs()));
            Object[] args = processInputArg(proceedingJoinPoint);
            // 执行方法,这里可以操作进行限流等其他操作
            Object obj = proceedingJoinPoint.proceed(args);
            processOutPutObj(obj);
            return obj;
        } catch (Throwable throwable) {
            log.info("环绕通知出现异常了!!!!");
            throwable.printStackTrace();
        }
        log.info("环绕通知结束了!!!!");
        return null;
    }

    /**
     * 处理输入参数
     *
     * @param pjp 切面
     */
    private Object[] processInputArg(ProceedingJoinPoint pjp) {
        Object[] args = pjp.getArgs();
        for (Object arg : args) {
            System.out.println("ARG原来为:" + arg);
        }
        args[0] = "改一下进参";
        return args;
    }

    /**
     * 处理返回对象
     *
     * @param obj 返回值
     */
    private void processOutPutObj(Object obj) {
        System.out.println("OBJ 原本为:" + obj);
        Map<String, Object> map = (Map<String, Object>) obj;
        map.put("newMessage", "改一下出参" + map.get("newMessage"));
    }
}

4. AnnotationController.java

import com.example.annotation.util.OnePrint;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * 功能: 注解测试类
 *
 * @author : zhouzhaodong
 * @version : 1.0
 * @date : 2021/6/28 16:30
 */
@RestController
public class AnnotationController {

    @GetMapping("/testAnnotation")
    @OnePrint
    public Object testAnnotation(String message){
        System.out.println(message);
        Map<String, Object> map = new HashMap<>();
        map.put("message", message);
        map.put("newMessage", "one" + message);
        return map;
    }
}

5. postman测试

发起请求后:
image.png
控制台打印log:
image.png
对比后发现入参和返回值都进行了修改。

个人博客地址:

http://www.zhouzhaodong.xyz/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK