1 AOP概述
AOP 并不是 Spring 框架的专属名称,它的全称是 Aspect Oriented Programming ,意为:面向切面编程。
在程序运行某个方法的时候,不修改原始执行代码逻辑,由程序动态地执行某些额外的功能,对原有的方法做增强,这就叫做面向切面编程。
在学习AOP之前,建议先学习一下设计模式中的代理模式。
2 术语解释
Join point(连接点)
连接点是指那些被拦截到的点。在 Spring 中这些点指的是方法,可以看作正在访问的,或者等待访问的那些需要被增强功能的方法。Spring 只支持方法类型的连接点。
Pointcut(切入点)
切入点是一个规则,定义了我们要对哪些 Joinpoint 进行拦截。因为在一个程序中会存在很多的类,每个类又存在很多的方法,而哪些方法会应用 AOP 对该方法做功能增强呢?这就需要依据我们配置的切入点规则。
Advice(通知)
拦截到 Joinpoint 之后所要做的事情就是通知。 也就是对方法做的增强功能。
通知分类
前置通知:在连接点之前运行的通知类型,它不会阻止流程进行到连接点,只是在到达连接点之前运行该通知内的行为,当然 —— 除非它引发异常;
后置通知:在连接点正常完成后要运行的通知,正常的连接点逻辑执行完,会运行该通知,当然 —— 方法正常返回而没有引发异常;
最终通知:无论连接点执行后的结果如何,正常还是异常,都会执行的通知;
异常通知:如果连接点执行因抛出异常而退出,则执行此通知;
环绕通知:环绕通知可以在方法调用之前和之后执行自定义行为。
Target (目标)
Target 指的是代理的目标对象,也就是连接点所在的类。
Aspect(切面)
切面本质是一个类,只不过是个功能类,作为整合 AOP 的切入点和通知。一般来讲,需要在 Spring 的配置文件中配置,或者通过注解来配置。
Weaving(织入)
织入是一种动作的描述,在程序运行时将增强的功能代码也就是通知,根据通知的类型(前缀后缀等…)放到对应的位置,生成代理对象。
Proxy(代理)
一个类被 AOP 织入增强后,产生的结果就是代理类
3 Spring AOP 的代理模式
代理名词解释为:以被代理人名义,在授权范围内与第三方实施行为。而在软件行业中代理模式是一种非常常用的设计模式,跟现实生活中的逻辑一致。
在开发中代理模式的表现为:创建带有现有对象的代理对象以便向外界提供功能接口。代理对象可以为委托对象执行一些附带的,增加的额外功能。
3.1 代理模式分类
在开发中,实现代理模式可以分为两种。
静态代理:若代理类在程序运行前就已经存在,那么这种代理方式被成为静态代理 ,这种情况下的代理类通常都是我们在 Java 代码中定义的, 静态代理中的代理类和委托类会实现同一接口;
动态代理:代理类在程序运行时创建的代理方式被称为动态代理。 也就是说,这种情况下,代理类并不是在 Java 代码中定义的,而是在运行时根据我们在 Java 代码中的 “指示” 动态生成的。
3.2 静态代理示例
userService 接口代码
public interface UserService {
public void saveUser();
}
userServiceImpl 实现类代码
@Service
public class UserServiceImpl implements UserService {
public void saveUser() {
System.out.println("执行service中的保存逻辑");
}
}
userServiceProxy 代理类代码
public class UserServiceProxy implements UserService {
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
@Override
public void saveUser() {
System.out.println("委托类执行saveUser方法之前的逻辑代码");
userService.saveUser();
System.out.println("委托类执行saveUser方法之后的逻辑代码");
}
}
代码解析:
userServiceProxy 代理类中的属性为委托类的接口对象,目的是在构造方法中接收委托类实例,对实例方法做功能增强。
saveUser 方法是代理类执行的逻辑,在方法内部有增强的代码逻辑,也保留了原始实例的代码功能。
测试代码:
public class test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext();
context.register(SpringConfig.class);
context.refresh();
UserService userService=context.getBean(UserService.class);
UserServiceProxy proxy=new UserServiceProxy(userService);
proxy.saveUser();
}
}
运行结果
可以看到,执行结果中即包含了被代理对象的原始保存方法的逻辑,也有代理类中对原始方法的两个增强代码。
3.3 动态代理示例
创建动态处理器
public class DynamicProxy implements InvocationHandler {
private Object object;
public DynamicProxy(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行前逻辑");
Object result = method.invoke(object, args);
System.out.println("执行后逻辑");
return result;
}
}
测试类代码
public class test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(SpringConfig.class);
context.refresh();
// 获取接口实例
UserService userService = context.getBean(UserService.class);
// 动态创建实例的代码
UserService proxy = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(),
new Class[]{
UserService.class}, new DynamicProxy(userService));
// proxy执行方法
proxy.saveUser();
}
}
代码解析
Proxy.newProxyInstance 是 JDK 提供的一个用于动态创建代理实例的方法,参数解释如下:
ClassLoader loader: 指定当前目标对象使用的类加载器,获取加载器的方法是固定的。
Class<?>[] interfaces: 指定目标对象实现的接口的类型,使用泛型方式确认类型。
InvocationHandler: 指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法。
运行结果
4 Spring AOP 实现之 XML 配置
4.1 工程搭建介绍
数据库建表 SQL:
CREATE TABLE `account` (
`id` int(11) NOT NULL auto_increment COMMENT 'id',
`accountNum` varchar(20) default NULL COMMENT '账号',
`money` int(8) default NULL COMMENT '余额',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
工程代码介绍
实体类: 跟数据库表对应的 Java 类 Account ;
操作实体类的: Dao 和 Dao 的接口实现类 ;
调用持久层的业务类: Service 和 Service 的实现类 ;
事务管理器类: TransactionManager 提供事务的一系列操作 ;
测试代码类: 初始化 Spring 调用类中的方法测试 。
AOP 中的核心概念
所以:在对原始业务类中的方法执行之前的增强行为就是前置通知,在对原始业务类中的方法执行之后的增强行为就是后置通知。而一旦出现异常,那么所做的动作就是异常通知。本案例使用几种通知,来实现事务的控制。
4.2 代码实现
创建 maven 工程:省略
pom 文件的依赖坐标如下:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.17</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.17</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>