Spring学习笔记-AOP
在学习 AOP 前,你需要对代理模式有一定了解,因为Spring AOP 的底层实现是基于代理模式的,关于代理模式可以参考我的这篇笔记。
1. 什么是 AOP
AOP (Aspect Oriented Programming)意为:面向切面编程,通过预编译和运行期动态代理实现程序功能的统一维护的一种技术。是对面向对象编程 OOP 的补充,是函数式编程的一种衍生泛型。
2. AOP 概念
AOP 在 Spring Framework 中用于:
- 提供声明式企业服务。最重要的此类服务是 声明式事务管理。
- 让用户实现自定义方面,用 AOP 补充他们对 OOP 的使用。
2.1 名词解释
- 横切关注点:跨越应用程序多个模块的方法或功能。即与业务逻辑无关的,但是需要我们关注的部分,如日志,安全,缓存,事务等等。
- 切面(Aspect):跨多个类的关注点的模块化对象。即,它是一个类。
- 连接点(JointPoint):程序执行过程中的一个点,例如方法的执行或异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
- 通知(Advice):被通知的对象。
- 切入点(PointCut):匹配连接点的谓词。Advice 与切入点表达式相关联,并在与切入点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。
- 目标(Target):一个或多个方面被通知的对象。也称为“被通知对象”。由于 Spring AOP 是使用运行时代理实现的,因此该对象始终是代理对象。
- 代理(Proxy):由 AOP 框架创建的对象,用于实现方面协定(建议方法执行等)。在 Spring Framework 中,AOP 代理是 JDK 动态代理或 CGLIB 代理。
- 编织(Weaving):将方面与其他应用程序类型或对象链接以创建建议对象。这可以在编译时(例如,使用 AspectJ 编译器)、加载时或运行时完成。Spring AOP 与其他纯 Java AOP 框架一样,在运行时执行编织。
2.2 通知类型种类
- 前置通知(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。
- ApplicationContext 中在 < aop:aspect > 里面使用 < aop:before > 元素进行声明;
- Spring 接口:org.springframework.aop.AfterReturningAdvice
- 后置通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- ApplicationContext 中在 < aop:aspect > 里面使用 < aop:after > 元素进行声明。
- Spring 接口:org.springframework.aop.AfterAdvice
- 返回后通知(After Returning advice :在某连接点正常完成后执行的通知,不包括抛出异常的情况。
- ApplicationContext 中在 < aop:aspect > 里面使用 << after-returning >> 元素进行声明。
- Spring 接口:org.springframework.aop.AfterReturningAdvice
- 环绕通知(Around advice):包围一个连接点的通知,类似 Web 中 Servlet规范中的 Filter 的 doFilter 方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。
- ApplicationContext 中在 < aop:aspect > 里面使用 < aop:around > 元素进行声明。
- Spring 接口:org.springframework.aop.MethodInterceptor
- 抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。
- ApplicationContext 中在 < aop:aspect > 里面使用 < aop:after-throwing > 元素进行声明。
- org.springframework.aop.ThrowsAdvice
3. AOP 实现
下面我们用不同的方法实现一个简单的AOP例子:在方法调用前后打印日志。
3.1 导入依赖包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
如果程序运行时报 BeanCreationException 错误,先检查是否没有导入该包再去排查其他错误原因
3.2 方法一:Spring 接口实现
- 编写日志类分别实现 MethodBeforeAdvice 接口和 AfterReturningAdvice 接口
public class BeforeLog implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("execute " + method.getName() + " before");
}
}
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("execute " + method.getName() + " after");
}
}
- xml 配置文件
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="com.zhao.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.zhao.log.BeforeLog"/>
<bean id="afterLog" class="com.zhao.log.AfterLog"/>
<aop:config>
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.zhao.service.UserServiceImpl.*(..))"/>
<!--执行环绕增强!-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
- 测试代码
public class App{
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 错误写法 UserServiceImpl userService = ctx.getBean("userService", UserServiceImpl.class);
UserService userService = (UserService) ctx.getBean("userService");
userService.add();
}
}
注意:这里很容易把第中间那行代码写成下面错误形式(UserService 是接口,UserServiceImpl 是实现类),程序会报Bean named 'userService' is expected to be of type 'com.zhao.service.UserServiceImpl' but was actually of type 'com.sun.proxy.$Proxy3'错误,这是因为这里通过aop获得的是代理类,不是实现类。
UserServiceImpl userService = ctx.getBean("userService", UserServiceImpl.class);
- 运行结果
execute add before
add
execute add after
3.3 方法二:自定义切面实现
-
编写自定义切面类
public class Log { public void before() { System.out.println("before"); } public void after() { System.out.println("after"); } }
-
配置文件
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="log" class="com.zhao.log.Log"/> <aop:config> <!--自定义切面--> <aop:aspect ref="log"> <!--切入点--> <aop:pointcut id="pointcut" expression="execution(* com.zhao.service.UserServiceImpl.*(..))"/> <!--通知--> <aop:before method="before" pointcut-ref="pointcut"/> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
-
测试代码同上不变
3.4 方法三:注解实现
-
注解标注切面和方法
@Component @Aspect public class AnnotationLog { @Before("execution(* com.zhao.service.UserServiceImpl.*(..))") public void before() { System.out.println("before"); } @After("execution(* com.zhao.service.UserServiceImpl.*(..))") public void after() { System.out.println("before"); } }
-
xml 配置文件
<?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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--组件扫描--> <context:component-scan base-package="com.zhao.*"/> <context:annotation-config/> <!--启用 @AspectJ 支持--> <aop:aspectj-autoproxy/> </beans>
-
输出为
环绕前 void com.zhao.service.UserService.add() before add before 环绕后
3.5 切换JDK和CGLIB
<aop:aspectj-autoproxy proxy-target-class="false"/> // 默认为JDK
<aop:aspectj-autoproxy proxy-target-class="true"/> // CGLIB
相关面试题
-
解释下什么是 AOP?
-
AOP 的代理有哪几种方式?
-
怎么实现 JDK 动态代理?
-
AOP 的基本概念:切面、连接点、切入点等?
-
通知类型(Advice)型(Advice)有哪些?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具