- 代理类必须是从接口实现的
代理后的匿名类(动态生成)的继承Proxy,实现代理的接口
为什么说是动态呢?
- 这个匿名类是在运行的时候通过jvm的字节码编辑器生产的类,所以为动态
在 JDK 中,动态代理是通过 java.lang.reflect.Proxy
类实现的。实际上,当您使用 newProxyInstance()
方法创建一个动态代理实例时,您需要提供一个 InvocationHandler
对象作为参数。 InvocationHandler
是一个接口,它包含一个方法 invoke()
,用于处理代理实例上的方法调用。当代理对象上的方法被调用时,invoke()
方法将被调用,并传入代理对象、方法对应的 Method
对象以及传递给该方法的参数。然后,invoke()
方法可以执行任意自定义逻辑,并返回一个结果对象,作为代理方法的返回值。
在 JDK 动态代理中,InvocationHandler
的作用非常重要,因为它使得我们能够在运行时动态地生成代理类,并在调用代理对象方法时进行前置或后置操作,例如记录日志、授权、缓存等。 简而言之,InvocationHandler
是 JDK 动态代理机制的核心,它允许我们在运行时动态地生成代理类,并且可以对代理对象上的方法调用进行拦截和处理。
引用知乎
InvocationHandler 里面的invoke是怎么实现被调用的?
InvocationHandler
接口只有一个方法:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
其中,invoke()
方法的参数含义如下:
Object proxy
:代理类实例对象。Method method
:被代理的方法对象。Object[] args
:方法调用时所传递的参数。
在动态代理中,当客户端通过代理对象调用一个方法时,这个方法实际上会委托给 InvocationHandler
中的 invoke()
方法来处理。因此,invoke()
方法是动态代理的核心。
在 invoke()
方法中,我们可以根据需要进行任何自定义操作,例如记录日志、鉴权、缓存等。最后,再通过反射机制调用目标对象的方法并返回结果。
以下是简单的示例代码,展示了如何从 invoke()
方法中调用目标对象的方法:
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在执行目标对象的方法之前,可以进行一些自定义操作
Object result = method.invoke(target, args);
// 在执行目标对象的方法之后,可以进行一些自定义操作
return result;
}
}
在上面的示例代码中,MyInvocationHandler
实现了 InvocationHandler
接口,并通过构造函数传入了目标对象。在 invoke()
方法中,首先可以进行一些自定义操作,然后使用反射机制调用目标对象的方法,并将结果返回给客户端。最后,也可以再进行一些自定义操作。
这种委托是怎么实现的,委托是一种怎么样的设计模式?
委托(Delegation)是一种常见的设计模式,也被称为代理模式。
在这个模式中,一个对象(即委托对象)将一些任务委托给另一个对象(即代理对象)来处理。代理对象与委托对象具有相同的接口,因此客户端可以通过代理对象调用相应的方法,而不必关心实际执行任务的对象是谁。
代理模式有多种实现形式,如静态代理、动态代理等。不同的实现方式都有不同的优缺点和适用场景。
在静态代理中,代理类和委托类都是在编译期间确定的,因此通常需要手动编写代理类代码。而在动态代理中,代理类是在运行时动态生成的,因此更加灵活,但是也需要使用反射机制,可能会对性能产生一定影响。
总之,委托/代理模式是一种非常常见的设计模式,在软件开发中经常会用到。它可以提高代码的可复用性和灵活性,并且可以方便地实现一些横切关注点的功能,例如日志记录、安全检查、事务管理等。
那jdk动态代理中的委托是怎么实现的,可以用源码解释一下吗?
下面是 Proxy.newProxyInstance()
方法的源码:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
在这个方法中,JDK 首先会检查传入的 InvocationHandler
对象是否为 null
,如果为 null
则会抛出异常。然后,JDK 会检查代理类的访问权限,如果代理类不是公共类,则需要使用特权访问来设置其可访问性。
接着,JDK 会调用 getProxyClass0()
方法来获取或生成代理类。在这个方法中,JDK 首先会检查缓存中是否已经存在对应的代理类,如果已经存在则直接返回。否则,JDK 会动态生成一个新的代理类,并将其字节码加载到 JVM 中。
最后,JDK 获取代理类的构造函数,并调用这个构造函数来创建代理对象。在构造函数中,传入的参数就是 InvocationHandler
对象,因此我们可以在 invoke()
方法中进一步处理客户端的方法调用,并最终委托给目标对象完成任务。
代理类的构造函数实际上是由 Proxy.getProxyClass()
方法动态生成的,它会继承自 java.lang.reflect.Proxy
类,同时实现了客户端传入的一组接口类型。下面是这个构造函数的源码:
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
在这个构造函数中,我们可以看到只有一个参数 h
,这个参数就是客户端传入的 InvocationHandler
对象。当客户端调用代理对象的方法时,JVM 实际上会将该方法调用转发给代理类的 invoke()
方法,在 invoke()
方法中我们可以执行任意自定义操作,并最终委托给目标对象完成任务。
需要注意的是,这个构造函数是受保护的,因此我们无法直接使用它来创建代理对象。相反,我们必须使用 JDK 提供的 Proxy.newProxyInstance()
方法来创建代理对象,这个方法会根据客户端传入的参数动态生成一个新的代理类,并创建其实例。
当客户端调用代理对象的方法时,JVM 是怎么将该方法调用转发给代理类的 invoke() 方法的?
当客户端调用代理对象的方法时,JVM 实际上会将该方法调用转发给代理类的 invoke()
方法。这是因为当客户端调用代理对象的方法时,JVM 实际上会将该方法调用转换为一个 Method
对象,并将其作为参数传递给代理类的 invoke()
方法。
具体来说,JVM 会在代理类中实现被调用方法所在接口类型中声明的所有方法,并将这些方法转换为 Method
对象。然后,JVM 将需要调用的方法名、参数列表等信息封装为一个 Method
对象,并将其作为参数传递给代理类的 invoke()
方法。
在 invoke()
方法中,我们可以通过反射机制获取到这个 Method
对象,并进一步处理它。例如,我们可以根据方法名和参数类型等信息动态生成方法调用所需的参数列表,并最终委托给目标对象完成任务。
当客户端调用代理对象的方法时,JVM 会将该方法调用转换为一个 Method
对象。下面是这个过程的源码:
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
try {
// 首先,检查是否是 Object 类中的方法,如果是,则直接调用
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
// 然后,调用 InvocationHandler 对象的 invoke() 方法进行处理
return handler.invoke(proxy, method, args);
} catch (Throwable t) {
throw t;
}
}
在这个方法中,首先会检查当前调用的方法是否来自 Object
类,如果是,则直接调用该方法。否则,调用传入的 InvocationHandler
对象的 invoke()
方法进行处理。
在 invoke()
方法中,我们可以通过反射机制获取到要调用的方法的名称和参数类型等信息,并使用这些信息来查找与之匹配的方法。如果找到了匹配的方法,则返回该方法的 Method
对象;否则抛出 NoSuchMethodException
异常。
需要注意的是,由于代理对象的方法调用是在运行时进行的动态操作,因此 JVM 在每次调用代理对象的方法时都会执行这个过程,以确定要调用的方法。
当客户端调用代理对象的方法时,JVM 会将该方法调用转换为一个 Method 对象,对应的字节码是怎么样的?
以下是 Proxy.newProxyInstance()
方法生成的字节码,其中包含了代理对象的类定义和 invoke()
方法的定义:
// 代理对象类的定义
class com.sun.proxy.$Proxy0 implements MyInterface {
private InvocationHandler handler;
public $Proxy0(InvocationHandler handler) { this.handler = handler; }
public void doSomething() throws ...
// 其他接口方法...
public final void equals(...) throws ...
public final String toString() throws ...
public final int hashCode() throws ...
}
从上面的代码可以看出,Proxy.newProxyInstance()
方法会动态生成一个新的代理类,并在其中实现指定的接口类型。在代理类中,每个接口方法都被转换为一个对 invoke()
方法的调用,以便在方法调用时委托给 MyInvocationHandler.invoke()
方法进行处理。
当代理对象的方法被调用时,JVM 会将该方法调用转换为对 InvocationHandler.invoke(Object proxy, Method method, Object[] args)
方法的调用。在 JVM 字节码中,这个过程通常是通过 invokespecial
或 invokeinterface
指令来实现的。
下面是一个简单的示例代码,演示了代理对象方法调用转换为 invoke()
方法调用的字节码:
public class Test {
public static void main(String[] args) {
// 创建一个实现了 MyInterface 接口的代理类
MyInterface myProxy = (MyInterface) Proxy.newProxyInstance(
Test.class.getClassLoader(),
new Class<?>[]{MyInterface.class},
new MyInvocationHandler()
);
// 调用代理对象的方法
myProxy.doSomething();
}
public interface MyInterface {
void doSomething();
}
public static class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 在这里根据需要进行处理
return null;
}
}
}
在这个示例中,当执行 myProxy.doSomething()
方法时,JVM 会将其转换为以下字节码指令:
// 将代理对象压入操作数栈
aload_1
// 获取要调用的方法名和参数类型等信息,并创建 Method 对象
ldc "doSomething"
iconst_0
anewarray java/lang/Class
dup
iconst_0
ldc #1
aastore
invokevirtual java/lang/Class.getDeclaredMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
// 将代理对象、Method 对象以及方法参数列表压入操作数栈
aload_0
aload_2
aload_3
// 调用 InvocationHandler.invoke(Object proxy, Method method, Object[] args) 方法
invokespecial MyInvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
areturn
从上述代码可以看出,JVM 在将代理对象的方法调用转换为 invoke()
方法调用时,需要通过反射机制获取到要调用的方法的名称和参数类型等信息,并创建一个 Method
对象。然后,在调用 invoke()
方法时,将代理对象、Method
对象以及方法参数列表作为参数传递给该方法。
需要注意的是,由于 JVM 字节码指令的具体实现方式可能会因不同的 JVM 实现而有所差异,因此上述代码仅供参考,实际情况可能会有所不同。
这个invoke方法是哪个类下的方法?
这个 invoke()
方法是在实现了 java.lang.reflect.InvocationHandler
接口的类中定义的,它用于处理客户端对代理对象的方法调用。具体来说,在使用动态代理时,我们需要实现一个自定义的 InvocationHandler
类,并在其中重写 invoke()
方法。
下面是一个自定义的 InvocationHandler
类的示例代码,它演示了如何在 invoke()
方法中处理客户端的方法调用:
public class MyInvocationHandler implements InvocationHandler {
private 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 invoking " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After invoking " + method.getName());
return result;
}
}
在这个示例中,我们通过构造函数将目标对象传入自定义的 MyInvocationHandler
类中,并在 invoke()
方法中对方法调用进行处理。在这个例子中,我们简单地在方法调用前后输出日志信息。
需要注意的是,Proxy.newProxyInstance()
方法会根据客户端传入的参数动态生成一个新的代理类,并创建其实例。在这个过程中,我们需要将自定义的 InvocationHandler
对象传递给 newProxyInstance()
方法,以便在代理对象上处理客户端的方法调用。
最后附上一个手动实现动态代理以及方法调用的案例
JVM 实现动态代理的过程比较复杂,涉及到大量的字节码生成和类加载等底层操作。以下是一个简单的示例代码,演示了如何在 JVM 中手动实现动态代理以及方法调用的过程:
public class ProxyGenerator {
public static void main(String[] args) throws Exception {
// 定义要实现的接口类型
Class<?>[] interfaces = new Class<?>[]{MyInterface.class};
// 生成代理类的字节码,并使用自定义的类加载器加载该类
byte[] bytes = ProxyGenerator.generateProxyClass("MyProxy", interfaces);
ClassLoader loader = new MyClassLoader();
Class<?> clazz = loader.defineClass("MyProxy", bytes, 0, bytes.length);
// 创建代理对象并完成方法调用
MyInterface proxy = (MyInterface) clazz.getConstructor(InvocationHandler.class)
.newInstance(new MyInvocationHandler());
proxy.doSomething();
}
public interface MyInterface {
void doSomething();
}
public static class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("Before invoking " + method.getName());
Object result = method.invoke(this, args);
System.out.println("After invoking " + method.getName());
return result;
}
}
public static class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 在这里从本地或远程文件系统中读取指定名称的类字节码,并返回相应的 Class 对象
// 这里为了演示方便,直接返回 null
return null;
}
}
}