Spring——AOP
Spring中的AOP
AOP 是 Aspect Oriented Programming(面向切面编程)的首字母缩写,是一种编程范式,它的目的是通过分离横切关注点(cross-cutting concerns)来提升代码的模块化程度。
AOP中提到的关注点,其实就是一段特定的功能,有些关注点出现在多个模块中,就称为横切关注点。
AOP解决了两个问题:
- 代码混乱:核心的业务逻辑代码还必须兼顾其他功能,这就导致不同功能的代码交织在一起,可读性很差
- 代码分散:同一个功能的代码分散在多个模块中,不易维护。
AOP中几个重要概念
概念 | 说明 |
---|---|
切面(aspect) | 按关注点进行模块分解时,横切关注点就表示为一个切面 |
连接点(join point) | 程序执行的某一刻,在这个点上可以添加额外的动作 |
通知(advice) | 切面在特定连接点上执行的动作 |
切入点(pointcut) | 切入点是用来描述连接点的,它决定了当前代码与连接点是否匹配 |
aop的具体流程:通过切入点来匹配程序中的特定连接点,在这些连接点上执行通知,这种通知可以是在连接点前后执行,也可以是将连接点包围起来
Spring AOP的实现原理
在Spring中,AOP背后的实现原理是基于动态代理技术(Dynamic Proxy),代理模式是GoF提出的23种经典设计模式之一,我们可以对某个对象提供一个代理,控制对该对象的访问,代理可以在两个有调用关系的对象之间起到中介作用——代理封装了目标对象,调用者调用了代理的方法,代理再去调用实际的目标对象
动态代理就是在运行时动态地为对象创建代理的技术,在Spring中,由AOP框架创建、用来实现切面的对象被称为AOP代理(AOP Proxy),一般采用JDK动态代理或者是CGLIB代理;二者区别如下:
必须实现接口 | 支持拦截public方法 | 支持拦截protected方法 | 拦截默认作用域方法 | |
---|---|---|---|---|
JDK动态代理 | 是 | 是 | 否 | 否 |
CGLIB代理 | 否 | 是 | 是 | 是 |
Spring容器在为Bean注入依赖时,会自动将被依赖Bean的AOP代理注入进来,这就让我们感觉是在使用原始的Bean,实则不然。
被切面拦截的对象称为目标对象(Target Object)或通知对象(Advised Object),因为Spring用了动态代理,所以目标对象就是要被代理的对象。
以JDK代理为例,假设我们想在say()方法前后增加两条日志,可以采用下面的代码
点击查看代码
/** * TODO 要被动态代理的Hello接口及其实现片段 * * @author ss_419 * @version 1.0 * @date 2023/3/12 10:45 */ interface Hello { void say() throws NoSuchMethodException; } public class SpringHello implements Hello { @Override public void say() throws NoSuchMethodException { System.out.println(this.getClass().getName() + ": " + this.getClass().getMethod("say") + "Hello SpringAOP!"); } }
点击查看代码
/** * TODO 在Hello.say()前后打印日志的InvocationHandler * * @author ss_419 * @version 1.0 * @date 2023/3/12 10:47 */ public class LogHandler implements InvocationHandler { private Hello source; public LogHandler(Hello source) { this.source = source; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Ready to say something"); try{ return method.invoke(source,args); }catch (Exception e){ e.printStackTrace(); }finally { System.out.println("Already say something"); } return method.invoke(source,args); } }
点击查看代码
/** * TODO 使用Proxy.newProxyInstance()获取代理的目标对象 * * @author ss_419 * @version 1.0 * @date 2023/3/12 10:50 */ public class AopApplication { public static void main(String[] args) { SpringHello original = new SpringHello(); Hello target =(Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(),original.getClass().getInterfaces(),new LogHandler(original)); try { target.say(); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } }
Spring AOP的实现方式:
- JDK动态代理:AopProxyFactory会创建JdkDynamicAopProxy
- CGLIB代理:创建ObjenesisCglibApoProxy
基于@AspectJ的配置
Spring Framework同时支持@AspectJ注解和XML Schema两种方式来使用AOP。
基于注解的方式
首先,需要引入org.springframework:spring-aspects依赖,以便使用AspectJ相关的注解和功能。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.3</version> </dependency>
要开启@AspectJ支持,可以在Java配置类上增加@EnableAspectJAutoProxy注解
/** * TODO 基于注解的方式 * * @author ss_419 * @version 1.0 * @date 2023/3/12 12:10 */ @Configuration @EnableAspectJAutoProxy public class Config { }
@EnableAspectJAutoProxy有两个属性,两者默认值都是false
- proxyTargetClass用于选择是否开启基于类的代理(是否使用CGLIB来做代理)
- exposeProxy用于选择是否将代理对象暴露到AopContext中
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/beans/spring-aop.xsd"> <aop:aspectj-autoproxy/> </beans>
注解方式的切入点声明由两部分组成——切入点表达式和切入点方法签名。
前者用来描述要匹配的连接点,后者可以用来引入切入点,方便切入点的复用。
@Pointcut注解中使用的就是AspectJ5的表达式,其中一些常用的PCD(pointcut designator,切入点标识符)
PCD | 说明 |
---|---|
execution | 最常用的一个PCD,用来匹配特定方法的执行 |
within | 匹配特定范围内的类型,可以用通配符来匹配某个Java包内的所有类 |
this | Spring AOP代理对象这个Bean本身要匹配某个给定的类型 |
target | 目标对象要匹配某个给定的类型,比this更常用一些 |
args | 传入的方法参数要匹配给定的类型,它也可以用于绑定请求参数 |
bean | Spring AOP 特有的一个PCD,匹配Bean的ID或名称,可以用通配符 |
execution常见表达式:
每个部分都可以使用*通配符
类名中使用.表示包中的所有类,..表示当前包与子包中的所有类
参数情况:
- ()表示方法无参数
- (..)表示有任意个参数
- (*) 表示有一个任意类型的参数
- (String表示有一个String类型参数
- (String,String)代表有两个String类型的参数
前置通知:@Before()
后置通知:@AfterReturning()
环绕通知:@Around()
posted on 2023-03-12 12:45 JavaCoderPan 阅读(42) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南