1、核心术语介绍
AOP 切面编程核心术语如下:
术语 | 含义 |
---|---|
目标(Target) | 被通知的对象 |
代理(Proxy) | 向目标对象应用通知之后创建的代理对象 |
连接点(JoinPoint) | 目标对象的所属类中,定义的所有方法均为连接点 |
切入点(Pointcut) | 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点) |
通知(Advice) | 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情 |
切面(Aspect) | 切入点(Pointcut)+通知(Advice) |
Weaving(织入) | 将通知应用到目标对象,进而生成代理对象的过程动作 |
上述描述比较抽象,上述术语间的关系如下图所示:
2、spring 实现方式
对于有接口的bean,采用JDK Proxy去创建代理对象。对于没有实现接口的对象,使用 Cglib 生成一个被代理对象的子类来作为代理。
2.1 JDK Proxy
采用java反射机制实现。一个简单的示例如下所示:
// 定义接口
public interface TenantService {
void doSomething(String tenantId);
}
// 定义实现
public class TenantServiceImpl implements TenantService {
@Override
public void doSomething(String tenantId) {
System.out.println("Doing something for tenant: " + tenantId);
}
}
public class JdkProxyExample {
public static void main(String[] args) {
// 创建真实对象
TenantService realTenantService = new TenantServiceImpl();
// 创建 InvocationHandler
InvocationHandler handler = new InvocationHandler() {
private final TenantService tenantService = realTenantService;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String tenantId = (String) args[0];
System.out.println("Before method call - Tenant ID: " + tenantId);
// 调用真实对象的方法
method.invoke(tenantService, args);
System.out.println("After method call - Tenant ID: " + tenantId);
return null; // 无返回值方法返回 null
}
};
// 创建代理对象
TenantService proxyTenantService = (TenantService) Proxy.newProxyInstance(
TenantService.class.getClassLoader(),
new Class[]{TenantService.class},
handler
);
// 调用代理对象的方法
proxyTenantService.doSomething("tenant1");
}
}
2.2 Cglib
Cglib 通过字节码技术为现有类生成子类,并在子类中增强逻辑,从而实现AOP功能。
CGLIB 实现 AOP 的基本原理
- 生成子类: CGLIB 通过生成现有类的子类来实现增强逻辑。这意味着原始类的所有方法都会在子类中存在,并且可以在子类中添加新的方法或覆盖父类的方法。
- 方法拦截: 在子类中,CGLIB 可以通过拦截器(Interceptor)来控制方法的调用。拦截器可以在方法调用前后执行自定义逻辑。
- 动态代理: CGLIB 使用动态代理机制来创建增强的对象。这种代理机制允许在运行时动态地生成增强的类实例。
CGLIB 的核心组件
- Enhancer:负责生成增强类的工具类。
- MethodInterceptor:方法拦截器,用于在方法调用前后执行自定义逻辑。
- MethodProxy:方法代理,提供了对方法的代理调用。
代码示例:
// 业务实现,不实现接口
public class TargetClass {
public void doSomething() {
System.out.println("TargetClass doing something...");
}
}
// 拦截器,在方法执行前后织入逻辑
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method call");
Object result = proxy.invokeSuper(obj, args); // 调用原始方法
System.out.println("After method call");
return result;
}
}
public class CglibAopExample {
public static void main(String[] args) {
// 创建 Enhancer 实例
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(TargetClass.class);
// 设置方法拦截器
enhancer.setCallback(new MyMethodInterceptor());
// 创建增强对象
TargetClass enhancedObject = (TargetClass) enhancer.create();
// 调用增强对象的方法
enhancedObject.doSomething();
}
}
2.3 JDK Proxy VS Cglib
对2种方式的优缺点进行对比。
特性 |
JDK Proxy | CGLIB |
---|---|---|
适用范围 | 仅适用于实现了接口的类 | 适用于所有类(包括未实现接口的类) |
实现方式 | 通过 Proxy.newProxyInstance 方法创建代理对象 |
通过 Enhancer 类生成子类 |
性能 | 通常比 CGLIB 略慢 | 性能较好,因为避免了接口查找和反射调用 |
灵活性 | 需要实现接口,限制较大 | 更加灵活,可以代理任意类 |
兼容性 | 兼容性较好,广泛支持 | 兼容性良好,但在某些旧版本 JVM 上可能需要额外配置 |
代理类型 | 动态代理 | 动态代理 |
代码侵入性 | 低,只需要实现接口 | 较高,需要生成子类 |
调试难度 | 较低,因为不需要生成额外的类 | 较高,因为生成了额外的子类 |
扩展性 | 扩展性较好,可以通过接口进行扩展 | 扩展性一般,需要修改子类的生成逻辑 |
安全性 | 较好,因为基于接口 | 一般,因为涉及子类生成 |
应用场景 | 适合于需要代理实现了接口的类 | 适合于需要代理任意类,包括未实现接口的类 |
从上述分析来看,JDK proxy的方式并没有明显的优点,那么为什么spring 会选择JDK proxy作为默认aop实现呢?
我认为有以下几个原因:
- 兼容性:JDK proxy 是jdk自身的实现,兼容性更高
- 安全性:基于接口实现,安全性更高
- 历史原因:因为spring最开始就是选择的JDK proxy作为实现方式,一脉相承。