于其说这是一篇文章,不如说这是一篇笔记,主要介绍了@annotation
、@args
和args
的作用以及一些坑点。这里主要记录一些项目用到的,没有成一套体系,网上其他文章对Spring AOP的切点修饰符可能有比较全的描述。如果以后有遇到其他场景,会在这里补充。
主要是实习公司需要开发一个注解来实现某些特定功能,项目是基于Spring Boot搭建的,因此很容易想到Spring的AOP技术来实现。通过查阅资料和官方文档,发现@annotation
注解可以满足这个需求。同时我也研究了@args
与·args
两个修饰符,讨论一下这两个修饰符到底在干什么。
Spring的AOP使用动态代理模式。在代理模式中有两个对象:代理对象与被代理对象(目标对象),代理模式中是说使用代理对象来操作被代理对象,而动态代理,说明这个代理对象可以根据需要生成,这就是“动”的含义。
关键就在于这个代理对象,我们既然可以在代理对象中调用被代理对象的方法,那么我们就可以在方法执行前、后等做一些操作,这种操作叫做类增强。我们定义方法,写各种各样的表达式,目标就是要匹配正确的方法做类增强。
所以,我们就遇到了第一个坑点:为什么AOP的切点定义不起作用?
大方向有两个原因:
官方定义:
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 ***
。
@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
public class XServiceImpl {
@Append
public String foo(String val) {
return val;
}
}
@Controller
@RestController
public class TestController {
@Autowired
private XServiceImpl xService;
@GetMapping("/hello")
public String hello() {
return xService.foo("Hello Word!");
}
}
对于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
官方定义
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
: 关注待增强方法参数的类是否被某个注解修饰