UserDao{}
CGLIB 使用的前提是目标类不能为 final 修饰。因为 final 修饰的类不能被继承。
现在,我们可以看看 AOP 的定义:面向切面编程,核心原理是使用动态代理模式在方法执行前后或出现异常时加入相关逻辑。
通过定义和前面代码我们可以发现3点:
- AOP 是基于动态代理模式。
- AOP 是方法级别的。
- AOP 可以分离业务代码和关注点代码(重复代码),在执行业务代码时,动态的注入关注点代码。切面就是关注点代码形成的类。
4. Spring AOP
前文提到 JDK 代理和 CGLIB 代理两种动态代理。优秀的 Spring 框架把两种方式在底层都集成了进去,我们无需担心自己去实现动态生成代理。那么,Spring是如何生成代理对象的?
- 创建容器对象的时候,根据切入点表达式拦截的类,生成代理对象。
- 如果目标对象有实现接口,使用 JDK 代理。如果目标对象没有实现接口,则使用 CGLIB 代理。然后从容器获取代理后的对象,在运行期植入“切面”类的方法。通过查看 Spring 源码,我们在 DefaultAopProxyFactory 类中,找到这样一段话。
简单的从字面意思看出:如果有接口,则使用 JDK 代理,反之使用 CGLIB ,这刚好印证了前文所阐述的内容。Spring AOP 综合两种代理方式的使用前提有会如下结论:如果目标类没有实现接口,且 class 为 final 修饰的,则不能进行 Spring AOP 编程!
知道了原理,现在我们将自己手动实现 Spring 的 AOP:
package test.spring_aop_anno;
import org.aspectj.lang.ProceedingJoinPoint;
public interface IUserDao {
void save();
}
// 用于测试 CGLIB 动态代理
class OrderDao {
public void save() {
//int i =1/0; 用于测试异常通知
System.out.println("保存订单...");
}
}
//用于测试 JDK 动态代理
class UserDao implements IUserDao {
public void save() {
//int i =1/0; 用于测试异常通知
System.out.println("保存用户...");
}
}
//切面类
class TransactionAop {
public void beginTransaction() {
System.out.println("[前置通知] 开启事务..");
}
public void commit() {
System.out.println("[后置通知] 提交事务..");
}
public void afterReturing() {
System.out.println("[返回后通知]");
}
public void afterThrowing() {
System.out.println("[异常通知]");
}
public void arroud(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("[环绕前:]");
pjp.proceed(); // 执行目标方法
System.out.println("[环绕后:]");
}
}
Spring 的 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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- dao实例加入容器 -->
<bean id="userDao" class="test.spring_aop_anno.UserDao"></bean>
<!-- dao实例加入容器 -->
<bean id="orderDao" class="test.spring_aop_anno.OrderDao"></bean>
<!-- 实例化切面类 -->
<bean id="transactionAop" class="test.spring_aop_anno.TransactionAop"></bean>
<!-- Aop相关配置 -->
<aop:config>
<!-- 切入点表达式定义 -->
<aop:pointcut expression="execution(* test.spring_aop_anno.*Dao.*(..))" id="transactionPointcut"/>
<!-- 切面配置 -->
<aop:aspect ref="transactionAop">
<!-- 【环绕通知】 -->
<aop:around method="arroud" pointcut-ref="transactionPointcut"/>
<!-- 【前置通知】 在目标方法之前