Java AOP自定义注解

一、背景

在之前文章:Java注解详解中,主要介绍了注解的含义、作用、以及常用的各类注解。今天主要介绍在Springboot中如何实现一个自定义注解,通过自定义注解去实现一些定制化的需求。

二、了解元注解

『元注解』是用于修饰注解的注解,通常用在注解的定义上,例如:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

这是我们 @Override 注解的定义,你可以看到其中的 @Target,@Retention 两个注解就是我们所谓的『元注解』,『元注解』一般用于指定某个注解生命周期以及作用目标等信息。

JAVA 中有以下几个『元注解』:

@Target:注解的作用目标
@Retention:注解的生命周期
@Documented:注解是否应当被包含在 JavaDoc 文档中
@Inherited:是否允许子类继承该注解

@Target:用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。语法如下:

@Target(value = {ElementType.METHOD})
  • ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
  • ElementType.FIELD:允许作用在属性字段上
  • ElementType.METHOD:允许作用在方法上
  • ElementType.PARAMETER:允许作用在方法参数上
  • ElementType.CONSTRUCTOR:允许作用在构造器上
  • ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
  • ElementType.ANNOTATION_TYPE:允许作用在注解上
  • ElementType.PACKAGE:允许作用在包上

@Retention: 注解指定了被修饰的注解的生命周期。语法如下:

