SpringBoot 自定义注解的使用(AOP实现)

10 篇文章 0 订阅
8 篇文章 0 订阅

一、依赖

在正常的SpringBoot项目中加入如下:

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

二、自定义注解

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAop {
    String value() default "";

    String[] params() default {};
}   

1、@Target

ElementType 是一个枚举类型,它规范了注解的使用位置;

public enum ElementType {
    /** 类, 接口 (包括注解类型), 或 枚举 声明 */
    TYPE,
 
    /** 字段声明(包括枚举常量) */
    FIELD,
 
    /** 方法声明(Method declaration) */
    METHOD,
 
    /** 正式的参数声明 */
    PARAMETER,
 
    /** 构造函数声明 */
    CONSTRUCTOR,
 
    /** 局部变量声明 */
    LOCAL_VARIABLE,
 
    /** 注解类型声明 */
    ANNOTATION_TYPE,
 
    /** 包声明 */
    PACKAGE,
 
    /**
     * 类型参数声明
     *
     * @since 1.8
     */
    TYPE_PARAMETER,
 
    /**
     * 使用的类型
     *
     * @since 1.8
     */
    TYPE_USE
}

2、@Retention

RetentionPolicy这个枚举类型的常量描述保留注解的各种策略,它确定了注解的存活时间

public enum RetentionPolicy {
    /**
     * 注解只在源代码级别保留,编译时被忽略
     */
    SOURCE,
    /**
     * 注解将被编译器在类文件中记录
     * 但在运行时不需要JVM保留。这是默认的
     * 行为.
     */
    CLASS,
    /**
     *注解将被编译器记录在类文件中
     *在运行时保留VM,因此可以反读。
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

3、@Documented

这个注解表明将会被javadoc记录, 如果类型声明被这个注解了,它将成为公共API的一部分。

三、解析注解

在使用自定义注解的时候基本是使用AOP的方式来处理问题的,同时也有标注的功能,通常接口的注解通过AOP可以起到拦截作用,下面演示一下注解的功能,还是上面的注解,可以知道这个注解可以使用在方法上、类型上和属性上,首先定义一个测试的实例对象并使用这个注解

@TestAop("Dept")
public class Dept {

    private Long deptId;

    @TestAop("deptName")
    private String deptName;
    private DataSourceType dataSource;

    public Dept() { }

    public Dept(Long deptId, String deptName, DataSourceType dataSource) {
        this.deptId = deptId;
        this.deptName = deptName;
        this.dataSource = dataSource;
    }
    
    @Override
    public String toString() {
        return "Dept{" +
                "deptId=" + deptId +
                ", deptName='" + deptName + '\'' +
                ", dataSource=" + dataSource +
                '}';
    }
    /***************************省略setter和getter***********************************
}

这里可以看出对这个类使用了注解

在写一个方法来使用注解

@Service
public class DeptServiceImpl implements DeptService {
    public static final Logger logger = LoggerFactory.getLogger(DeptServiceImpl.class);
    
    @Override
    @TestAop(value = "TEST_AOP", params = {"name", "dept"})
    public String testAop(String name, Dept dept) {
        logger.info(name);
        logger.info(dept.toString());
        logger.info("testAop");
        return "testAop";
    }
}

这里方法使用了注解,同时传入的参数是上面使用注解的类型

下面使用AOP去使用注解,这里就不介绍AOP的使用了,这里使用前置增强,后置增强,环绕增强和返回增强来说明

@Aspect
@Component
public class TestAopAspect {
    public static final Logger logger = LoggerFactory.getLogger(TestAopAspect.class);

    /**
     * 设置使用这个注解的方法所在组成切面
     */
    @Pointcut("@annotation(com.example.myproject.aop.annotation.TestAop)")
    public void annotationPointcut() {
    }

    /**
     * 前置增强
     *
     * @param joinPoint
     */
    @Before("annotationPointcut()")
    public void beforePointcut(JoinPoint joinPoint) {
        // 此处进入到方法前  可以实现一些业务逻辑
        logger.info("beforePointcut2");
    }

    /**
     * 后置增强
     *
     * @param joinPoint
     */
    @After("annotationPointcut()")
    public void afterPointcut(JoinPoint joinPoint) {
        // 此处进入到方法前  可以实现一些业务逻辑
        logger.info("afterPointcut4");
    }

    /**
     * 环绕增强
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("annotationPointcut()")
    public String doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("doAround1");

        /**
         * 获取添加了这个注解的方法实例
         */
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        /**
         * 获取这个注解的信息
         */
        TestAop testAop = method.getAnnotation(TestAop.class);

        /**
         * 输出这个方法上这个注解的属性信息
         */
        logger.info(testAop.toString());

