8 Spring AOP
约 9531 字大约 32 分钟
2025-12-24
学习目标
- 了解Spring AOP的概念及其术语
- 熟悉Spring AOP的JDK动态代理
- 熟悉Spring AOP的CGLib动态代理
- 掌握基于XML的AOP实现
- 掌握基于注解的AOP实现
Spring的AOP模块是Spring框架体系中十分重要的内容,该模块一般适用于具有横切逻辑的场景,如访问控制、事务管理和性能监控等。
8.1 Spring AOP介绍
8.1.1 Spring AOP概述
AOP与OOP不同,AOP主张将程序中相同的业务逻辑进行横向隔离,并将重复的业务逻辑抽取到一个独立的模块中,以达到提高程序可重用性和开发效率的目的。
在传统的业务处理代码中,通常都会进行事务处理、日志记录等操作。虽然使用OOP可以通过组合或者继承的方式来实现代码的重用,但如果要实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。如果想要关闭某个功能,或者对其进行修改,就必须修改所有的相关方法。这不但增加了开发人员的工作量,而且增加了代码的出错率。例如,订单系统中有添加订单信息、更新订单信息和删除订单信息3个方法,这3个方法中都包含事务管理业务代码。订单系统的逻辑如图8-1所示。

图8-1 订单系统的逻辑
由图8-1可知,添加订单信息、更新订单信息、删除订单信息的方法体中都包含事务管理业务逻辑,这就带来了一定数量的重复代码并使程序的维护成本增加。AOP可以为此类问题提供完美的解决方案,它可以将事务管理业务逻辑从这3个方法体中抽取到一个可重用的模块,进而降低横向业务逻辑之间的耦合,减少重复代码。
AOP的使用使开发人员在编写业务逻辑时可以专心于核心业务,而不用过多地关注其他业务逻辑的实现,这不但提高了开发效率,而且增强了代码的可维护性。
8.1.2 Spring AOP术语
AOP并不是一个新的概念,在Java语言中,早就出现了类似的机制。Java平台的EJB规范、Servlet规范和Struts2框架中存在的拦截器机制,实际上与AOP的实现机制非常相似。AOP是在这些概念基础上发展起来的,为重复的业务逻辑提供了更通用的解决方案。
AOP中涉及很多术语,如切面、连接点、切入点、通知/增强处理、目标对象、织入、代理和引介等,下面将对AOP的常用术语进行简单介绍。
1. 切面(Aspect)
切面是指关注点(指类中重复的代码)形成的类,通常是指封装的、用于横向插入系统的功能类(如事务管理、日志记录等)。在实际开发中,该类被Spring容器识别为切面,需要在配置文件中通过<bean>元素指定。
2. 连接点(Joinpoint)
连接点是程序执行过程中某个特定的节点,例如,某方法调用时或处理异常时。在Spring AOP中,一个连接点通常是一个方法的执行。
3. 切入点(Pointcut)
当某个连接点满足预先指定的条件时,AOP就能够定位到这个连接点,在连接点处插入切面,该连接点也就变成了切入点。例如,在图8-1中,添加订单信息方法、更新订单信息方法、删除订单信息方法都满足操作订单相关的规则,需要插入事务管理切面,AOP会根据规则定位到这些连接点,将事务管理切面横向插入。
4. 通知/增强处理(Advice)
通知/增强处理就是插入的切面程序代码。可以将通知/增强处理理解为切面中的方法,它是切面的具体实现。
5. 目标对象(Target)
目标对象是指被插入切面的方法,例如,图8-1中插入事务管理切面的添加订单信息方法就是一个目标对象。
6. 织入(Weaving)
将切面代码插入到目标对象上,从而生成代理对象的过程。
7. 代理(Proxy)
将通知应用到目标对象之后,程序动态创建的通知对象,就称为代理。
8. 引介(Introduction)
引介是一种特殊的通知,它可为目标对象添加一些属性和方法。这样,即使一个业务类原本没有实现某一个接口,通过AOP的引介功能,也可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。例如,在图8-1中的添加订单信息方法中需要添加一个信息审核功能,此时可以定义一个信息审核功能接口并实现,然后将实现类作为切面插入到添加订单信息方法中使用,信息审核功能接口的实现类就是引介。
8.2 Spring AOP的实现机制
Spring AOP实现时需要创建一个代理对象,根据代理对象的创建方式,可以将AOP实现机制分为两种,一种是JDK动态代理,另一种是CGLib动态代理。
8.2.1 JDK动态代理
默认情况下,Spring AOP使用JDK动态代理,JDK动态代理是通过java.lang.reflect.Proxy类实现的,可以调用Proxy类的newProxyInstance()方法创建代理对象。JDK动态代理可以实现无侵入式的代码扩展,并且可以在不修改源代码的情况下增强某些方法。
下面通过一个案例演示Spring中JDK动态代理的实现过程,具体实现步骤如下。
(1)在IDEA中创建一个名称为ch8的Maven项目,然后在项目的pom.xml文件中加载需使用到的Spring基础包和Spring的依赖包。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>heima_ssm_book</artifactId>
<groupId>io.weew12.github</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ch8</artifactId>
<packaging>jar</packaging>
<name>ch8</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>5.3.31</spring.version>
<common.logging.version>1.2</common.logging.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>${common.logging.version}</version>
</dependency>
</dependencies>
</project>(2)在项目的src/main/java目录下,创建一个demo01包,在该包下创建接口UserDao,在UserDao接口中编写添加和删除的方法:
package io.weew12.github.demo01;
public interface UserDao {
void addUser();
void deleteUser();
}(3)在demo01包中,创建UserDao接口的实现类UserDaoImpl,分别实现接口中的方法:
package io.weew12.github.demo01;
/**
* 目标类
*/
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}需要注意的是,本案例将实现类UserDaoImpl作为目标类,对其中的方法进行增强处理。
(4)在demo01包下创建切面类MyAspect,在该类中定义一个模拟权限检查的方法和一个模拟日志记录的方法,这两个方法就是切面中的通知:
package io.weew12.github.demo01;
/**
* 切面类:存在多个通知 Advice/(增强的方法)
*/
public class MyAspect {
public void checkPermissions() {
System.out.println("模拟检查权限...");
}
public void log() {
System.out.println("模拟日志记录...");
}
}(5)在demo01包下创建代理类MyProxy,该类需要实现InvocationHandler接口设置代理类的调用处理程序。在代理类中,通过newProxyInstance()生成代理方法:
package io.weew12.github.demo01;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK代理类
*/
public class MyProxy implements InvocationHandler {
/**
* 声明目标类接口
*/
private UserDao userDao;
/**
* 创建代理方法
* <p> Description:
* 创建代理对象
* </p>
*
* @param userDao 目标类对象
* @return 代理对象
*/
public Object createProxy(UserDao userDao) {
this.userDao = userDao;
// 类加载器
ClassLoader classLoader = MyProxy.class.getClassLoader();
// 被代理对象实现的所有接口
Class<?>[] interfaces = userDao.getClass().getInterfaces();
// 使用代理类进行增强 返回的是代理对象
return Proxy.newProxyInstance(classLoader, interfaces, this);
}
/**
* 所有动态代理类的方法调用,都会交由invoke()方法去处理
*
* @param proxy 被代理的对象
* @param method 将要被执行的方法信息(反射)
* @param args 执行方法时需要的参数
* @return 方法执行后的返回值
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 创建切面对象
MyAspect aspect = new MyAspect();
// 前增强
aspect.checkPermissions();
// 在目标类上调用方法,并传入参数
Object result = method.invoke(userDao, args);
// 后增强
aspect.log();
return result;
}
}在文件中,创建InvocationHandler接口的实现类MyProxy;定义了代理方法createProxy(),在creatProxy()方法中,调用Proxy类的newProxyInstance()方法创建代理对象。
newProxyInstance()方法包含3个参数,具体介绍如下:
- 第1个参数是
classLoader,表示当前类的类加载器。 - 第2个参数是
classes,表示被代理对象实现的所有接口。 - 第3个参数是
this,表示代理类JdkProxy本身。
代码实现了InvocationHandler接口中的invoke()方法,动态代理类调用的所有方法都会交由invoke()方法处理。在invoke()方法中,在目标类UserDao执行前,会执行切面类中的checkPermissions()方法;目标类方法执行后,会执行切面类中的log()方法。
(6)在demo01包中,创建测试类JDKTest。在该类中的main()方法中创建代理对象jdkProxy和目标对象userDao,然后从代理对象jdkProxy中获得对目标对象userDao增强后的对象userDao1,最后调用userDao1对象中的添加和删除方法:
package io.weew12.github.demo01;
public class JDKTest {
public static void main(String[] args) {
// 创建代理对象
MyProxy myProxy = new MyProxy();
// 创建目标对象
UserDao userDao = new UserDaoImpl();
// 从代理对象中获取增强后的目标对象
UserDao proxyUserDao = (UserDao) myProxy.createProxy(userDao);
// 执行方法
proxyUserDao.addUser();
proxyUserDao.deleteUser();
}
}在IDEA中启动JDKTest类,控制台的输出结果如图所示。

由图可知,目标对象userDao中的添加用户和删除用户的方法被成功调用,并且在调用前后分别增加了检查权限和记录日志的功能。这种实现了接口的代理方式,就是Spring中的JDK动态代理。
8.2.2 CGLib动态代理
JDK动态代理存在缺陷,它只能为接口创建代理对象,当需要为类创建代理对象时,就需要使用CGLib(Code Generation Library)动态代理,CGLib动态代理不要求目标类实现接口,它采用底层的字节码技术,通过继承的方式动态创建代理对象。Spring的核心包已经集成了CGLib所需要的包,所以开发中不需要另外导入JAR包。
下面通过一个案例演示CGLib动态代理的实现过程,具体步骤如下。
(1)在项目的src/main/java目录下创建一个demo02包,在该包下创建目标类UserDao,在该类中编写添加用户和删除用户的方法
package io.weew12.github.demo2;
public class UserDao {
void addUser() {
System.out.println("添加用户");
}
void deleteUser() {
System.out.println("删除用户");
}
}(2)在demo02包下创建代理类CglibProxy,该代理类需要实现**MethodInterceptor**接口用于设置代理类的调用处理程序,并实现接口中的intercept()方法
package io.weew12.github.demo2;
import io.weew12.github.demo01.MyAspect;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 代理类
*/
public class CglibProxy implements MethodInterceptor {
/**
* 代理方法
*/
public Object createProxy(Object target) {
// 创建一个动态代理对象
Enhancer enhancer = new Enhancer();
// 确定要增强的类 设置其父类
enhancer.setSuperclass(target.getClass());
// 添加回调函数
enhancer.setCallback(this);
// 返回创建的代理实例
return enhancer.create();
}
/**
* @param proxy CGLib根据指定父类生成的代理对象
* @param method 拦截的方法
* @param args 拦截方法的参数数组
* @param methodProxy 方法的代理对象,用于执行父类的方法
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 创建切面对象
MyAspect myAspect = new MyAspect();
// 前增强
myAspect.checkPermissions();
// 目标方法的执行
Object o = methodProxy.invokeSuper(proxy, args);
// 后增强
myAspect.log();
return o;
}
}在文件中,createProxy是定义的代理方法。在该方法中,创建了一个动态类Enhancer的对象enhancer,Enhancer是CGLib的核心类;调用Enhancer类的setSuperclass()方法设置目标对象;调用setCallback()方法添加回调函数,其中,参数this代表的是代理类CglibProxy本身;通过return语句将创建的代理实例对象返回。intercept()方法会在程序执行目标方法时被调用,intercept()方法运行时会执行切面类中的增强方法。
(3)在demo02包中创建测试类CglibTest,在main()方法中首先创建代理对象cglibProxy和目标对象userDao,然后从代理对象cglibProxy中获得增强后的目标对象userDao1,最后调用userDao1对象的添加和删除方法
package io.weew12.github.demo2;
public class CglibTest {
public static void main(String[] args) {
// 创建代理对象
CglibProxy cglibProxy = new CglibProxy();
// 创建目标对象
UserDao userDao = new UserDao();
// 获取增强后的目标对象
UserDao userDao1 = (UserDao) cglibProxy.createProxy(userDao);
// 执行方法
userDao1.addUser();
userDao1.deleteUser();
}
}在IDEA中启动CglibTest类,控制台的输出结果如图所示。

由图可知,目标类UserDao中的方法被成功调用并进行了增强。这种没有实现接口的目标类的代理方式,就是CGLib动态代理。
8.3 基于XML的AOP实现
8.2节介绍了Spring AOP的实现机制,下面讲解Spring AOP的实现方法。Spring AOP的常用实现方法有两种,分别是基于XML文件的实现和基于注解的实现,下面首先对基于XML的Spring AOP实现做详细讲解。
因为Spring AOP中的代理对象由IoC容器自动生成,所以开发者无须过多关注代理对象生成的过程,只需选择连接点、创建切面、定义切点并在XML文件中添加配置信息即可。
Spring提供了一系列配置Spring AOP的XML元素,具体如表8-1所示。
表8-1 配置SpringAOP的XML元素
| 元素 | 描述 |
|---|---|
<aop:config> | Spring AOP配置的根元素 |
<aop:aspect> | 配置切面 |
<aop:advisor> | 配置通知器 |
<aop:pointcut> | 配置切入点 |
<aop:before> | 配置前置通知,在目标方法执行前实施增强,可以应用于权限管理等功能 |
<aop:after> | 配置后置通知,在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能 |
<aop:around> | 配置环绕通知,在目标方法执行前后实施增强,可以应用于日志、事务管理等功能 |
<aop:after-returning> | 配置返回通知,在目标方法成功执行之后调用通知 |
<aop:after-throwing> | 配置异常通知,在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能 |
表8-1列举了XML文件配置Spring AOP的相关元素,下面对表8-1中的元素进行详细讲解。
1. 配置切面
在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,因此,在使用<aop:aspect>元素之前,要在配置文件中先定义一个普通的Spring Bean。Spring Bean定义完成后,通过<aop:aspect>元素的ref属性即可引用该Bean。
配置<aop:aspect>元素时,通常会指定id和ref这两个属性,其具体描述如表8-2所示。
表8-2 <aop:aspect> 元素的id属性和ref属性
| 属性名称 | 描述 |
|---|---|
id | 用于定义该切面的唯一标识 |
ref | 用于引用普通的Spring Bean |
2. 配置切入点
在Spring的配置文件中,切入点是通过<aop:pointcut>元素来定义的。当<aop:pointcut>元素作为<aop:config>元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面共享;当<aop:pointcut>元素作为<aop:aspect>元素的子元素时,表示该切入点只对当前切面有效。
在定义<aop:pointcut>元素时,通常会指定id和expression这两个属性,其具体描述如表8-3所示。
表8-3<aop:pointcut>元素的id属性和expression属性
| 属性名称 | 描述 |
|---|---|
id | 用于指定切入点的唯一标识 |
expression | 用于指定切入点关联的切入点表达式 |
Spring AOP切入点表达式的基本格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)在上述格式中,execution表达式各部分参数说明如下:
modifiers-pattern:表示定义的目标方法的访问修饰符,如public、private等。ret-type-pattern:表示定义的目标方法的返回值类型,如void、String等。declaring-type-pattern:表示定义的目标方法的类路径,如com.itheima.jdk.UserDaoImpl。name-pattern:表示具体需要被代理的目标方法,如add()方法。param-pattern:表示需要被代理的目标方法包含的参数。throws-pattern:表示需要被代理的目标方法抛出的异常类型。
其中,带有问号?的部分,如modifiers-pattern、declaring-type-pattern和throws-pattern表示可选配置项,而其他部分是必备配置项。
更多切入点表达式的配置信息:https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/pointcuts.html
3. 配置通知
在Spring的配置文件中,使用<aop:aspect>元素配置了5种常用通知,如表8-1所示,5种通知分别为前置通知、后置通知、环绕通知、返回通知和异常通知,<aop:aspect>元素的常用属性如表8-4所示。
表8-4<aop:aspect>元素的常用属性
| 属性名称 | 描述 |
|---|---|
pointcut | 该属性用于指定一个切入点表达式,Spring将在匹配该表达式的连接点时织入该通知 |
pointcut-ref | 该属性指定一个已经存在的切入点名称,如配置代码中的myPointCut。通常只需要使用pointcut和pointcut-ref这两个属性中的一个即可 |
method | 该属性指定一个方法名,指定将切面Bean中的该方法转换为增强处理 |
throwing | 该属性只对<aop:after-throwing>元素有效,用于指定一个形参名,异常通知方法可以通过该形参访问目标方法所抛出的异常 |
returning | 该属性只对<aop:after-returning>元素有效,用于指定一个形参名,后置通知方法可以通过该形参访问目标方法的返回值 |
下面通过一个案例演示如何在Spring中使用XML实现Spring AOP,具体实现步骤如下。
(1)在项目的pom.xml文件中导入依赖,代码如下:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>(2)在项目的src/main/java目录下创建一个demo03包,在该包下创建接口UserDao,并在该接口中编写添加、删除、修改和查询的方法
package io.weew12.github.demo03;
public interface UserDao {
void insert();
void delete();
void update();
void select();
}(3)在demo03包下创建UserDao接口的实现类UserDaoImpl,实现UserDao接口中的方法
package io.weew12.github.demo03;
public class UserDaoImpl implements UserDao {
@Override
public void insert() {
System.out.println("添加用户信息");
}
@Override
public void delete() {
System.out.println("删除用户信息");
}
@Override
public void update() {
System.out.println("更新用户信息");
}
@Override
public void select() {
System.out.println("查询用户信息");
}
}(4)在demo03包下创建XmlAdvice类,用于定义通知
package io.weew12.github.demo03;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class XmlAdvice {
/**
* 前置通知
*/
public void before(JoinPoint joinPoint) {
System.out.print("这是前置通知!");
System.out.print("目标类是: " + joinPoint.getTarget());
System.out.println(",被织入增强处理的目标方法为:" +
joinPoint.getSignature().getName());
}
/**
* 返回通知
*/
public void afterReturning(JoinPoint joinPoint) {
System.out.print("这是返回通知(方法不出现异常时调用)!");
System.out.println("被织入增强处理的目标方法为:" +
joinPoint.getSignature().getName());
}
/**
* 环绕通知
*/
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("这是环绕通知之前的部分!");
// 调用目标方法
Object object = point.proceed();
System.out.println("这是环绕通知之后的部分!");
return object;
}
/**
* 异常通知
*/
public void afterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.print("这是异常通知!");
System.out.println("方法" + joinPoint.getSignature().getName()
+ "发生异常:" + e.getMessage());
}
/**
* 后置通知
*/
public void after(JoinPoint joinPoint) {
System.out.println("这是后置通知(最终通知),方法:"
+ joinPoint.getSignature().getName() + "执行完成");
}
}需要注意的是,环绕通知必须接收一个类型为ProceedingJoinPoint的参数,返回值也必须是Object类型,且必须抛出异常。
(5)在项目的src/main/resources目录下创建applicationContext.xml文件,在该文件中引入AOP命名空间,用<bean>元素添加Spring AOP的配置信息
<?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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册Bean -->
<bean name="userDao" class="io.weew12.github.demo03.UserDaoImpl"/>
<bean name="xmlAdvice" class="io.weew12.github.demo03.XmlAdvice"/>
<!-- 配置Spring AOP -->
<aop:config>
<!-- 指定切入点 -->
<aop:pointcut id="pointcut" expression="execution(* io.weew12.github.demo03.UserDaoImpl.*(..))"/>
<!-- 指定切面 -->
<aop:aspect ref="xmlAdvice">
<!-- 指定前置通知 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<!-- 指定返回通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
<!-- 指定环绕通知 -->
<aop:around method="around" pointcut-ref="pointcut"/>
<!-- 指定异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
<!-- 指定后置通知 -->
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>在文件中,使用<aop:pointcut>元素指定了一个id为pointcut的切入点;使用<aop:aspect>元素指定了一个切面。在<aop:aspect>元素中,使用<aop:before>元素指定了前置通知;使用<aop:after-returning>元素指定了返回通知;使用<aop:around>元素指定了环绕通知;使用<aop:after-throwing>元素指定了异常通知;使用<aop:after>元素指定了后置通知。
(6)在demo03包中创建测试类TestXml
package io.weew12.github.demo03;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestXml {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
userDao.delete();
System.out.println();
userDao.insert();
System.out.println();
userDao.select();
System.out.println();
userDao.update();
}
}在IDEA中启动TestXml类,控制台的输出结果如下所示。
D:\Java\jdk1.8.0_202\bin\java.exe "-javaagent:D:\JetBrains\IntelliJ IDEA 2022.2.4\lib\idea_rt.jar=14618:D:\JetBrains\IntelliJ IDEA 2022.2.4\bin" -Dfile.encoding=UTF-8 -classpath D:\Java\jdk1.8.0_202\jre\lib\charsets.jar;D:\Java\jdk1.8.0_202\jre\lib\deploy.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\access-bridge-64.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\cldrdata.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\dnsns.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\jaccess.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\jfxrt.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\localedata.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\nashorn.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\sunec.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\sunjce_provider.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\sunmscapi.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\sunpkcs11.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\zipfs.jar;D:\Java\jdk1.8.0_202\jre\lib\javaws.jar;D:\Java\jdk1.8.0_202\jre\lib\jce.jar;D:\Java\jdk1.8.0_202\jre\lib\jfr.jar;D:\Java\jdk1.8.0_202\jre\lib\jfxswt.jar;D:\Java\jdk1.8.0_202\jre\lib\jsse.jar;D:\Java\jdk1.8.0_202\jre\lib\management-agent.jar;D:\Java\jdk1.8.0_202\jre\lib\plugin.jar;D:\Java\jdk1.8.0_202\jre\lib\resources.jar;D:\Java\jdk1.8.0_202\jre\lib\rt.jar;F:\技术\projects\heima_ssm_book\ch8\target\classes;D:\apache-maven-3.8.6\repository\org\springframework\spring-core\5.3.31\spring-core-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-jcl\5.3.31\spring-jcl-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-beans\5.3.31\spring-beans-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-context\5.3.31\spring-context-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-aop\5.3.31\spring-aop-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-expression\5.3.31\spring-expression-5.3.31.jar;D:\apache-maven-3.8.6\repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;D:\apache-maven-3.8.6\repository\org\aspectj\aspectjrt\1.9.7\aspectjrt-1.9.7.jar;D:\apache-maven-3.8.6\repository\org\aspectj\aspectjweaver\1.9.7\aspectjweaver-1.9.7.jar io.weew12.github.demo03.TestXml
这是前置通知!目标类是: io.weew12.github.demo03.UserDaoImpl@56a6d5a6,被织入增强处理的目标方法为:delete
这是环绕通知之前的部分!
删除用户信息
这是后置通知(最终通知),方法:delete执行完成
这是环绕通知之后的部分!
这是返回通知(方法不出现异常时调用)!被织入增强处理的目标方法为:delete
这是前置通知!目标类是: io.weew12.github.demo03.UserDaoImpl@56a6d5a6,被织入增强处理的目标方法为:insert
这是环绕通知之前的部分!
添加用户信息
这是后置通知(最终通知),方法:insert执行完成
这是环绕通知之后的部分!
这是返回通知(方法不出现异常时调用)!被织入增强处理的目标方法为:insert
这是前置通知!目标类是: io.weew12.github.demo03.UserDaoImpl@56a6d5a6,被织入增强处理的目标方法为:select
这是环绕通知之前的部分!
查询用户信息
这是后置通知(最终通知),方法:select执行完成
这是环绕通知之后的部分!
这是返回通知(方法不出现异常时调用)!被织入增强处理的目标方法为:select
这是前置通知!目标类是: io.weew12.github.demo03.UserDaoImpl@56a6d5a6,被织入增强处理的目标方法为:update
这是环绕通知之前的部分!
更新用户信息
这是后置通知(最终通知),方法:update执行完成
这是环绕通知之后的部分!
这是返回通知(方法不出现异常时调用)!被织入增强处理的目标方法为:update
Process finished with exit code 0补充:异常版本的流程
package io.weew12.github.demo03;
public interface UserDao {
void insert() throws Exception;
void delete();
void update();
void select();
}package io.weew12.github.demo03;
public class UserDaoImpl implements UserDao {
@Override
public void insert() throws Exception{
System.out.println("添加用户信息");
throw new Exception("test");
}
@Override
public void delete() {
System.out.println("删除用户信息");
}
@Override
public void update() {
System.out.println("更新用户信息");
}
@Override
public void select() {
System.out.println("查询用户信息");
}
}package io.weew12.github.demo03;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestXml {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
// userDao.delete();
// System.out.println();
userDao.insert();
// System.out.println();
// userDao.select();
// System.out.println();
// userDao.update();
}
}D:\Java\jdk1.8.0_202\bin\java.exe "-javaagent:D:\JetBrains\IntelliJ IDEA 2022.2.4\lib\idea_rt.jar=1616:D:\JetBrains\IntelliJ IDEA 2022.2.4\bin" -Dfile.encoding=UTF-8 -classpath D:\Java\jdk1.8.0_202\jre\lib\charsets.jar;D:\Java\jdk1.8.0_202\jre\lib\deploy.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\access-bridge-64.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\cldrdata.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\dnsns.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\jaccess.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\jfxrt.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\localedata.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\nashorn.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\sunec.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\sunjce_provider.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\sunmscapi.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\sunpkcs11.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\zipfs.jar;D:\Java\jdk1.8.0_202\jre\lib\javaws.jar;D:\Java\jdk1.8.0_202\jre\lib\jce.jar;D:\Java\jdk1.8.0_202\jre\lib\jfr.jar;D:\Java\jdk1.8.0_202\jre\lib\jfxswt.jar;D:\Java\jdk1.8.0_202\jre\lib\jsse.jar;D:\Java\jdk1.8.0_202\jre\lib\management-agent.jar;D:\Java\jdk1.8.0_202\jre\lib\plugin.jar;D:\Java\jdk1.8.0_202\jre\lib\resources.jar;D:\Java\jdk1.8.0_202\jre\lib\rt.jar;F:\技术\projects\heima_ssm_book\ch8\target\classes;D:\apache-maven-3.8.6\repository\org\springframework\spring-core\5.3.31\spring-core-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-jcl\5.3.31\spring-jcl-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-beans\5.3.31\spring-beans-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-context\5.3.31\spring-context-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-aop\5.3.31\spring-aop-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-expression\5.3.31\spring-expression-5.3.31.jar;D:\apache-maven-3.8.6\repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;D:\apache-maven-3.8.6\repository\org\aspectj\aspectjrt\1.9.7\aspectjrt-1.9.7.jar;D:\apache-maven-3.8.6\repository\org\aspectj\aspectjweaver\1.9.7\aspectjweaver-1.9.7.jar io.weew12.github.demo03.TestXml
这是前置通知!目标类是: io.weew12.github.demo03.UserDaoImpl@18ce0030,被织入增强处理的目标方法为:insert
这是环绕通知之前的部分!
添加用户信息
这是后置通知(最终通知),方法:insert执行完成
这是异常通知!方法insert发生异常:test
Exception in thread "main" java.lang.Exception: test
at io.weew12.github.demo03.UserDaoImpl.insert(UserDaoImpl.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:49)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:64)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at io.weew12.github.demo03.XmlAdvice.around(XmlAdvice.java:43)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:634)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:624)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:72)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:57)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:58)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:241)
at com.sun.proxy.$Proxy3.insert(Unknown Source)
at io.weew12.github.demo03.TestXml.main(TestXml.java:23)
Process finished with exit code 18.4 基于注解的AOP实现
8.3节中讲解了基于XML的AOP实现,但基于XML的AOP实现需要在Spring文件中配置大量的代码信息,不利于代码阅读和维护。为了解决此问题,Spring AOP允许使用基于注解的方式实现AOP,这样做可以简化Spring配置文件中的臃肿代码。为实现AOP,Spring AOP提供了一系列的注解,如表8-5所示。
表8-5 SpringAOP提供的一系列注解
| 注解名称 | 描述 |
|---|---|
@Aspect | 配置切面 |
@Pointcut | 配置切入点 |
@Before | 配置前置通知 |
@After | 配置后置通知 |
@Around | 配置环绕通知 |
@AfterReturning | 配置返回通知 |
@AfterThrowing | 配置异常通知 |
下面通过一个案例演示基于注解的AOP的实现,具体步骤如下。
定义 UserDao 接口和实现类 UserDaoImpl:
package io.weew12.github.demo4;
public interface UserDao {
void insert();
void delete();
void update();
void select();
}package io.weew12.github.demo4;
public class UserDaoImpl implements UserDao {
@Override
public void insert() {
System.out.println("添加用户信息");
}
@Override
public void delete() {
System.out.println("删除用户信息");
}
@Override
public void update() {
System.out.println("更新用户信息");
}
@Override
public void select() {
System.out.println("查询用户信息");
}
}(1)在项目的src/main/java目录下创建一个demo04包,在该包下创建AnnoAdvice类,用于定义通知:
package io.weew12.github.demo4;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class AnnoAdvice {
/**
* 切点
*/
@Pointcut("execution(* io.weew12.github.demo4.UserDaoImpl.*(..))")
public void pointcut() {
}
/**
* 前置通知
*/
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
System.out.println("这是前置通知!");
System.out.println("目标类是:" + joinPoint.getTarget());
System.out.println(",被织入增强处理的目标方法为:" + joinPoint.getSignature().getName());
}
/**
* 返回通知
*/
@AfterReturning("pointcut()")
public void afterReturning(JoinPoint joinPoint) {
System.out.println("这是返回通知!");
System.out.println("目标类是:" + joinPoint.getTarget());
System.out.println(",被织入增强处理的目标方法为:" + joinPoint.getSignature().getName());
}
/**
* 环绕通知
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("这是环绕通知之前的部分!");
System.out.println("目标类是:" + point.getTarget());
System.out.println(",被织入增强处理的目标方法为:" + point.getSignature().getName());
// 调用目标方法
Object res = point.proceed();
System.out.println("这是环绕通知之后的部分!");
return res;
}
/**
* 异常通知
*/
@AfterThrowing("pointcut()")
public void afterThrowing(JoinPoint joinPoint) {
System.out.println("这是异常通知!");
System.out.println("目标类是:" + joinPoint.getTarget());
System.out.println(",被织入增强处理的目标方法为:" + joinPoint.getSignature().getName());
}
/**
* 后置通知
*/
@After("pointcut()")
public void after(JoinPoint joinPoint) {
System.out.println("这是后置通知!");
System.out.println("目标类是:" + joinPoint.getTarget());
System.out.println(",被织入增强处理的目标方法为:" + joinPoint.getSignature().getName());
}
}在文件中,使用@Aspect注解定义了AnnoAdvice类为切面类;使用@Pointcut注解来配置切入点表达式;使用@Before注解来配置前置通知方法;使用@AfterReturning注解来配置返回通知方法;使用@Around注解来配置环绕通知方法;使用@AfterThrowing注解来配置异常通知方法;使用@After注解来配置后置通知方法。
(2)在项目的src/main/resources目录下创建applicationContextAnno.xml文件,在该文件中引入AOP命名空间,使用<bean>元素添加Spring AOP的配置信息
<?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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册bean-->
<bean id="userDao" class="io.weew12.github.demo4.UserDaoImpl"/>
<bean id="annoAdvice" class="io.weew12.github.demo4.AnnoAdvice"/>
<!-- 开启@AspectJ的自动代理支持-->
<aop:aspectj-autoproxy/>
</beans>在文件中,将UserDaoImpl类和AnnoAdvice类注册为Spring容器中的Bean;用于开启@AspectJ的自动代理支持。
(3)在demo04包中创建测试类TestAnnotation
package io.weew12.github.demo4;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAnnotation {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContextAnno.xml");
UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
userDao.delete();
System.out.println();
userDao.insert();
System.out.println();
userDao.select();
System.out.println();
userDao.update();
}
}在IDEA中启动TestAnnotation类,控制台的输出结果如下所示。
D:\Java\jdk1.8.0_202\bin\java.exe "-javaagent:D:\JetBrains\IntelliJ IDEA 2022.2.4\lib\idea_rt.jar=14742:D:\JetBrains\IntelliJ IDEA 2022.2.4\bin" -Dfile.encoding=UTF-8 -classpath D:\Java\jdk1.8.0_202\jre\lib\charsets.jar;D:\Java\jdk1.8.0_202\jre\lib\deploy.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\access-bridge-64.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\cldrdata.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\dnsns.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\jaccess.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\jfxrt.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\localedata.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\nashorn.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\sunec.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\sunjce_provider.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\sunmscapi.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\sunpkcs11.jar;D:\Java\jdk1.8.0_202\jre\lib\ext\zipfs.jar;D:\Java\jdk1.8.0_202\jre\lib\javaws.jar;D:\Java\jdk1.8.0_202\jre\lib\jce.jar;D:\Java\jdk1.8.0_202\jre\lib\jfr.jar;D:\Java\jdk1.8.0_202\jre\lib\jfxswt.jar;D:\Java\jdk1.8.0_202\jre\lib\jsse.jar;D:\Java\jdk1.8.0_202\jre\lib\management-agent.jar;D:\Java\jdk1.8.0_202\jre\lib\plugin.jar;D:\Java\jdk1.8.0_202\jre\lib\resources.jar;D:\Java\jdk1.8.0_202\jre\lib\rt.jar;F:\技术\projects\heima_ssm_book\ch8\target\classes;D:\apache-maven-3.8.6\repository\org\springframework\spring-core\5.3.31\spring-core-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-jcl\5.3.31\spring-jcl-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-beans\5.3.31\spring-beans-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-context\5.3.31\spring-context-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-aop\5.3.31\spring-aop-5.3.31.jar;D:\apache-maven-3.8.6\repository\org\springframework\spring-expression\5.3.31\spring-expression-5.3.31.jar;D:\apache-maven-3.8.6\repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;D:\apache-maven-3.8.6\repository\org\aspectj\aspectjrt\1.9.7\aspectjrt-1.9.7.jar;D:\apache-maven-3.8.6\repository\org\aspectj\aspectjweaver\1.9.7\aspectjweaver-1.9.7.jar io.weew12.github.demo4.TestAnnotation
这是环绕通知之前的部分!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:delete
这是前置通知!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:delete
删除用户信息
这是返回通知!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:delete
这是后置通知!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:delete
这是环绕通知之后的部分!
这是环绕通知之前的部分!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:insert
这是前置通知!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:insert
添加用户信息
这是返回通知!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:insert
这是后置通知!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:insert
这是环绕通知之后的部分!
这是环绕通知之前的部分!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:select
这是前置通知!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:select
查询用户信息
这是返回通知!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:select
这是后置通知!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:select
这是环绕通知之后的部分!
这是环绕通知之前的部分!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:update
这是前置通知!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:update
更新用户信息
这是返回通知!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:update
这是后置通知!
目标类是:io.weew12.github.demo4.UserDaoImpl@78e117e3
,被织入增强处理的目标方法为:update
这是环绕通知之后的部分!
Process finished with exit code 0由上面的输出结果可知,程序执行了AnnoAdvice类中的增强方法,由此可见,采用注解方式实现了与XML配置文件方式同样的效果。
8.5 本章小结
本章主要讲解了Spring中的AOP。首先介绍了Spring AOP,包括Spring AOP的概述和Spring AOP的术语;然后讲解了Spring AOP的实现机制,包括JDK动态代理和CGLib动态代理;接着讲解了基于XML的AOP实现,并使用案例演示了基于XML文件的AOP实现;最后讲解了基于注解的AOP实现。通过学习本章的内容,可以对Spring AOP有基本的了解。
【思考题】
1. 请列举Spring AOP的术语并解释
- 切面(Aspect):切面是跨越多个对象的行为或关注点。在Spring AOP中,它是一个类,包含了通知和切入点定义。
- 连接点(Join Point):程序执行过程中明确的点,如方法调用、异常抛出等。Spring AOP目前只支持方法级别的连接点。
- 通知(Advice):这是切面的具体实现部分,定义了在某个特定连接点上执行的动作。根据何时执行,可以分为前置通知(Before Advice)、后置通知(After Returning Advice)、环绕通知(Around Advice)、异常通知(After Throwing Advice)以及最终通知(After Finally Advice)。
- 切入点(Pointcut):定义了哪些连接点会被通知所影响。通过表达式来匹配一个或多个连接点。
- 引入(Introduction):允许我们向现有的类添加新的方法或属性,从而改变其行为。
- 目标对象(Target Object):被一个或多个切面所通知的对象。也称为被代理的对象。
- AOP代理(AOP Proxy):由AOP框架创建的一个对象,用来实现切面契约(即应用通知)。当从容器中获取到的是一个代理时,该对象将根据配置织入相应的通知。
- 织入(Weaving):将切面与其他应用程序类型或对象连接起来创建一个被通知的对象的过程。这可以在编译时、类加载时或者运行时完成。Spring AOP使用JDK动态代理和CGLIB来实现代理机制,在运行时进行织入。
2. 请列举AOP实现中Spring提供的注解并解释其作用
- @Aspect:用于声明一个类为切面。标记了此注解的类将被视为配置有通知和其他切面相关的元素。
- @Pointcut:用来定义一个切入点表达式,并且可以给这个表达式命名,以便于在其他地方引用。这样可以避免重复编写相同的切入点逻辑。
- @Before:表示在目标方法执行之前执行的通知。可以通过指定的切入点表达式来确定具体要拦截的方法。
- @AfterReturning:表示在目标方法正常返回结果之后执行的通知。同样需要配合切入点使用。
- @Around:是最强大的一种通知类型,它可以完全控制目标方法的执行过程。它可以在方法调用前后都执行自定义逻辑,并且能够选择是否继续执行原方法。
- @AfterThrowing:当目标方法抛出异常时触发的通知。适用于处理异常情况下的清理工作或其他逻辑。
- @After:无论目标方法是否成功完成都会被执行的通知。类似于finally块的功能。
- @DeclareParents:用于实现引介功能,使得目标类可以拥有额外的方法或字段,而无需修改原有代码。
这些术语与注解共同构成了Spring AOP的核心概念和技术手段,使得开发者能够更加灵活地管理和增强系统中的横切关注点。
