前言

于其说这是一篇文章,不如说这是一篇笔记,主要介绍了@annotation@argsargs的作用以及一些坑点。这里主要记录一些项目用到的,没有成一套体系,网上其他文章对Spring AOP的切点修饰符可能有比较全的描述。如果以后有遇到其他场景,会在这里补充。

背景

主要是实习公司需要开发一个注解来实现某些特定功能,项目是基于Spring Boot搭建的,因此很容易想到Spring的AOP技术来实现。通过查阅资料和官方文档,发现@annotation注解可以满足这个需求。同时我也研究了@args与·args两个修饰符,讨论一下这两个修饰符到底在干什么。

Spring AOP原理

Spring的AOP使用动态代理模式。在代理模式中有两个对象:代理对象与被代理对象(目标对象),代理模式中是说使用代理对象来操作被代理对象,而动态代理,说明这个代理对象可以根据需要生成,这就是“动”的含义。

关键就在于这个代理对象,我们既然可以在代理对象中调用被代理对象的方法,那么我们就可以在方法执行前、后等做一些操作,这种操作叫做类增强。我们定义方法,写各种各样的表达式,目标就是要匹配正确的方法做类增强。

所以,我们就遇到了第一个坑点:为什么AOP的切点定义不起作用?
大方向有两个原因:

  • 切点定义有问题
  • Spring为你生成的根本不是代理对象
    第二点的排查比较容易,找到你期望增强的方法,然后运行debug模式,观察定义目标方法的对象是不是代理对象:
    在这里插入图片描述
    例如,TestController就不是代理对象,xServiceImpl就是代理对象,而且是通过GCLIB进行动态代理的
    相关阅读:spring 依赖注入时,什么时候会创建代理类

切点修饰符

@annotation

定义

官方定义:

Limits matching to join points where the subject of the join point (the method being run in Spring AOP) has the given annotation.

也就是说,如果把你定义的注解修饰在某一个方法上,那么就会命中,执行定义的类增强逻辑。

例子

定义一个注解@append,用于在字符串前后增加一些符号。例如接收到的字符串为hello,输出*** hello ***

  • Append注解定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Append {
    public String word() default "***";
}

  • 注解处理器定义
/**
 * Append的注解处理器
 */
@Aspect
@Component
public class AppendProcessor {


    @Around("@annotation(appendAnnotation)")
    public String process(ProceedingJoinPoint joinPoint, Append appendAnnotation) throws Throwable{
        String res = appendAnnotation.word() + " " + joinPoint.proceed() + " " + appendAnnotation.word();
        return res;
    }
}

@annotation(…)传入的是注解对象的引用,可以是类型引用

  • service方法定义

@Service
public class XServiceImpl {

    @Append
    public String foo(String val) {
        return val;
    }
}
  • controller定义
@Controller
@RestController
public class TestController {

    @Autowired
    private XServiceImpl xService;

    @GetMapping("/hello")
    public String hello() {
        return xService.foo("Hello Word!");
    }

}

运行结果(关注控制台):

args族

对于args约束,我的理解是用于限制匹配方法的参数类型。这个参数有两种,一种是针对普通方法的,另外一种是针对注解的

args

官方定义

Limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types.

这是针对普通方法的,从定义中可以知道,args用来限制匹配方法的参数类型。

使用args,可以传递匹配方法的调用参数

例子

@annotation那一节中的例子一样,我们修改一下注解处理器

  • 注解处理器定义
/**
 * Append的注解处理器
 */
@Aspect
@Component
public class AppendProcessor {
    @Around("@annotation(appendAnnotation) && args(val)")
    public String process(ProceedingJoinPoint joinPoint, Append appendAnnotation, String val) throws Throwable{
        System.out.println(val);
        String res = appendAnnotation.word() + " " + joinPoint.proceed() + " " + appendAnnotation.word();
        return res;
    }
}

运行结果:

同时观察控制台,会发现val的值是调用匹配方法所传递的值,即Hello Word!

如果我把注解处理器中val的类型改成Integer会发生什么呢?

/**
 * Append的注解处理器
 */
@Aspect
@Component
public class AppendProcessor {

    @Around("@annotation(appendAnnotation) && args(val)")
    public String process(ProceedingJoinPoint joinPoint, Append appendAnnotation, Integer val) throws Throwable{
        System.out.println(val);
        String res = appendAnnotation.word() + " " + joinPoint.proceed() + " " + appendAnnotation.word();
        return res;
    }
}

运行结果:

可以发现,方法匹配失败了,因为打了@Append注解方法的第一个参数是String,而不是Integer

@args

官方定义

Limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given types.

@标志的说明跟注解有关,用来描述方法参数的封装类是否有某个注解。这某个注解的作用域要有

例子

@annotation那一节中的例子的基础上,定义一个新的注解@Demo

  • @Demo注解定义
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Demo {
}
  • 新定义一个YService,与XService做区分,YService上有@Demo注解
@Service
@Demo
public class YServiceImpl {

    public void foo() {}
}
  • 修改Xservice中的foo方法:
@Service
public class XServiceImpl {
    @Append
    public String foo(YServiceImpl yService, String val) {
        System.out.println("方法开始执行");
        return val;
    }
}
  • 在注解处理器中新增一个方法
    @Around("@annotation(appendAnnotation) && @args(cn.acmsmu.aop.Demo,..)")
    public String process2(ProceedingJoinPoint joinPoint, Append appendAnnotation) throws Throwable{
        String res = appendAnnotation.word() + " " + joinPoint.proceed() + " " + appendAnnotation.word();
        System.out.println(res);
        return res;
    }

这里的@args表示匹配方法可以有多个参数,第一个参数的类必须被@Demo注解修饰

运行结果

共同点与区别

  • 共同点:都是对目标方法的参数类型进行限制
  • 区别:
    • args: 单纯针对待增强方法的参数类型,不会关系参数的类
    • @args: 关注待增强方法参数的类是否被某个注解修饰
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