
上面的代码中,我们一直在使用切点表达式来描述切点。下面我们来介绍一下切点表达式的语法。切点表达式常见有两种表达方式
execution(......):根据方法的签名来匹配@annotation(......):根据注解匹配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..*(..))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";
}
}创建一个注解类
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,@Slf4jRetentionPolicy.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 广泛使用之后,这种方法几乎看不到了)