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";
}