当前位置: 首页 > news >正文

Spring AOP_2

切点表达式

上篇文章中,我们一直使用切点表达式来描述切点。

切点表达式常见有两种表达方式:

  1. execution(……):根据方法的签名来匹配
  2. @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..*(..))

@annotation

 execution表达式更适用有规则的方法,如果我们要匹配多个无规则方法就不是很方便了。

比如:TestController中的t1()方法,UsrController中的u1()方法

这时我们可以借助自定义注解的方式以及另一种切点表达式@annotation来描述这一类的切点

实现步骤:

  1.  编写自定义注解。
  2. 使用@annotation表达式来描述切点。
  3. 在连接点的方法上添加自定义注解。
自定义注解@MyAspect

与创建类的方式相同:

注解代码如下:

@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:表示注解仅存在于源代码中,编译成字节码后会被丢弃。这意味着在运行时无法获取到该注解的信息,只能在编译时使用。
  • RetentionPolicy.CLASS:编译时注解。表示注解存在于源代码和字节码中,但在运行时会被丢弃。这意味着在编译时和字节码中可以通过反射获取到该注解的信息,但在实际运行时无法获取。通常用于一些框架和工具的注解。
  • RetentionPolicy.RUNTIME:运行时注解。表示注解存在于源代码、字节码和运行时中。这意味着在编译时,字节码中和实际运行时都可以通过反射获取到该注解的信息,通常用于一些需要在运行时处理的注解。
切面类 

使用@annotation切点表达式定义切点,只对@MyAspect生效。

切面类代码如下:

@Slf4j
@Aspect
@Configuration
public class MyAspectDemo {@Around("@annotation(com.example.springaop11111.MyAspect.MyAspect)")public Object MyAspectDemo(ProceedingJoinPoint joinPoint) throws Throwable {log.info("do Around Before");Object proceed = joinPoint.proceed();log.info("do Around After");return proceed;}
}

注意:这里@annotation()括号里要添加上自定义注解的在当前项目中的相对路径!!!

添加自定义注解

在TestController中的t1()和UserController中的u1方法上添加上我们的自定义注解。 

@Slf4j
@RestController
public class TestController {@MyAspect@RequestMapping("/t1")public void t1(){log.info("t1方法执行中");}/*** 测试有异常的情况*/@RequestMapping("/t2")public void t2(){log.info("t2方法执行中");int a = 10/0;}}
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@MyAspect@RequestMapping("/u1")public void u1(){log.info("u1方法执行中");}@RequestMapping("/u2")public void u2(){log.info("u2方法执行中");}
}

进行测试:

t1:

u1: 

u2: 

Spring AOP的实现方式

1、基于注解@Aspect

2、基于自定义注解(@annotation)

3、基于Spring API(通过xml配置的方式,自从SpringBoot广泛使用之后,这种方法几乎看不到了)

4、基于代理来实现(更加远古的一种实现方式,写法笨重,不建议使用)

参考:面试官:谈谈你对IOC和AOP的理解及AOP四种实现方式[通俗易懂]-腾讯云开发者社区-腾讯云 (tencent.com)

Spring AOP原理

Spring AOP是基于动态代理来实现AOP的。

代理模式

代理模式,也叫委托模式。

定义:为其他对象提供一种代理以控制对这个对象到的访问。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。

在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象间起到中介的作用。

使用代理前:

使用代理后: 

举个例子: 

代理模式的主要角色

1、Subject:业务接口类。可以是抽象类或者接口(不一定有)(房客)

2、RealSubject:业务实现类。具体业务执行,也就是被代理对象(房东)

3、Proxy:代理类,RealSubject的代理(中介)

UML类图如下: 

代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。根据代理的创建时期,代理模式分为静态代理动态代理 。

  • 静态代理:由程序员创建代理类或者特定工具自动生成源代码再对其编译,在程序运行前代理类的.class文件就已经存在了。
  • 动态代理:在程序运行时,运用反射机制动态创建而成。

还是刚才的例子:

下面我们通过代码来加深对这两种代理模式的理解~~~

静态代理

定义接口(中介和房东要做的事情): 

public interface HouseSubject {void rentHouse();
}

实现接口(房东出租房子):

public class RealHouseSubject implements HouseSubject{@Overridepublic void rentHouse() {System.out.println("我是房东, 我出租房⼦");}
}

代理(中介,帮房东出租房子):

public class HouseProxy implements HouseSubject{//将被代理对象声明为成员变量private HouseSubject houseSubject;public HouseProxy(HouseSubject houseSubject) {this.houseSubject = houseSubject;}@Overridepublic void rentHouse() {//开始代理System.out.println("我是中介, 开始代理");//代理房东出租房⼦houseSubject.rentHouse();//代理结束System.out.println("我是中介, 代理结束");}
}

 使用:

