上面的代码中,我们一直在使用切点表达式来描述切点。下面我们来介绍一下切点表达式的语法。切点表达式常见有两种表达方式
execution(......)
:根据方法的签名来匹配@annotation(......)
:根据注解匹配
execution 表达式
execution()
是最常用的切点表达式,用来匹配方法,语法为:
execution(<访问修饰符> <返回类型> <包名.类名.方法(方法参数)> <异常>)
其中,访问修饰符和异常可以省略
切点表达式支持通配符表达:
*
:匹配任意字符,只匹配一个元素(返回类型,包,类名,方法或者方法参数)- 包名使用
*
表示任意包(一层包使用一个*
) - 类名使用
*
表示任意类 - 返回值使用
*
表示任意返回值类型 - 方法名使用
*
表示任意方法 - 参数使用
*
表示一个任意类型的参数
- 包名使用
..
:匹配多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数- 使用
..
配置包名,标识此包以及此包下的所有子包 - 可以是用
..
配置参数,任意个任意类型的参数
- 使用
切点表达式示例
TestController
下的 public
修饰,返回类型为 String
方法名为 t1
,无参方法
execution(public String com.example.demo.controller.TestController.t1())
省略访问修饰符
execution(String com.example.demo.controller.TestController.t1())
匹配所有返回类型
execution(* com.example.demo.controller.TestController.t1())
匹配 TestController
下的所有无参方法
execution(* com.example.demo.controller.TestController.*())
匹配 TestController
下的所有方法
execution(* com.example.demo.controller.TestController.*(..))
匹配 controller
包下所有的类的所有方法
execution(* com.example.demo.controller.*.*(..))
匹配所有包下的 TestController
execution(* com..TestController.*(..))
匹配 com.example.demo
包下,子孙包下的所有类的所有方法
execution(* com.example.demo..*(..))
@annotation
execution
表达式更适用有规则的,如果我们要匹配多个无规则的方法呢?
- 比如
TestController
中的t1()
和UserController
中的u1()
这两个方法 - 这个时候我们使用
execution
这种切点表达式来描述就不是很方便了
我们可以借助自定义注解的方式以及另一种切点表达式 @annotation
来描述这一类的切点。实现步骤:
- 编写自定义注解
- 使用
@annotation
表达式来描述切点 - 在连接点的方法上添加自定义注解
准备测试代码:
@RequestMapping("/test")
@RestController
public class TestController {
@RequestMapping("/t1")
public String t1() {
return "t1";
}
@RequestMapping("/t2")
public boolean t2() {
return true;
}
}
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/u1")
public String u1() {
return "t1";
}
@RequestMapping("/u2")
public String u2() {
return "u2";
}
}
@MyAspect(自定义注解 )
创建一个注解类
- 和创建
class
文件一样的流程,选择Annotation
就可以了
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}
代码简单说明,了解即可
-
@Target
标识了Annotation
所修饰的对象范围,即该注解可以用在什么地方。常用取值:ElementType.TYPE
:用于描述类、接口(包括注解类型)或enum
声明ElementType.METHOD
:描述方法ElementType.PARAMETER
:描述参数ElementType.TYPE_USE
:可以标注任意类型
-
@Retention
指Annotation
被保留的时间长短,标明注解的声明周期RetentionPolicy.SOURCE
:表示注解仅存在于源代码中,编译成字节码后会被丢弃。这意味着在运行时无法获取到该注解的信息,只能在编译时使用。比如@SuppressWarnings
以及lombok
提供的注解@Data
,@Slf4j
RetentionPolicy.CLASS
:编译时注解。表示注解存在于源代码和字节码中,但在运行时会被丢弃。这意味着在编译时和字节码中可以通过反射获取到该注解的信息,但在实际运行时无法获取,通常用于一些框架和工具的注解RetentionPolicy.RUNTIME
:运行时注解。表示注解存在于源代码,字节码和运行时中。这意味着在编译时,字节码中和实际运行时都可以通过反射获取到该注解的信息。通常用于一些需要再运行时处理的注解,如Spring
的@Controller
,@ResponseBody
切面类
使用 @annotation
切点表达式定义切点,只对 @MyAspect
生效
切面代码如下:
@Slf4j
@Aspect
@Component
public class MyAspectDemo {
// 前置通知
@Before("@annotation(com.example.demo.aspect.MyAspect)")
public void before() {
log.info("MyAspect -> before ...");
}
// 后置通知
@After("@annotation(com.example.demo.aspect.MyAspect)")
public void after() {
log.info("MyAspect -> after ...");
}
}
添加自定义注解
在 TestController
中的 t1()
和 UserController
中的 u1()
这两个方法上添加自定义注解 @Aspect
,其他方法不添加
@MyAspect
@RequestMapping("/t1")
public String t1() {
return "t1";
}
@MyAspect
@RequestMapping("/u1")
public String u1() {
return "u1";
}
程序运行,测试接口: http://127.0.0.1:8080/test/t1
观察日志:
可以看到,切面通知被执行了
继续测试:
http://127.0.0.1:8080/test/t2 ,切⾯通知未执⾏
http://127.0.0.1:8080/user/u1 , 切⾯通知执⾏
Spring AOP
的实现方式 #高频面试
- 基于注解
@Aspect
(参考上面)- 基于自定义注解(参考上面自定义注解
@annotation
部分)- 基于
Spring API
(通过xml
配置的方式,自从SpringBoot
广泛使用之后,这种方法几乎看不到了)- 基于代理来实现(更遥远了,不建议使用)