先提一句:
静态代理 vs 动态代理
1.对比表格
特性 | 静态代理 | JDK动态代理 |
---|---|---|
实现时机 | 编译时 | 运行时 |
代码生成 | 手动编写代理类 | 自动生成代理类 |
接口要求 | 需要为每个类创建代理 | 基于接口,自动实现 |
方法处理 | 显式调用目标方法 | 通过InvocationHandler统一处理 |
可维护性 | 修改频繁时代码量大 | 修改只需调整InvocationHandler |
性能 | 直接调用,性能较好 | 反射调用,有一定性能开销 |
适用场景 | 代理类少、方法固定的情况 | 需要灵活代理多个接口的情况 |
2.在 Java 的动态代理机制里,除了代理对象,还涉及到接口、目标对象(被代理对象)以及调用处理器(InvocationHandler
)。下面为你详细介绍它们各自的作用以及相互之间的关系。
各身份的作用
接口(Interface)
接口定义了一系列方法的签名,它规定了代理对象和目标对象需要实现的行为。在 JDK 动态代理中,代理对象和目标对象都要实现相同的接口,这是动态代理机制的基础。因为 JDK 动态代理是基于接口的,代理对象实际上是实现了指定接口的类的实例。
目标对象(Target Object)
目标对象即被代理的对象,它是实际执行具体业务逻辑的对象。代理对象会将方法调用转发给目标对象,从而让目标对象完成真正的工作。目标对象需要实现接口中定义的方法。
调用处理器(InvocationHandler)
InvocationHandler
是一个接口,它包含一个 invoke
方法。当调用代理对象的方法时,实际上会调用 InvocationHandler
的 invoke
方法。在 invoke
方法中,可以添加额外的逻辑,如日志记录、事务管理等,然后再将方法调用转发给目标对象。InvocationHandler
是实现 AOP(面向切面编程)的关键。
代理对象(Proxy Object)
代理对象是由 Proxy.newProxyInstance
方法动态生成的对象,它实现了指定的接口。代理对象并不实际执行具体的业务逻辑,而是将方法调用委托给 InvocationHandler
处理。代理对象的存在使得我们可以在不修改目标对象代码的情况下,对目标对象的方法进行增强。
3.它们之间的关系
- 目标对象与接口:目标对象
MyTargetObject
实现了MyInterface
接口,这表明它具备了接口所定义的行为。- 调用处理器与目标对象:调用处理器
MyInvocationHandler
持有目标对象的引用,这样在invoke
方法中就可以调用目标对象的方法。- 代理对象与接口:代理对象实现了
MyInterface
接口,因此可以将其转换为该接口类型。- 代理对象与调用处理器:在创建代理对象时,需要传入调用处理器,当调用代理对象的方法时,会自动调用调用处理器的
invoke
方法。
这些身份通过接口和调用处理器相互关联,共同构成了 Java 动态代理机制,使得我们可以在不修改目标对象代码的情况下,对其方法进行增强。
引言:一个类型转换的谜题
在Java开发中,使用JDK动态代理时,我们经常会看到这样的代码:
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(...);
而不是:
MyInterfaceImpl proxy = (MyInterfaceImpl) Proxy.newProxyInstance(...);
这看似简单的类型转换背后,其实蕴含着Java动态代理的核心设计思想。本文将带你深入探究这一现象的原因,并说说JDK动态代理的运作机制。
第一部分:JDK动态代理基础
1.1 创建JDK动态代理的标准流程
创建JDK动态代理对象通常需要以下步骤:
定义接口:定义一个或多个接口,代理对象将实现这些接口
实现接口:创建实现这些接口的具体类
实现InvocationHandler接口:该接口中的invoke方法会在代理对象的方法被调用时执行
使用Proxy.newProxyInstance方法创建代理对象
我现在通过一个完整示例代码来演示这个过程:
import java.lang.reflect.*;
// 定义接口
interface MyInterface {
void doSomething();
}
// 实现类
class MyInterfaceImpl implements MyInterface {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
}
// 调用处理器
class MyInvocationHandler implements InvocationHandler {
private final Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method call");
Object result = method.invoke(target, args);
System.out.println("After method call");
return result;
}
}
public class Main {
public static void main(String[] args) {
MyInterface target = new MyInterfaceImpl();
MyInvocationHandler handler = new MyInvocationHandler(target);
// 创建代理对象
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class<?>[]{MyInterface.class},
handler
);
proxy.doSomething();
}
}
1.2 关键观察点
注意:Proxy.newProxyInstance
方法的返回值被强制转换为了MyInterface
接口类型,而不是MyInterfaceImpl
实现类类型。这是理解JDK动态代理的关键起点。
第二部分:为什么是接口类型?
2.1 代理对象的本质
JDK动态代理机制是基于接口的,它会生成一个实现了指定接口的代理类。具体来说:
-
代理类在运行时动态生成
-
它实现了我们在
newProxyInstance
方法中指定的所有接口 -
但它不继承自目标实现类
从JVM的角度来看,代理对象与目标实现类没有任何继承关系,因此无法将代理对象转换为实现类类型。
2.2 面向接口编程的优势
这种设计符合面向接口编程的重要原则:
-
降低耦合:调用者只需知道接口,无需关心具体实现
-
提高扩展性:可以随时替换实现类而不影响调用方代码
-
增强抽象性:关注"做什么"而非"怎么做"
2.3 Java单继承的限制
从技术实现层面看:
-
JDK动态代理生成的代理类已经继承自
java.lang.reflect.Proxy
类 -
Java不支持多重继承
-
因此代理类无法再继承目标实现类
这从根本上决定了代理对象不能是目标实现类的实例。懂了吗?兄弟们
第三部分:深入代理机制
3.1 代理类的生成过程
当调用Proxy.newProxyInstance()
时,JVM会:
根据接口列表动态生成代理类的字节码
该类继承
Proxy
并实现指定接口每个方法调用都被路由到
InvocationHandler.invoke()
生成的代理类大致相当于:
public final class $Proxy0 extends Proxy implements MyInterface {
private InvocationHandler h;
public $Proxy0(InvocationHandler h) {
this.h = h;
}
public void doSomething() {
h.invoke(this,
MyInterface.class.getMethod("doSomething"),
null);
}
}
3.2 类型系统验证
Java的类型安全机制确保:
-
代理对象是接口类型的实例
-
但不是实现类类型的实例
-
转型检查在运行时严格执行
因此以下代码会抛出ClassCastException
:
MyInterfaceImpl impl = (MyInterfaceImpl) proxy; // 运行时错误
第四部分:实际应用场景
4.1 Spring框架中的应用
Spring AOP默认使用JDK动态代理(当目标对象实现接口时)。例如:
@Service
public class UserServiceImpl implements UserService {
@Transactional
public void createUser(User user) {
// 业务逻辑
}
}
Spring会创建一个实现了UserService
的代理对象,处理@Transactional
等切面逻辑。
第五部分:限制与替代方案
5.1 JDK动态代理的限制
只能代理接口方法
性能开销(反射调用)
无法代理final类和方法
5.2 CGLIB代理
对于没有实现接口的类,可以使用CGLIB:
-
通过继承目标类生成子类
-
重写方法实现代理
Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(MyConcreteClass.class); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // 增强逻辑 return proxy.invokeSuper(obj, args); } }); MyConcreteClass proxy = (MyConcreteClass) enhancer.create();
-
需要处理final方法的特殊情况
第六部分:最佳实践
-
始终使用接口类型引用代理对象
-
考虑缓存代理实例(创建代理有一定开销)
-
明确代理边界(不要在代理对象上调用非接口方法)
-
合理使用组合(当需要代理类和接口时)
public class UserServiceProxy implements UserService {
private final UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void createUser(User user) {
// 前置处理
target.createUser(user);
// 后置处理
}
}
JDK动态代理将返回值强制转换为接口类型而非实现类类型,这一设计:
符合代理对象的技术实现(实现接口但不继承实现类)
遵循面向接口编程的最佳实践
受限于Java的单继承模型
提供了灵活性和扩展性
拜拜