目录
Spring AOP详解
@PointCut
切面优先级@Order
切点表达式
execution表达式
切点表达式示例
@annotation
自定义注解@MyAspect
切面类
添加自定义注解
Spring AOP详解
@PointCut
上面代码存在一个问题, 就是对于excution(* com.example.demo.controller.*.*(..))的大量重复使用, Spring提供了@PointCut注解, 把公共的切点表达式提取出来, 需要用到时引入切点表达式即可.
@Slf4j
@Component
@Aspect
public class AspectDemo {
//声明一个切点
@Pointcut("execution(* com.bite.aop.controller.*.*(..))")
public void pt(){}; //切点名称:pt()
@Before("pt()")
public void doBefore() {
//此处省略...
}
@After("pt()")
public void doAfter() {
//此处省略...
}
@Around("pt()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//此处省略...
}
@AfterReturning("pt()")
public void doAfterReturning() {
//此处省略...
}
}
当切点定义为private修饰时, 仅能在当前切面类使用, 当其他切面类也要使用当前切点定义时, 就需要把private改为public. 引用方式为: 全限定类名.方法名()
使用示例:
@Slf4j
@Aspect
@Component
public class AspectDemo2 {
//前置通知
@Before("com.example.demo.aspect.AspectDemo.pt()")
public void doBefore() {
log.info("卢本伟牛逼");
}
}
切面优先级@Order
当我们在一个项目中, 定义了多个切面类时, 并且这些切面类的多个切入点都匹配到了同一个目标方法. 当目标方法运行的时候, 这些切面类中的通知方法都会执行, 那么这些通知方法的执行顺序是怎样的呢?
还是通过程序求证:
定义多个切面类如下:
@Component
public class AspectDemo2 {
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
log.info("执⾏ AspectDemo2 -> Before ⽅法");
}
//后置通知
@After("pt()")
public void doAfter() {
log.info("执⾏ AspectDemo2 -> After ⽅法");
}
}
@Component
public class AspectDemo3 {
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
log.info("执⾏ AspectDemo3 -> Before ⽅法");
}
//后置通知
@After("pt()")
public void doAfter() {
log.info("执⾏ AspectDemo3 -> After ⽅法");
}
}
@Component
public class AspectDemo4 {
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
log.info("执⾏ AspectDemo4 -> Before ⽅法");
}
//后置通知
@After("pt()")
public void doAfter() {
log.info("执⾏ AspectDemo4 -> After ⽅法");
}
}
运行指定接口,可以得到日志:
通过上述程序的运行结果, 可以看出:
存在多个切面类时, 默认按照切面类的类名字母排序:
@Before通知: 字母排名靠前的先执行;
@After通知: 字母排名靠后的后执行.
但是哥们不想这么搞, 哥们想让它们按照哥们的想法排序, 到这里我们就可以使用@Order来对切面优先级排序.
使用方式如下:
@Aspect
@Component
@Order(2)
public class AspectDemo2 {
//代码省略...
}
@Aspect
@Component
@Order(1)
public class AspectDemo3 {
//代码省略...
}
@Aspect
@Component
@Order(3)
public class AspectDemo4 {
//代码省略...
}
执行结果如下:
通过上述程序的运行结果, 得出结论:
@Order注解标识的切面类, 执行顺序如下:
@Before通知: 数字越小先执行
@After通知: 数字越大先执行
@Order控制切面的优先级控制切面的优先级, 先执行优先级较高的切面, 在执行优先级较低的切面, 最终执行目标方法.
切点表达式
上面的代码中, 我们一直在使用切点表达式来描述切点, 下面我们来介绍一下切点表达式的语法.
切点表达式常见有两种表达方式
1.execution(......): 根据方法的签名来匹配
2.annotation(......): 根据注解来匹配.
execution表达式
execution()作为常用的表达式, 语法为:
execution(<访问修饰符> <返回类型> <包名.类名.方法(方法参数)> <异常>)
其中: 访问修饰符和异常可以省略.
切点表达式支持通配符表达:
1. * : 匹配任意字符, 只匹配任意一个元素(返回类型, 包, 类名, 方法名, 方法参数)
2. .. :匹配多个连续的任意符号, 可以通配任意层级的包, 或任意类型, 任意个数的参数
切点表达式示例
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.*(..))
@annotation
execution表达式更适用于有规则的, 如果我们要匹配出多个无规则的方法呢? 比如: 匹配TestController中的t1(), 和UserController中的u1()这两个方法.
这个时候使用execution就不是很方便了, 因此这里引入@annotation来表示这一类的切点.
实现步骤:
1.编写自定义注解
2.使用@annotation表达式来描述切点
3.在连接点的方法上添加自定义注解.
准备测试代码:
@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {
@MyAspect
@RequestMapping("/t1")
public String t1() {
log.info("执行t1方法...");
return "t1";
}
@RequestMapping("/t2")
public boolean t2() {
log.info("执行t2方法");
return true;
}
}
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
@RequestMapping("/u1")
public String u1() {
log.info("执行u1方法...");
return "u1";
}
@MyAspect
@RequestMapping("/u2")
public boolean u2() {
log.info("执行u2方法");
return true;
}
}
自定义注解@MyAspect
创建一个注解类:
定义如下代码:
//注解类型
@Target({ElementType.METHOD})
//注解生命周期
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}
这里只做简单说明, 不必深究:
1.@Target标识了Annotation所修饰对象的范围, 即该注解用于什么地方(上文就是用于方法)
2.@Retention指Annotation被保留的时间长短, 标明注解的生命周期.
切面类
使用@annotation切点表达式定义切点, 只对@MyAspect生效.
切面类代码如下:
@Slf4j
@Component
@Aspect
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()这两个方法上添加自定义注解 @MyAspect.
@MyAspect
@RequestMapping("/t1")
public String t1() {
log.info("执行t1方法...");
return "t1";
}
@MyAspect
@RequestMapping("/u2")
public boolean u2() {
log.info("执行u2方法");
return true;
}
顺利执行.
Spring AOP实现方式(常见面试题)
1.基于注解@Aspect
2.基于自定义注解
3.基于Spring API(现在很少见)
4.基于代理实现(笨重, 不建议使用).