AOP的概述
AOP是面向切面编程。切面就是指某一类特定的问题,所以AOP也可以理解为面向特定方法编程。AOP是一种思想,拦截器,统一数据返回和统一异常处理是AOP思想的一种实现。简单来说:AOP是一种思想,对某一类事务的集中处理。
spring对AOP进行了实现,并且提供了一些API,这就是spring AOP.
spring AOP的简单使用
预先准备:
@RestController
public class TestController {
@RequestMapping("/t1")
public String t1(){
String sum = "";
for (int i = 1; i <= 10000; i++) {
sum += 'a';
}
return "t1";
}
@RequestMapping("/t2")
public String t2(){
StringBuilder sum = new StringBuilder();
for (int i = 1; i <= 10000; i++) {
sum.append('a');
}
return "t2";
}
}
- 引入springAOP依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 编写AOP程序
@Slf4j
@Aspect//表示是一个切面类
@Component
public class TimeAspect {
@Around("execution(* com.example.aopdemo.controller.*.*(..))")//作用域
public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();//方法执行前的逻辑
Object result = joinPoint.proceed();//执行目标方法
long end = System.currentTimeMillis();//方法执行后的逻辑
log.info(joinPoint + "消耗时间:" + (end - start) + "ms");
return result;
}
}
我们通过AOP⼊⻔程序完成了业务接⼝执⾏耗时的统计.
@Around:环绕通知,在⽬标⽅法的前后都会被执⾏.后⾯的表达式表⽰对哪些⽅法进⾏增强
优点:
- 代码⽆侵⼊:不修改原始的业务⽅法,就可以对原始的业务⽅法进⾏了功能的增强或者是功能的改变
- 减少了重复代码
- 提⾼开发效率
- 维护⽅便
spring AOP 详解
spring AOP 的核心概念(了解)
- 切点:一组规则,通过表达式来描述
- 连接点: 切面要作用的方法,目标方法切点描述的方法.
- 通知:具体的逻辑,要做什么事情
- 切面:切点+通知
com.example.aopdemo.controller目录下的方法就是连接点。
切点和连接点的关系:连接点是满⾜切点表达式的元素.切点可以看做是保存了众多连接点的⼀个集合.
spring AOP通知类型
- @Around:环绕通知,此注解标注的通知方法在目标方法前,后都被执行 --> 使用最多
- @Before:前置通知,此注解标注的通知方法在目标方法前被执行
- @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
- @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
- @AfterThrowing:异常后通知,此注解标注的通知方法发⽣异常后执行
@Slf4j
@Aspect
@Component
public class AspectDemo {
//定义切点
@Pointcut("execution(* com.example.aopdemo.controller.*.*(..))")
public void pt(){
}
@Before("pt()")//后置通知
public void doBefore(){
log.info("执行AspectDemo doBefore……");
}
@After("pt()")//前置通知
public void doAfter(){
log.info("执行AspectDemo doAfter……");
}
@AfterReturning("pt()")//返回后通知
public void doAfterReturning(){
log.info("执行AspectDemo doAfterReturning……");
}
@AfterThrowing("pt()")//异常后通知
public void doAfterThrowing(){
log.info("执行AspectDemo doAfterThrowing……");
}
@SneakyThrows
@Around("pt()")//环绕通知
public Object doAround(ProceedingJoinPoint joinPoint){
log.info("执行AspectDemo doAround前……");
Object result = joinPoint.proceed();
log.info("执行AspectDemo doAround后……");
return result;
}
}
正常执行结果:
先执行around,再执行before。先执行after,再执行Around。
异常执行结果:
当发生异常时,不执行AfterReturning,也不执行Around的方法后的逻辑
@PointCut,定义切点
如果其他类需要使用,需要把切点声明为public.使用时,类的全限定名称+切点名称 ==> 包+类名
@Slf4j
@Aspect
@Component
public class AspectDemo1 {
@After("com.example.aopdemo.aspect.AspectDemo.pt()")//前置通知
public void doAfter(){
log.info("执行AspectDemo1 doAfter……");
}
}
切面的优先级
定义三个切面类:
@Slf4j
@Aspect
@Component
public class AspectDemo1 {
@Before("com.example.aopdemo.aspect.AspectDemo.pt()")//后置通知
public void doBefore(){
log.info("执行AspectDemo1 doBefore……");
}
@After("com.example.aopdemo.aspect.AspectDemo.pt()")//前置通知
public void doAfter(){
log.info("执行AspectDemo1 doAfter……");
}
}
@Slf4j
@Aspect
@Component
public class AspectDemo2 {
@Before("com.example.aopdemo.aspect.AspectDemo.pt()")//后置通知
public void doBefore(){
log.info("执行AspectDemo2 doBefore……");
}
@After("com.example.aopdemo.aspect.AspectDemo.pt()")//前置通知
public void doAfter(){
log.info("执行AspectDemo2 doAfter……");
}
}
@Slf4j
@Aspect
@Component
public class AspectDemo3 {
@Before("com.example.aopdemo.aspect.AspectDemo.pt()")//后置通知
public void doBefore(){
log.info("执行AspectDemo3 doBefore……");
}
@After("com.example.aopdemo.aspect.AspectDemo.pt()")//前置通知
public void doAfter(){
log.info("执行AspectDemo3 doAfter……");
}
}
存在多个切面类时,默认按照切面类的类名字母排序:
- @Before通知:字母排名靠前的先执行
- @After通知:字母排名靠前的后执行
优先级高:先执行before,后执行After
Spring给我们提供了一个新的注解,来控制这些切面通知的执行顺序:@Order。使用@Order时,数字越小,优先级越高
@Order(2)
public class AspectDemo1 {
}
@Order(3)
public class AspectDemo2 {
}
@Order(1)
public class AspectDemo3 {
}
执行结果:
@Order控制切面的优先级,先执行优先级较高的切面,再执行优先级较低的切面,最终执行目标方法.
切点表达式
切点表达式常见有两种表达方式
- execution(…):根据方法的签名来匹配
- @annotation(…):根据注解匹配
execution表达式上面已经用过.
主要介绍切点表达式支持通配符表达:
-
- :匹配任意字符,只匹配一个元素(返回类型,包,类名,方法或者方法去参数
- 包名使用 * 表示任意包(一层包使用一个*)
- 类名使用 * 表示任意类
- 返回值使用 *表示任意返回值类型
- 方法名使用 * 表示任意方法
- 参数使用 * 表示一个任意类型的参数
-
… :匹配多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数
- 使用 … 配置包名,标识此包以及此包下的所有子包
- 可以使用 … 配置参数,任意个任意类型的参数
execution表达式更适用有规则的,如果我们要匹配多个无规则的方法,比如:TestController中的t1和UserController中的u1()这两个方法。这个时候我们使用execution这种切点表达式来描述就不是是很方便了。我们可以借助自定义注解的方式以及另一种切点表达式@annotation来描述这一类的切点。
实现步骤:
- 编写自定义注解
- 使用@annotation表达式来描述切点
- 在连接点的方法上添加自定义注解
准备测试代码:
@RestController
public class UserController {
@RequestMapping("/hello")
public String hello(){
return "hello";
}
@RequestMapping("/h1")
public String h1(){
return "h1";
}
@RequestMapping("/h2")
public String h2(){
return "h2";
}
}
编写自定义注解:
@Target({ElementType.METHOD})//标识了 Annotation所修饰的对象范围,即该注解可以用在什么地方(方法上)
@Retention(RetentionPolicy.RUNTIME)//指Annotation被保留的时间长短,标明注解的生命周期(运行时注解)
public @interface MyAspect {
}
使用@annotation表达式来描述切点
@Slf4j
@Component
@Aspect
public class MyAspectDemo {
@Before("@annotation(com.example.aopdemo.aspect.MyAspect)")
public void doBefore(){
log.info("执行MyAspectDemo before...");
}
@After("@annotation(com.example.aopdemo.aspect.MyAspect)")
public void doAfter1(){
log.info("执行MyAspectDemo doAfter...");
}
//所有使用RequestMapping注解都会触发
@After("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void doAfter(){
log.info("执行RequestMapping doAfter...");
}
}
在连接点的方法上添加自定义注解:
@MyAspect
@RequestMapping("/h1")
public String h1(){
return "h1";
}