1.Spring AOP 术语介绍
Spring中的AOP(面向切面编程)是一种编程范式,它允许开发人员将应用程序的关注点分离出来,Spring的AOP提供了一种方便的方式来实现关注点的分离,使得开发人员可以更加专注于业务逻辑而不必过多地关注横切关注点。
2.SpringAop快速入门
- 添加依赖: spring-aspect , spring-context
xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.11</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.11</version>
</dependency>
- 新增 接口-ISale 、目标类-WuDaLang
java
public interface ISale {
//实现买烧饼等方法
void saleShaoBing(String str , Integer num);
String saleJianBing();
void saleYueBing();
void saleManTou();
}
java
//目标类 - Target
@Component
public class WuDaLang implements ISale {
public void saleShaoBing(String str , Integer num) {
System.out.println("卖烧饼...");
//throw new NullPointerException("故意抛出一个空指针异常...");
}
public String saleJianBing() {
System.out.println("卖煎饼...");
return "煎饼真好吃...";
}
public void saleYueBing() {
System.out.println("卖月饼...");
throw new RuntimeException("震惊,月饼中惊现半条虫,重达半斤");
}
public void saleManTou() {
System.out.println("卖馒头...");
}
}
- 新增切面类 WuDaAspect
- @Component
- @Aspect
- song() -> @Before("execution(* ...*Bing(..))")
java
//1.演示AOP快速入门和增强的分类
@Aspect
@Component
public class WuDaAspect {
// * *..*.*Bing(..)
// 第一个* 表示访问修饰符和返回值类型不限制
// *.. 表示包名和包的层级不限制
// * 表示类名不限制
// *Bing 表示方法名以Bing结尾
// (..) 表示参数不限制
//@After("execution(* *..*.*Bing(..))")
@Before("execution(* *..*.*Bing(..))")
public void song(){
System.out.println("送大麦茶...");
}
@After("execution(* *..*.*Bing(..))")
public void song2(){
System.out.println("送玫瑰花茶...");
}
@AfterReturning("execution(* *..*.*Bing(..))")
public void song3(){
System.out.println("送茉莉花茶...");
}
@AfterThrowing("execution(* *..*.*Bing(..))")
public void song4(){
System.out.println("送乌龙茶...");
}
/*
@Around("execution(* *..*.*Bing(..))")
public void song5(ProceedingJoinPoint proceedingJoinPoint){
}
*/
}
- 新增配置类 MyConfiguration
- @Configuration
- @ComponentScan
- @EnableAspectJAutoProxy
java
@Configuration
@ComponentScan
//开启基于注解的AOP
@EnableAspectJAutoProxy(proxyTargetClass = true) //开启基于AspectJ的自动代理 // Enable:使...可用 AutoProxy:自动代理
public class MyConfiguration {
}
- 测试
java
//1.演示Spring的AOP快速入门
@Test
public void test01(){
iSale.saleShaoBing("22",1);
}
实现在原有卖烧饼的基础上,增强了送大麦茶等功能
3. 增强的分类:
- 前置增强 在被代理的目标方法前执行
java
@Before("execution(* *..*.*Bing(..))")
public void song(){
System.out.println("送大麦茶...");
}
- 后置增强 在被代理的目标方法成功结束后执行(寿终正寝)
java
@After("execution(* *..*.*Bing(..))")
public void song2(){
System.out.println("送玫瑰花茶...");
}
- 后置返回增强 在被代理的目标方法最终结束后执行(盖棺定论)
java
@AfterReturning("execution(* *..*.*Bing(..))")
public void song3(){
System.out.println("送茉莉花茶...");
}
- 后置抛异常时增强 在被代理的目标方法异常结束后执行(死于非命)
java
@AfterThrowing("execution(* *..*.*Bing(..))")
public void song4(){
System.out.println("送乌龙茶...");
}
- 环绕增强 使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
java
@Around("execution(* *..*.*Bing(..))")
public void song(ProceedingJoinPoint joinPoint){
try {
System.out.println("此处相当于@Before");
Object obj = joinPoint.proceed(joinPoint.getArgs());
System.out.println("此处相当于@AfterReturning");
} catch (Throwable e) {
System.out.println("此处相当于@AfterThrowing");
e.printStackTrace();
}finally {
System.out.println("此处相当于@After");
}
}
4. 获取连接点信息,以及在增强方法中获取目标方法相关信息
1.建立 WuDaAspect2 类
java
//2.演示在增强方法中获取连接点信息、返回值信息、异常信息
@Aspect
@Component
public class WuDaAspect2 {
@Before("execution(* *..*.*Bing(..))")
public void song(JoinPoint joinPoint){
System.out.println("送大麦茶...");
//获取方法签名
Signature signature = joinPoint.getSignature();
// 获取目标对象
Object target = joinPoint.getTarget();
// 获取目标方法的实参
Object[] args = joinPoint.getArgs();
System.out.println(Arrays.toString(args));
//从签名中获取访问修饰符
System.out.println(signature.getModifiers());
}
@AfterReturning(pointcut = "execution(* *..*.*Bing(..))",returning = "returnStr")
public void song(String returnStr){
System.out.println("送大麦茶...");
System.out.println("目标方法的返回值是:"+returnStr);
}
@AfterThrowing(pointcut = "execution(* *..*.*Bing(..))",throwing = "throwable")
public void song(Throwable throwable){
System.out.println("送大麦茶...");
System.out.println(throwable.getMessage());
}
}
2.测试类测试
java
//2.演示AOP的增强的分类
//执行结果顺序:before , afterReturning , after
//或者 before , afterThrowing , after
//以上执行顺序是从spring5.3开始的
//那么spring5.3版本之前执行的顺序是:before ,after, afterReturning
@Test
public void test02(){
iSale.saleShaoBing("22",11);
}
5. 切点表达式的语法
public void com.bottom.aop.WuDaLang.saleShaoBing(String,Integer)
切点表达式由6个部分组成:访问修饰符、返回值类型、包名部分、类名部分、方法名部分、参数签名部分
*表示通配 , 可以表示以上的前五个部分
*.. 表示包名部分,表示包名不限制以及层数不限制
在参数部分,(..) 表示 参数不限制(参数的类型和个数都不限制)
切点表达式也支持逻辑运算符。 例如: @Before("execution(...) || execution(...)")
示例:
java
① * com.bottom.aop.WuDaLang.saleShaoBing(..)
此处的*表示访问修饰符和返回值类型不限制
② public * com.bottom.WuDaLang.saleShaoBing(..)
此处的*表示返回值类型不限制
③ * void com.bottom.aop.WuDaLang.saleShaoBing(..)
这种是错误写法
④ * com.*..WuDalang.*(..)
包名第一层要求是com、类名要求是WuDaLang,其他都不限制
⑤ * *..WuDaLang.*(..)
只要求类名是WuDaLang
⑥ * *..*.sale*(..)
只要求方法名以sale开头
⑦ * *..*.*(java.lang.String,..)
只要求方法的第一个参数必须是String
⑧ * *..*.*(String,Integer) 和 * *..*.*(Integer,String) 这两个是不一样
* *..*.*(Integer) 和 * *..*.*(int) 这两个也是不一样的!
6. 切点表达式重用
定义一个切点方法,其他切点引用该方法
java
//4.演示重用切点表达式
@Aspect
@Component
public class WuDaAspect4 {
@Pointcut("execution(* *..*.*Bing(..))")
public void mypc(){}
@Before("mypc()")
public void song(){
System.out.println("送大麦茶...");
}
@After("mypc()")
public void song2(){
System.out.println("送玫瑰花茶...");
}
@AfterReturning("mypc()")
public void song3(){
System.out.println("送茉莉花茶...");
}
@AfterThrowing("mypc()")
public void song4(){
System.out.println("送乌龙茶...");
}
}
java
//5.演示重用切点表达式
@Aspect
@Component
public class WuDaAspect5 {
@Before("com.bottom.aop.MyPointCut.mypc()")
public void song(){
System.out.println("送大麦茶...");
}
@After("com.bottom.aop.MyPointCut.mypc()")
public void song2(){
System.out.println("送玫瑰花茶...");
}
@AfterReturning("com.bottom.aop.MyPointCut.mypc()")
public void song3(){
System.out.println("送茉莉花茶...");
}
@AfterThrowing("com.bottom.aop.MyPointCut.mypc()")
public void song4(){
System.out.println("送乌龙茶...");
}
}
7. @Order 控制切面的优先级
- 默认值是整型的最大值,表示最低的优先级
- 数字越小,优先级越高
8. Spring使用动态代理
底层使用的是JDK的动态代理或者CGLIB动态代理。 也就是说,spring能够根据具体的情况切换代理技术。
- 有接口的情况下,当前spring6优先使用jdk动态代理技术
- 没有接口的情况下,只能使用CGLIB代理
- 没有接口,并且目标类被final修饰,那么代理失败
- 在有接口的情况下,我们可以使用 proxyTargetClass=true , 从而告诉spring使用CGLIB做代理
- JDK动态代理 VS CGLIB动态代理 ① 早期时,性能上有差异:JDK创建代理对象的性能较高;调用代理方法的性能较差。CGLIB恰好相反。 因此,对于不频繁创建代理对象,频繁调用代理方法的场景,优先考虑CGLIB。反之,优先考虑JDK。 ② 目前,从JDK8开始,JDK动态代理的性能有了极大提高,CGLIB在性能方面的优势不明显。 目前spring优先考虑使用JDK作为底层代理技术 ③ JDK产生的代理对象和目标对象是兄弟关系。CGLIB产生的代理对象和目标对象是父子关系。