JDK 动态代理(Java Dynamic Proxy)是 Java 标准库提供的一种代理模式实现,主要用于在运行时动态地创建接口的代理实例。动态代理可以用于拦截和修改方法调用,常用于实现 AOP(面向切面编程)、中间件、测试框架等。
JDK 动态代理主要涉及两个核心类:
-
java.lang.reflect.Proxy
:这是一个用于创建代理实例的工具类。它提供了一个newProxyInstance()
静态方法,用于创建一个给定接口的代理实例。 -
java.lang.reflect.InvocationHandler
:这是一个接口,其实现类需要提供invoke()
方法。当代理实例上的方法被调用时,invoke()
方法会被执行。在invoke()
方法中,可以自定义如何处理方法调用,通过反射来调用目标对象的实际方法,以及在方法调用前后添加其他操作(如日志、安全检查等)。
举个简单的例子:
//定义一个接口
public interface MyInterface {
String sayHi(String para);
}
//实现接口
public class MyClass implements MyInterface{
@Override
public String sayHi(String para) {
System.out.println("Hi,"+para);
return "OK";
}
}
//实现handler的代码增强逻辑
public class MyClassProxy implements InvocationHandler {
private Object target;
private void before(){
System.out.println("Before invoke method...");
}
private void after(){
System.out.println("After invoke method...");
}
public MyClassProxy(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(proxy instanceof MyInterface){
System.out.println(proxy.getClass() + "is instance of MyInterface");
}
before();
Object result = method.invoke(target,args);
after();
return result;
}
}
//测试类
public class JDKProxyTest {
public static void main(String[] args) {
MyClass target = new MyClass();
InvocationHandler handler = new MyClassProxy(target);
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),handler);
proxy.sayHi("Java");
}
}
输出:
class com.sun.proxy.$Proxy0is instance of MyInterface
Before invoke method...
Hi,Java
After invoke method...
几个问题:
1.为什么代理类和目标类使用相同的类加载器?
在 JDK 动态代理中,代理类和目标类使用相同的类加载器有以下原因:
-
兼容性:确保代理类和目标类在相同的类路径下被加载,可以避免类加载器之间的不兼容性问题。类加载器之间可能存在不同的类路径和委托模式,如果代理类和目标类使用不同的类加载器,可能导致代理类无法找到目标类或目标类的依赖。
-
类型安全:当代理类和目标类使用相同的类加载器时,可以确保它们实现的接口是相同的。这意味着代理类可以安全地替换目标类,而不会引发类型转换异常(如
ClassCastException
)。这对于在运行时修改或拦截方法调用至关重要。 -
简化实现:通过重用目标类的类加载器和接口列表,可以简化 JDK 动态代理的实现。例如,
Proxy.newProxyInstance()
方法可以直接从目标类的类加载器和接口列表中获取必要的信息,无需手动创建或修改这些参数。这使得动态代理更加灵活和易于使用。
2.InvocationHandler的invoke方法的Object proxy参数的含义
InvocationHandler
接口的 invoke()
方法有三个参数:Object proxy
、Method method
和 Object[] args
。Object proxy
参数的含义如下:
这是代理实例本身。在 invoke()
方法中,你可以通过这个参数引用调用方法的代理对象。然而,在大多数情况下,你不需要使用此参数。实际上,在 invoke()
方法内部调用 proxy
上的方法会导致递归调用 invoke()
方法,从而引发栈溢出错误。通常,在 invoke()
方法中,你应该调用目标对象(而非代理对象)上的方法,并在方法调用前后添加其他操作(如日志、安全检查等)。从上文的代码输出来看,proxy就是代理类本身。
3.为什么参数要传Object proxy?
在 InvocationHandler
的 invoke()
方法中,传递 Object proxy
参数的目的主要是为了提供更多的上下文信息和灵活性。虽然在大多数情况下,你可能不需要使用这个参数,但在某些特定场景下,它可能会派上用场。以下是一些使用 Object proxy
参数的可能场景:
-
代理对象的引用:有时候,你可能需要在
invoke()
方法中获取对代理对象的引用。例如,你可能需要将代理对象传递给其他方法或组件,以便在后续操作中继续使用代理而不是目标对象。 -
多个代理对象:如果
InvocationHandler
实例需要处理多个代理对象,你可以使用Object proxy
参数来区分它们。这样,你可以在invoke()
方法中根据不同的代理对象执行不同的操作。 -
自定义行为:在某些情况下,你可能需要根据代理对象的特性或状态来自定义
invoke()
方法的行为。例如,你可以在代理对象上添加自定义注解或元数据,并在invoke()
方法中检查这些信息以决定如何处理方法调用。
然而,正如先前提到的,你应该避免在 invoke()
方法中直接调用 proxy
上的方法,因为这会导致无限递归和栈溢出错误。在大多数情况下,你应该在 invoke()
方法中调用目标对象上的方法,而不是代理对象上的方法。
总之,虽然 Object proxy
参数在许多场景下可能用不到,但它的存在仍然为 InvocationHandler
提供了更多的上下文信息和灵活性。在实际使用中,你可以根据具体需求和场景决定是否使用 Object proxy
参数。