第二节 使用自定义注解实现AOP功能

亮子 2023-06-07 07:30:16 17095 0 0 0

1、引入依赖

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
        </dependency>

2、自定义注解

package com.shenma2009.log;

import java.lang.annotation.*;

/**
 * @author 军哥
 * @version 1.0
 * @description: 自定义注解
 * @date 2023/6/7 8:57
 */

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SmLog {
    // 日志标识
    String value() default "sm";
    // 日志级别
    String level() default "info";
}

元注解

  • @Target:

   @Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

  作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

  **取值(ElementType)有:**

    1.CONSTRUCTOR:用于描述构造器
    2.FIELD:用于描述域
    3.LOCAL_VARIABLE:用于描述局部变量
    4.METHOD:用于描述方法
    5.PACKAGE:用于描述包
    6.PARAMETER:用于描述参数
    7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

  • @Retention:

  @Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

  作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

  **取值(RetentionPoicy)有:**

    1.SOURCE:在源文件中有效(即源文件保留)
    2.CLASS:在class文件中有效(即class保留)
    3.RUNTIME:在运行时有效(即运行时保留)

3、切面类

package com.shenma2009.log;

import com.alibaba.fastjson2.JSON;
import com.shenma2009.domain.ResultResponse;
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 java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

/**
 * @author 军哥
 * @version 1.0
 * @description: SmLogAspect
 * @date 2023/6/7 9:01
 */

@Aspect
@Component
public class SmLogAspect {

    @Pointcut("@annotation(com.shenma2009.log.SmLog)")
    public void smLogPointCut() {}

    /**
     * @description 后置通知
     * @author 军哥
     * @date 2023/6/7 9:12
     * @version 1.0
     */
    @AfterReturning(value = "smLogPointCut()", returning = "returnValue")
    public void doAfterReturning(JoinPoint point, Object returnValue){

        MethodSignature signature = (MethodSignature) point.getSignature();
        // 获取方法名
        String methodName = signature.getName();
        // 获取参数
        List<Object> args = Arrays.asList(point.getArgs());

        Method method = signature.getMethod();
        SmLog annotation = method.getAnnotation(SmLog.class);
        System.out.println("后置通知 => value:"+annotation.value());
        System.out.println("后置通知 => level:"+annotation.level());

        System.out.println("后置通知 => 方法为:" + methodName + ",参数为:" + JSON.toJSONString(args) + ",返回值为:" + JSON.toJSONString(returnValue));
    }

    /**
     * @description 前置通知
     * @author 军哥
     * @date 2023/6/7 15:17
     * @version 1.0
     */
    @Before(value = "execution(* com.shenma2009.controller.IndexController.insertSmLog(..))")
    public void doBefore(JoinPoint point) throws Exception {
        MethodSignature signature = (MethodSignature) point.getSignature();

        // 获取注解信息
        Method method = signature.getMethod();
        SmLog annotation = method.getAnnotation(SmLog.class);
        System.out.println("前置通知 => value:"+annotation.value());
        System.out.println("前置通知 => level:"+annotation.level());

        // 检查权限
        if(!annotation.value().equals("insert")) {
            throw new Exception("权限不足");
        }
        System.out.println("前置通知 => 权限合法");
    }

    /**
     * @description 环绕通知
     * @author 军哥
     * @date 2023/6/7 14:53
     * @version 1.0
     */

    // 第一种方式
    @Around("@annotation(com.shenma2009.log.SmLog)")
    // 第二种表达式
    //@Around(value = "smLogPointCut()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        //获取方法参数
        Object[] args = point.getArgs();
        System.out.println("环绕通知 => 参数为:" + JSON.toJSONString(args));

        ResultResponse ret = (ResultResponse) point.proceed();
        if(ret.getCode() == 200){
            //code=1,说明操纵成功
            System.out.println("环绕通知 => 操纵成功");
            return ret;
        }
        //操作失败返回Result的执行结果
        System.out.println("环绕通知 => 操纵失败");
        return ret;
    }

    //设置抛出异常后通知获取原始方法运行时抛出的异常对象,要求throwing属性值必须与方法形参名相同
    @AfterThrowing(value = "smLogPointCut()",throwing = "t")
    public void afterThrowing(Throwable t) {
        System.out.println("异常通知 => "+JSON.toJSONString(t));
    }
}

4、controller类

package com.shenma2009.controller;

import com.shenma2009.domain.ResultResponse;
import com.shenma2009.log.SmLog;
import com.shenma2009.vo.IdVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

/**
 * @author 军哥
 * @version 1.0
 * @description: IndexController
 * @date 2023/6/7 9:20
 */

@RestController
@RequestMapping(value = "/index")
@Slf4j
public class IndexController {

    @PostMapping(value = "listRandSmLog/{id}")
    @SmLog(value = "test", level = "debug")
    public ResultResponse listRandSmLog(@PathVariable("id") Integer id) {
        log.info("id="+id);
        return ResultResponse.SUCCESS(id);
    }

    @PostMapping(value = "/insertSmLog")
    @SmLog(value = "insert", level = "info")
    public ResultResponse insertSmLog(@RequestBody IdVo idVo) {
        log.info("id="+idVo.getId());

        return ResultResponse.SUCCESS(idVo);
    }

    @PostMapping(value = "/throwError")
    @SmLog(value = "insert", level = "info")
    public ResultResponse throwError() {
        String msg = null;
        System.out.println(msg.length());

        return ResultResponse.SUCCESS();
    }

}

5、测试结果

图片alt