SpringBoot 自定义注解结合AOP的简单使用

前一篇关于redis缓存的博客让我对注解起了兴趣,原本在培训班的时候觉得AOP的课好无聊,而且一直以来都没怎么用AOP,觉得一点用都没有。当然现在明白过去的无知还不晚。而且现在感觉自己会写注解之后逼格高了一个档次。

首先要讲的内容来只上一篇Redis缓存数组管理。

AOP切面编程首先要引入依赖

<!-- 切面编程 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

然后创建一个切面类aspect。这个类大致包含如下内容:数个切入点和数个通知方法,当然你也可以把切入点嵌入通知中。

如下

@Aspect
@Component
public class MyAspect {

   /*
    *定义一个切入点,指定切入的类型和位置。
    *类型:
       execution: 切入方法中,可以使用通配符来切入多个方法
      @annotation: 切入注解中,不可以使用通配符指定切入什么注解。对方法注解和类注解有效,对参数注解无效
      其他的请看别人总结的:https://blog.csdn.net/sunlihuo/article/details/52701548
    */
   //这里我们指定了切入点为MyAnnotaion注解.
   @Pointcut("@annotation(com.dingguan.template.annotation.MyAnnotaion)")
   private void value(){}

   /*
    * 定义通知。
    * 通知有5中方式:
    * @Before: 前置通知, 在方法执行之前执行
    * @After: 后置通知, 在方法执行之后执行 。
    * @AfterRunning: 返回通知, 在方法返回结果之后执行
    * @AfterThrowing: 异常通知, 在方法抛出异常之后
    * @Around: 环绕通知, 围绕着方法执行
    * 然后括号里选择切入点
    * 也可以直接将切入点写在这里,例如:
    * @Around("@annotation(com.dingguan.template.annotation.MyAnnotaion)")
    * 这样就可以不用写上面的切入点了。
    * 参数 ProceedingJoinPoint 继承自JoinPoint,
    * 可以用其获得切入方法的注解、注解的参数、执行返回结果等等
    * 我们可以从其获得需要的值后进行加工,
    * 如果仍需要执行原方法,则调用joinPoint.proceed(),这个方法可以拿到返回体。
    * 这个方法的返回体会替换掉原方法的返回体
    */
   @Around("value()")
   public Object set(ProceedingJoinPoint joinPoint) throws Throwable {
      MethodSignature signature = (MethodSignature) joinPoint.getSignature();
      //获得该方法的全部注解
      Annotation[][] annotations = signature.getMethod().getParameterAnnotations();
      //获得该方法的指定注解,获得注解之后调用类似于  参数名()  这样的方法就可以得到注解的参数
      MyAnnotaion annotaion = signature.getMethod().getAnnotation(MyAnnotaion.class);
      //调用下面这个会执行原方法,并传回返回体,我们可以加工返回体后再返回给方法调用者
      Object object = joinPoint.proceed();
      
      return object;
   }
}

我们大致了解切面类之后就创建注解。

注解有四个元注解,如下:

[email protected]     //加了这个会把注解加入到javadoc中
[email protected](RetentionPolicy.RUNTIME)    //这里指定注解的保留策略,如果不太了解就用RUNTIME
[email protected](ElementType.ANNOTATION_TYPE)   //这里指定注解的作用目标,枚举类值的含义如下
ElementType.TYPE   接口、类、枚举、注解
ElementType.FIELD  字段、枚举的常量
ElementType.METHOD   方法
ElementType.PARAMETER   方法参数
ElementType.CONSTRUCTOR 构造函数
ElementType.LOCAL_VARIABLE  局部变量
ElementType.ANNOTATION_TYPE  注解
ElementType.PACKAGE   包    
[email protected]:说明子类可以继承父类中的该注解

注解使用@interface 来定义,它和接口一样,是不能有实现方法的。

而且注解的参数以  public/private   数据类型(八大数据类型的引用类型不能用)参数名()  [default  默认值]    的形式定义;

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotaion {

    public long value() default 20L;
    String str();  //如果没有给默认值,那么使用注解时必须给值
}

然后我们在需要的地方使用注解,就可以对其进行各种AOP通知了,也可以利用注解的参数做一些操作。

举例可以看上篇Redis数组管理。

AOP是在方法上去通知的。我们若是要对方法参数进行加工呢?例如验证参数合法性、把参数转换成指定类型等操作,

例如@ReuestBody和@Valid。这时AOP不适用了。我百度了很多博客,参考了下面两篇博客总结出了方法:

https://blog.csdn.net/a18716374124/article/details/79208990

https://blog.csdn.net/potatobeancox55555/article/details/80588507

目前我只能是处理controller下的方法的参数,其他非controller类的处理我还没找到,如果找到了会更新。

首先我们先定义一个函数参数处理器HandlerMethodArgumentResolver,并将其向spring注册

@Component
public class MyArgumentResolve implements HandlerMethodArgumentResolver {

   public MyArgumentResolve() {
   }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
      //System.out.println(parameter.hasParameterAnnotation(MyAnnotaion.class));
        return parameter.hasParameterAnnotation(MyAnnotaion.class);
    }

    @SuppressWarnings("unchecked")
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //controller 中的参数使用下面的方法获得
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        Object str = request.getParameter("str");//这个str应该是你的参数名
        System.out.println(str);
        return "yeye";//返回你处理后的内容
    }

}

然后我们向spring注册,记得把Myannotaion的作业类型改成ElementType.PARAMETER,然后我还去掉了全部参数

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

   @Autowired MyArgumentResolve myArgumentResolve;


   @Override
   public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
      argumentResolvers.add(myArgumentResolve);
   }


}

这个注册类你还可以做很多功能,比如添加转换器Convert之类的。

最后一个测试controller:

@RestController
@Api(description = "测试专用接口")
@RequestMapping("/test")
public class Test {

   @GetMapping("/test")
   @ApiOperation("/c")
   @ApiImplicitParam(name="str",paramType = "query",dataType = "string")
   public ResultBean test2(@MyAnnotaion String str){//如果你是复制我上面的注解肯定报错了,因为没有指定默认值也没有给值
      System.out.println(str);
      return new ResultBean(200,res);//ResultBean是我自己写的一个返回方法体
   }

}

通过swagger我进行了请求,然后就把我输入的bb改成了yeye,测试通过!

SpringBoot 自定义注解结合AOP的简单使用