public class StaticMain {public static void main(String[] args) {HouseSubject subject = new RealHouseSubject();//创建代理类HouseProxy proxy = new HouseProxy(subject);//通过代理类访问⽬标⽅法proxy.rentHouse();}
}

运行结果: 

此时如果我们添加了新的需求:对房屋进行出售,我们就需要对上述代码进行修改:

接口: 

public interface HouseSubject {void rentHouse();void saleHouse();
}

实现接口(房东出租房子):

public class RealHouseSubject implements HouseSubject{@Overridepublic void rentHouse() {System.out.println("我是房东, 我出租房⼦");}@Overridepublic void saleHouse() {System.out.println("我是房东, 我出售房⼦");}
}

代理(中介,帮房东出租房子):

public class HouseProxy implements HouseSubject{//将被代理对象声明为成员变量private HouseSubject houseSubject;public HouseProxy(HouseSubject houseSubject) {this.houseSubject = houseSubject;}@Overridepublic void rentHouse() {//开始代理System.out.println("我是中介, 开始代理");//代理房东出租房⼦houseSubject.rentHouse();//代理结束System.out.println("我是中介, 代理结束");}@Overridepublic void saleHouse() {//开始代理System.out.println("我是中介, 开始代理");//代理房东出租房⼦houseSubject.saleHouse();//代理结束System.out.println("我是中介, 代理结束");}
}

从上面的代码可以看出:静态代理如果想实现功能增强需要修改很多的代码。但是既然代理的流程是一样的,有没有一种办法,让它们通过一个代理类来实现呢?

这就需要使用动态代理技术了。

动态代理

动态代理有两种实现形式:1、JDK动态代理        2、CGLIB动态代理

JDK动态代理

定义JDK动态代理类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocationHandler implements InvocationHandler {//⽬标对象即就是被代理对象private Object target;public JDKInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {// 代理增强内容System.out.println("我是中介, 开始代理");//通过反射调⽤被代理类的⽅法Object retVal = method.invoke(target, args);//代理增强内容System.out.println("我是中介, 代理结束");return retVal;}
}

创建一个代理对象并使用: 

public class DynamicMain {public static void main(String[] args) {HouseSubject target= new RealHouseSubject();//创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class[]{HouseSubject.class},new JDKInvocationHandler(target));proxy.rentHouse();}
}
 CGLIB动态代理

JDK动态代理有一个致命的问题:他只能代理实现接口的类。

有些场景下,我们的业务代码是直接实现的,并没有接口定义,为了解决这个问题,我们可以用GCLIB动态代理机制来解决。

添加依赖:

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

自定义MethodInterceptor(方法拦截器) :

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBInterceptor implements MethodInterceptor {//⽬标对象, 即被代理对象private Object target;public CGLIBInterceptor(Object target){this.target = target;}@Overridepublic Object intercept(Object o, Method method, Object[] objects, 
MethodProxy methodProxy) throws Throwable {// 代理增强内容System.out.println("我是中介, 开始代理");//通过反射调⽤被代理类的⽅法Object retVal = methodProxy.invoke(target, objects);//代理增强内容System.out.println("我是中介, 代理结束");return retVal;}
}

创建代理类并使用:

public class DynamicMain {public static void main(String[] args) {HouseSubject target= new RealHouseSubject();HouseSubject proxy= (HouseSubject) 
Enhancer.create(target.getClass(),new CGLIBInterceptor(target));proxy.rentHouse();}
}
http://www.lryc.cn/news/609265.html

相关文章:

  • Python 小数据池(Small Object Pool)详解
  • NX969NX972美光固态闪存NX975NX977
  • 深度学习中的三种Embedding技术详解
  • Maven - 依赖的生命周期详解
  • MySQL深度理解-MySQL锁机制
  • vllm0.8.5:思维链(Chain-of-Thought, CoT)微调模型的输出结果包括</think>,提供一种关闭思考过程的方法
  • Remix框架:高性能React全栈开发实战
  • 音视频学习(四十九):音频有损压缩
  • 数据结构-双链表
  • 网络通信与Socket套接字详解
  • Flink程序关键一步:触发环境执行
  • 13-day10生成式任务
  • 全面解析 BGE Embedding 模型:训练方式、模型系列与实战用法
  • python批量gif图片转jpg
  • C++ vector容器详解:从基础使用到高效实践
  • 【GitHub探索】Agent开发平台CozeStudio开源版本踩坑体验
  • Obsidian结合CI/CD实现自动发布
  • 【设计模式】4.装饰器模式
  • 第二节 YOLOv5参数
  • 电商系统定制开发流程:ZKmall开源商城需求分析到上线全程可控
  • Linux命令基础(上)
  • 关于Web前端安全防御之内容安全策略(CSP)
  • 第八章:进入Redis的SET的核心
  • 基于 Spring Boot + Vue 实现人脸采集功能全流程
  • spring-ai-alibaba 之 graph 槽点
  • 无人机集群协同三维路径规划,采用冠豪猪优化器(Crested Porcupine Optimizer, CPO)实现,Matlab代码
  • 基于BiLSTM+CRF实现NER
  • JavaWeb学习------SpringCloud入门
  • 最小半径覆盖问题【C++解法+二分+扫描线】
  • 研报复现|史蒂夫·路佛价值选股法则