十分钟实现自定义注解(基于SpringBoot AOP实现自定义注解)

6,111 阅读4分钟

01-前言:

Java注解是一种特殊标记,其作用包括对属性赋值,方法逻辑扩展,类、属性说明,异常处理等。注解的使用可以减少对代码逻辑部分的入侵,提升开发效率,如Spring中的@Component,@Async,@RestController等常用的注解。但是在实际开发中,也会遇到一些特殊情况无法使用已有注解解决开发需求的情况。本文将描述如何通过Spring AOP实现自定义注解。

02-实现自定义属性打印注解

  1. 定义一个注解类HunterLogAnno
/**
 *  定义一个普通注解
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface HunterLogAnno {
    String value() default "";
}
  1. 使用AOP对注解进行解析,需要定义一个切面类,包括自定义的切点方法normalPointCut(),以及连接点的处理方法normalPointAround()。连接点中的ProceedingJoinPoint可以获取被代理类的方法属性等。
import com.example.annotations.HunterLogAnno;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 使用 Aspect 实现注解,别忘记添加 @Component 注解让Aspect注解生效
 */
@Component
@Aspect
public class HunterAspect {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     *  这里定义一个PointCut,用于描述满足条件的方法
     */
    @Pointcut("execution(public * com.example.Services..*(*))")
    private void normalPointCut() {
        System.out.println("pointCut");
    }

    /** 将Services 文件下,且带有 HunterLogAnno 注解的类进行
     *
     * @param joinPoint
     */
    @Around("normalPointCut() && @annotation(com.example.annotations.HunterLogAnno)")
    private void normalPointAround(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("===========================");
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 执行具体的方法,在使用 Around 类的时候,需要对执行方法进行调用,否则只会执行 Around 方法 normalPointBefore 里面的逻辑
        joinPoint.proceed();
        // 注解解析
        HunterLogAnno hunterLogAnno = method.getAnnotation(HunterLogAnno.class);
        String value = hunterLogAnno.value();
        // 打印注解记录
        logger.info(value);
        logger.info("===========================");
    }

}

上边定义了一个切点和一个环绕处理方法,通过定义 @PointCut 和 @annotation 来对注解解析。这里有个容易忽视的点,使用 @Around 环绕织入时需要JointPoint 的proccess方法执行原方法 (未被代理时) ,否则只会执行上图中的 normalPointAround() 方法的流程,稍后可以利用这个特性实现方法的异步执行。在这里,使用@Around注解,获取被代理方法的属性,并输出log日志,其中 MethodSignature 类可以获取方法的名称,输入参数等,可以通过ProceedingJoinPoint 类获取该类的各种属性。

  1. 接下来,实现一个普通类,并定义value的属性值。
import com.example.annotations.HunterLogAnno;
import org.springframework.stereotype.Service;

@Service
public class HunterServiceImpl {

    @HunterLogAnno(value = "honda")
    public void getTheLogs(String name) {
        System.out.println(name);
    }
}
  1. 最后,定义Controller和Service类进行模拟调用
import com.example.Services.HunterServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyHtrixController {

    @Autowired
    private HunterServiceImpl service;
    
    @RequestMapping("/hunterCar")
    @ResponseBody
    public String reqHunterAnno() {
        service.getTheLogs("honda");
        return "honda!!";
    }

}

成功将 SpringBoot Start 启动后,使用浏览器输入网址:http://localhost:8771/hunterCar image.png

image.png

03-自定义注解实现方法的异步执行

  1. 首先自定义一个异步处理的注解@HunterAsyncAnno
import java.lang.annotation.*;

/**
 * 异步执行方法的注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface HunterAsyncAnno {
    // 核心线程数
    int coreSize() default 1;

    // 最大核心线程数
    int maxSize() default 1;

}
  1. 在Aspect中定义一个注解的解析,对标注了@HunterAsyncAnno的方法进行解析,这里使用上边所说的@Around 中JoinPoint.proccess()方法,在执行AOP外部方法时使用Thread方法对其进行包裹并执行。
    /**
## *  定义一个注解的切点
     * @param joinPoint
     */
    @Around("normalPointCut() && @annotation(com.example.annotations.HunterAsyncAnno)")
    private void asyncPointAround(ProceedingJoinPoint joinPoint) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            HunterAsyncAnno hunterAsyncAnno = method.getAnnotation(HunterAsyncAnno.class);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(5000);
                        joinPoint.proceed();
                        System.out.println("END TIME:" + System.currentTimeMillis());
                    } catch (Throwable throwable) {
                        throwable.printStackTrace();
                    }
                }
            }).start();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
  1. 同样,在Service类中添加请求处理的方法:
import com.example.annotations.HunterAsyncAnno;
import com.example.annotations.HunterLogAnno;
import org.springframework.stereotype.Service;

@Service
public class HunterServiceImpl {

    @HunterLogAnno(value = "Integra")
    public void getTheLogs(String name) {
        System.out.println(name);
    }

    @HunterAsyncAnno
    public void asyncSystemOut(String name) {
        System.out.println("This text loggout in 5s!!");
    }

}

同样使用Controller方法进行测试,可以看到异步执行的结果。


import com.example.Services.HunterServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyHtrixController {

    @Autowired
    private HunterServiceImpl service;
    
    @RequestMapping("/hunterCar")
    @ResponseBody
    public String reqHunterAnno() {
        service.getTheLogs("honda");
        return "honda!!";
    }


    @RequestMapping("/hunterAsync")
    @ResponseBody
    public String reqHunterAsyncAnno() {
        System.out.println("BEGIN TIME:" + System.currentTimeMillis());
        service.asyncSystemOut("Integra!!");
        return "Integra!!";
    }

}

image.png

image.png

可以看到使用Thread类start后进行异步处理proccess(),在注解逻辑中使用Thread.sleep(5000)暂停了5s时间,可以看见打印的结果也相差了5s左右。

04-总结
以上,介绍了SpringBoot中结合AOP实现自定义注解的简单实践,依靠Spring中AOP的自定义切点以及通过ProceedingJoinPoint方法属性的获取进行更加多样化的注解定义。但是,这种注解也存在一定的缺点,由于使用的是AspectJ框架,因此在使用带有该注解的方法时,不能由该类的另一个方法调用,否则会另注解无效,这是因为动态代理的一些特点导致的。