AOP(面向切面编程)是一种编程范式,它通过将横切关注点(如日志记录、事务管理等)从主要业务逻辑中分离出来,以提高代码的模块化和可维护性。AOP的底层原理主要涉及两个关键概念:代理和切点。
-
代理:AOP使用代理模式来实现横切关注点的分离。代理是一个中间层,它可以拦截对目标对象的访问,并在目标对象的方法执行前后插入额外的逻辑。代理可以分为静态代理和动态代理两种形式。
-
切点:切点是指在程序执行过程中,我们希望插入额外逻辑的特定位置。切点可以通过定义切点表达式来确定,切点表达式可以根据方法名、参数类型等条件进行匹配。
通过代理和切点的结合,AOP可以在不修改原有代码的情况下,实现对横切关注点的增强。具体实现方式包括静态代理、动态代理和字节码增强等。
静态代理原理及实践
package com.ape.jintai;
// 接口
public interface IUserDao {
void save();
void find();
}
//目标对象
class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("模拟:保存用户!");
}
@Override
public void find() {
System.out.println("模拟:查询用户");
}
}
/**
* 静态代理
* 特点:
* 2. 目标对象必须要实现接口
* 2. 代理对象,要实现与目标对象一样的接口
*/
class UserDaoProxy implements IUserDao{
// 代理对象,需要维护一个目标对象
private IUserDao target = new UserDao();
@Override
public void save() {
System.out.println("代理操作: 开启事务...");
target.save(); // 执行目标对象的方法
System.out.println("代理操作:提交事务...");
}
@Override
public void find() {
target.find();
}
}
测试结果:
public class Main{
public static void main(String[] arges){
//代理对象
IUserDao proxy = new UserDaoProxy();
//执行代理方法
proxy.save();
}
}
静态代理虽然保证了业务类只需关注逻辑本身,代理对象的一个接口只服务于一种类型的对象。如果要代理的方法很多,势必要为每一种方法都进行代理。再者,如果增加一个方法,除了实现类需要实现这个方法外,所有的代理类也要实现此方法。增加了代码的维护成本。那么要如何解决呢?答案是使用动态代理。
动态代理原理及实践
动态代理模式:动态代理类的源码是在程序运行期间,通过 JVM 反射等机制动态生成。代理类和委托类的关系是运行时才确定的。实例如下:
package com.ape.dongtai;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 接口
public interface IUserDao {
void save();
void find();
}
//目标对象
class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("模拟: 保存用户!");
}
@Override
public void find() {
System.out.println("查询");
}
}
/**
* 动态代理:
* 代理工厂,给多个目标对象生成代理对象!
*
*/
class ProxyFactory {
// 接收一个目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
// 返回对目标对象(target)代理后的对象(proxy)
public Object getProxyInstance() {
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标对象使用的类加载器
target.getClass().getInterfaces(), // 目标对象实现的所有接口
new InvocationHandler() { // 执行代理对象方法时候触发
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 获取当前执行的方法的方法名
String methodName = method.getName();
// 方法返回值
Object result = null;
if ("find".equals(methodName)) {
// 直接调用目标对象方法
result = method.invoke(target, args);
} else {
System.out.println("开启事务...");
// 执行目标对象方法
result = method.invoke(target, args);
System.out.println("提交事务...");
}
return result;
}
}
);
return proxy;
}
}
测试结果如下
package com.ape.dongtai;
public class Test02 {
public static void main(String[] args) {
//创建目标对象
IUserDao target = new UserDao();
System.out.println("目标对象:"+target.getClass());
//代理对象
IUserDao proxy = (IUserDao)new ProxyFactory(target).getProxyInstance();
System.out.println("代理对象:"+proxy.getClass());
//执行代理对象的方法
proxy.save();
}
}
IUserDao proxy = (IUserDao)new ProxyFactory(target).getProxyInstance();
其实是 JDK 动态生成了一个类去实现接口,隐藏了这个过程:
class $jdkProxy implements IUserDao{}
使用 JDK 生成的动态代理的前提是目标类必须有实现的接口。但这里又引入一个问题,如果某个类没有实现接口,就不能使用 JDK 动态代理。所以 CGLIB 代理就是解决这个问题的。
CGLIB 是以动态生成的子类继承目标的方式实现,在运行期动态的在内存中构建一个子类,如下:
public class UserDao{}
// CGLIB 是以动态生成的子类继承目标的方式实现,程序执行时,隐藏了下面的过程
public class $Cglib_Proxy_class extends UserDao{}
CGLIB 使用的前提是目标类不能为 final 修饰。因为 final 修饰的类不能被继承。
现在,我们可以看看 AOP 的定义:面向切面编程,核心原理是使用动态代理模式在方法执行前后或出现异常时加入相关逻辑。
通过定义和前面代码我们可以发现3点:
AOP 是基于动态代理模式。
AOP 是方法级别的。
AOP 可以分离业务代码和关注点代码(重复代码),在执行业务代码时,动态的注入关注点代码。切面就是关注点代码形成的类。
Spring AOP
前文提到 JDK 代理和 CGLIB 代理两种动态代理。优秀的 Spring 框架把两种方式在底层都集成了进去,我们无需担心自己去实现动态生成代理。那么,Spring是如何生成代理对象的?
创建容器对象的时候,根据切入点表达式拦截的类,生成代理对象。
如果目标对象有实现接口,使用 JDK 代理。如果目标对象没有实现接口,则使用 CGLIB 代理。然后从容器获取代理后的对象,在运行期植入“切面”类的方法。通过查看 Spring 源码,我们在 DefaultAopProxyFactory 类中,找到这样一段话。
简单的从字面意思看出:如果有接口,则使用 JDK 代理,反之使用 CGLIB ,这刚好印证了前文所阐述的内容。Spring AOP 综合两种代理方式的使用前提有会如下结论:如果目标类没有实现接口,且 class 为 final 修饰的,则不能进行 Spring AOP 编程!
知道了原理,现在我们将自己手动实现 Spring 的 AOP:
package com.ape.aop;
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: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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- dao实例加入容器 -->
<bean id="userDao" class="com.ape.aop.UserDao"></bean>
<!-- dao实例加入容器 -->
<bean id="orderDao" class="com.ape.aop.OrderDao"></bean>
<!-- 实例化切面类 -->
<bean id="transactionAop" class="com.ape.aop.TransactionAop"></bean>
<!-- Aop相关配置 -->
<aop:config>
<!-- 切入点表达式定义 -->
<aop:pointcut expression="execution(* com.ape.aop.*Dao.*(..))" id="transactionPointcut"/>
<!-- 切面配置 -->
<aop:aspect ref="transactionAop">
<!-- 【环绕通知】 -->
<aop:around method="arroud" pointcut-ref="transactionPointcut"/>
<!-- 【前置通知】 在目标方法之前执行 -->
<aop:before method="beginTransaction" pointcut-ref="transactionPointcut" />
<!-- 【后置通知】 -->
<aop:after method="commit" pointcut-ref="transactionPointcut"/>
<!-- 【返回后通知】 -->
<aop:after-returning method="afterReturing" pointcut-ref="transactionPointcut"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="transactionPointcut"/>
</aop:aspect>
</aop:config>
</beans>
package com.ape.test;
import com.ape.aop.IUserDao;
import com.ape.aop.OrderDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class Test03 {
// private ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml",getClass());
@Autowired
public IUserDao userDao;
@Autowired
public OrderDao orderDao;
@Test
public void testProxy(){
//SpringIOC容器中获取对象,测试springJdk中动态代理方式
System.out.println(userDao.getClass());
userDao.save();
}
@Test
public void testCglib(){
//SpringIOC容器中获取对象,测试springCglib中动态代理方式
System.out.println(orderDao.getClass());
orderDao.save();
}
}