@Retention(value = RetentionPolicy.RUNTIME
  • RetentionPolicy.SOURCE:注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视
  • RetentionPolicy.CLASS:注解只被保留到编译进行的时候,不会被加载到JVM中
  • RetentionPolicy.RUNTIME:注解可以保留到程序运行的时候,会被加载到JVM中,所以程序运行时可以获取到它

剩下两种类型的注解我们日常用的不多,也比较简单,需要知道他们各自的作用即可:

  • @Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。
  • @Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。

三、创建Springboot AOP自定义注解

假设需求是每个方法调用的时候,我们都希望打印出方法名称,并且打印出发放调用的耗时时间。每个方法都去写代码实现就会显得比较耗时和臃肿。

这个时候我们自定义一个注解,然后只需要在有这个需求的方法上加上注解就OK了,这样实现起来就会非常方便。

AOP:在面向对象编程(oop)思想中,我们将事物纵向抽成一个个的对象。而在面向切面编程中(AOP),我们将一个个的对象某些类似的方面横向抽成一个切面,对这个切面进行一些如权限控制、日志操作等公用操作处理的过程就是面向切面编程的思想。

新建annotation包,然后下面新建InterfaceLog注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 接口日志注解
 * @see InterfaceLogAspect
 * */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InterfaceLog {
    String value() default "";
}

定义了该注解是运行时生效,注解作用在method方法上。

新建InterfaceLogAspect,通过AOP切面实现自定义注解InterfaceLog的代码逻辑:

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 java.lang.reflect.Method;

/**
 * 该类中编写InterfaceLog注解的代码逻辑
 */

@Aspect
@Component
@Slf4j
public class InterfaceLogAspect {
    private long startTime;
    private long endTime;

    /**
     * PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名
     * 切面最主要的就是切点,所有的故事都围绕切点发生
     * logPointCut()代表切点名称
     */
    @Pointcut("@annotation(InterfaceLog)")
    private void logPointCut(){}

    /**
     * 目标方法调用之前执行
     * 注意这里不能使用 ProceedingJoinPoint
     * @param joinPoint
     */
    @Before("logPointCut()")
    public void doBefore(JoinPoint joinPoint){
        log.info("Before Test");
    }

    /**
     * 目标方法调用之后执行
     * 注意这里不能使用 ProceedingJoinPoint
     * @param joinPoint
     */
    @After("logPointCut()")
    public void doAfter(JoinPoint joinPoint){
        log.info("End Test");
    }

    /**
     * 环绕通知
     * @param proceedingJoinPoint
     */
    @Around("logPointCut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        //方法调用前
        MethodSignature methodSignature =(MethodSignature) proceedingJoinPoint.getSignature();
        //获取方法名称
        String methodName=methodSignature.getName();
    	
        //获取@InterfaceLog注解入参的值
        Method method = methodSignature.getMethod();
        InterfaceLog interfaceLog=method.getAnnotation(InterfaceLog.class);
        String value=interfaceLog.value();

        startTime=System.currentTimeMillis();
        
        //根据入参值不同,使用不同的日志打印级别打印日志
        if(value==null || value.equals("")){
            log.info("==================开始打印日志==================");
            log.info("方法名为:"+methodName);
        }else if(value.equals("info")){
            log.info("==================开始打印日志==================");
            log.info("方法名为:"+methodName);
        }else if(value.equals("warn")){
            log.warn("==================开始打印日志==================");
            log.warn("方法名为:"+methodName);
        }else if(value.equals("error")){
            log.error("==================开始打印日志==================");
            log.error("方法名为:"+methodName);
        }else{
            log.error("自定义注解入参不正确!");
        }

        //继续执行方法
        Object result=proceedingJoinPoint.proceed();

        //方法调用后,打印方法耗时
        endTime = System.currentTimeMillis();
        if(value==null || value.equals("")){
            log.info("方法耗时为:"+(endTime -startTime));
            log.info("==================结束打印日志==================");
        }else if(value.equals("info")){
            log.info("方法耗时为:"+(endTime -startTime));
            log.info("==================结束打印日志==================");
        }else if(value.equals("warn")){
            log.warn("方法耗时为:"+(endTime -startTime));
            log.warn("==================结束打印日志==================");
        }else if(value.equals("error")){
            log.error("方法耗时为:"+(endTime -startTime));
            log.error("==================结束打印日志==================");
        }else{
            log.error("自定义注解入参不正确!");
        }
        return result;
    }
}

切面类实现了记录方法调用前的时间、调用后的时间,两者相减得到方法的执行耗时。获取注解的入参value的值,根据入参的值来决定打印哪种级别的日志。

UserController类的register方法上,加上上面我们自定义的注解@InterfaceLog,注解的参数value设置值为warn:

@InterfaceLog(value = "warn")
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String register(String name, Integer age, String pwd, Model model, HttpServletRequest request, HttpServletResponse response)throws Exception{

    try{
        //打印日志
        log.info(name+","+age+","+pwd);
        //获取注册的结果
        User result = userServices.register(name, age, pwd);

        if(result.isSuccess()){
            //将结果存到model里面,用于前端view层展示
            model.addAttribute("result",result);
            //跳转至注册结果页面
            return "/registerResult";
        }else{
            response.setContentType("application/json; charset=utf-8");
            response.getWriter().print("{\"code\":\"0002\",\"msg\":\"用户名已存在,注册失败!\"}");
        }
    }catch (Exception e){
        e.printStackTrace();
    }

    return null;
}

启动项目,调用register接口,可以看到自定义注解正常生效,doAround中打印的日志级别为注解入参传的Warn级别:

在这里插入图片描述

设置@InterfaceLog(value = “error”), 调用register接口,系统就打印error级别的日志:

在这里插入图片描述

设置@InterfaceLog不传参, 默认参数就是空,调用register接口,系统就默认打印info级别的日志:

在这里插入图片描述

Java AOP自定义注解的使用场景有很多,多数都是用于一些增强功能,比如上面我们举例的用于日志打印,还有常用的如统计方法耗时、多数据源切换、防重等等。

================================================================================================
以上就是本次的全部内容,都看到这里了,如果对你有帮助,麻烦点个赞+收藏+关注,一键三连啦~

欢迎下方关注我的公众号:程序员杨叔,各类文章都会第一时间在上面发布,持续分享全栈测试知识干货,你的支持就是作者更新最大的动力~

  • 7
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中可以通过自定义注解实现AOP,其中Spring Boot框架提供了一种简单的方式来创建自定义注解实现AOP。 首先,我们需要定义一个注解,注解可以使用在类、方法或者字段上。我们可以使用元注解`@Target`来指定注解的使用位置,比如在方法上使用。然后,我们可以使用元注解`@Retention`来指定注解的生命周期,比如运行时生命周期。接着,我们可以使用元注解`@Documented`来指定注解是否会保存在JavaDoc文档中。最后,我们还可以使用元注解`@Inherited`来指定注解是否具有可继承性。 在实现AOP时,我们可以通过自定义注解和切面来实现一些增强功能,比如日志打印、方法耗时统计、多数据源切换等。我们可以在自定义注解上添加一些切面逻辑,然后在需要应用这些增强功能的地方使用这个注解。 具体实现过程如下: 1. 定义一个自定义注解,使用`@Target`指定注解的使用位置,使用`@Retention`指定注解的生命周期,使用`@Documented`指定是否保存在JavaDoc文档中,使用`@Inherited`指定是否具有可继承性。 2. 创建一个切面类,使用`@Aspect`注解标识该类为切面类,并在该类中定义一些增强功能的逻辑,比如在方法执行前后打印日志。 3. 在需要应用增强功能的地方,使用自定义注解来标识,例如在方法上添加自定义注解。 4. 在Spring Boot配置类中,通过`@EnableAspectJAutoProxy`注解开启AOP功能。 通过以上步骤,我们就可以在Java中使用自定义注解实现AOP了。这样,我们可以通过在需要应用增强功能的地方使用自定义注解来触发切面逻辑,从而实现AOP的效果。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Java AOP自定义注解](https://blog.csdn.net/baidu_28340727/article/details/128319277)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值