        // 获取传入的参数,是有序的数组对象
        Object[] args = joinPoint.getArgs();
        // 获取传入的参数名称,是有序的数组对象
        String[] argNames = signature.getParameterNames();

        // 这里把参数名称和参数配对
        HashMap<String, Object> params = new HashMap<>(2);
        for (int i = 0, length = argNames.length; i < length; i++) {
            params.put(argNames[i], args[i]);
        }

        // 获取参数的值
        String name = params.get("name").toString();
        Dept dept = (Dept) params.get("dept");
        Object obj = params.get("dept");
        // 这里对参数修改一下
        dept.setDataSource(DataSourceType.db02);
        args[0] = dept.getDeptName();
        args[1] = dept;

        // 这里获取传入参数的注解,也就是上面定义的Dept对象类名上的注解信息
        TestAop deptAop = obj.getClass().getAnnotation(TestAop.class);
        // 输出类型上的注解信息
        logger.info(deptAop.toString());

        // 通过反射的方式获取属性信息
        Class clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            field.setAccessible(true);
            // 获取属性上的注解的信息
            TestAop fieldAop = field.getAnnotation(TestAop.class);
            if (fieldAop != null) {
                logger.info(fieldAop.toString());
                logger.info(fieldAop.value() + ":" + field.getName() + "=" + field.get(obj));
            }
        }

        /**
         * 执行方法,传入我们修改后的参数,返回值是拦截的方法的返回值
         * 这里要注意,已办情况下这个返回值要被这个环绕增强方法返回
         * 否则拦截的方法使用时将没有返回值
         */
        Object ottt = joinPoint.proceed(args);
        logger.info("doAround3");

        // 这里返回的值是拦截的方法返回值加上拼接的字段作为返回值,也将是拦截方法的返回值
        return ottt + "doAround 返回值";
    }

    /**
     * 返回增强
     *
     * @param joinPoint
     * @param data
     */
    @AfterReturning(pointcut = "annotationPointcut()", returning = "data")
    public void doAfterReturning(JoinPoint joinPoint, Object data) {
        //这里要注意,有环绕增强时一定要有返回值,否则这个data就没有值,这个data就是返回值
        logger.info(data.toString());
        logger.info("doAfterReturning5");
    }
}

这里添加一个调用接口,使用web的方式调用方法测试

@RestController
public class HelloWorldController {
    public static final Logger logger = LoggerFactory.getLogger(HelloWorldController.class);
    @Autowired
    private DeptService deptService;

    @RequestMapping("/hello")
    public String index() {
        return deptService.testAop("Hello World",new Dept(1L,"hello", DataSourceType.db01));
    }
}

启动主函数测试

在这里插入图片描述

浏览器返回值如上,我们可以知道这里的返回值应该是testAop方法的返回值,但是这个方法的返回值应该是testAop,而输出的结果是是被环绕增强修改过的,也从侧面说明如果环绕增强没有返回值,也将没有输出结果

查看后台

doAround1
@com.example.myproject.aop.annotation.TestAop(params=[name, dept], value=TEST_AOP)
@com.example.myproject.aop.annotation.TestAop(params=[], value=Dept)
@com.example.myproject.aop.annotation.TestAop(params=[], value=deptName)
deptName:deptName=hello
beforePointcut2
hello
Dept{deptId=1, deptName='hello', dataSource=db02}
testAop
doAround3
afterPointcut4
testAopdoAround 返回值
doAfterReturning5

通过后台我们可以知道增强的调用顺序

1、增强调用顺序

首先是环绕增强的前置部分,然后的前置增强到环绕增强的后置,然后是后置增强,最后是返回增强

2、环绕增强获取注解信息

  1. 获取接口参数

    使用环绕增强的参数ProceedingJoinPoint joinPoint,通过getSignature()方法获取拦截的实例

    然后通过实例的getAnnotation方法获取注解信息

  2. 获取类型的注解信息

    通过这个类的class对象使用getAnnotation可以获取

  3. 获取属性上的注解信息

    通过反射原理可以获取属性的实例对象,然后通过getAnnotation方法获取注解信息

3、环绕增强修改传入接口方法的参数

这里要了解一下proceed方法,有两个方法,无参的表示参数不修改,有参的参数是修改后要传入的参数,它表示执行的方法的位置是时机

所以我们修改了Deptname的值,重新传入,发现参数修改成功了

4、环绕增强的接口方法的返回值

通过上面可以知道proceed方法的返回值就是原来接口方法的返回值,经过环绕增强修改后,接口方法的实际返回值是修改后的,同时返回增强获取的返回值就是环绕增强的返回值,也就是被拦截的方法的有效返回值。

到这里就简单的介绍了自定义注解的使用了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值