最近阅读 Hadoop 的源码,看到各种动态代理,打算学习下。虽然以前也学过,但是感觉就是死记硬背的代码,有些地方根本不懂。温习了一遍网上 JDK 动态代理的教程后,自己尝试再一次死记硬背的写一写,结果错误百出。于是打算狠下心来系统的学习下动态代理。
在这篇博客中我不会具体的说明动态代理的实现,但是我会贴上我参考的博客供大家参考。
疑问
如果你和我一样,JAVA 基础不好,初次接触 JDK 动态代理,你一定会有下面的几个疑问。
我先贴上我的示例代码:
package org;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Person {
void say();
}
class PersonImpl implements Person {
public void say() {
System.out.println("Hello World!");
}
}
class CustomInvocation implements InvocationHandler {
private Object target;
public CustomInvocation(Object obj) {
this.target = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Q3
System.out.println("before invoke");
Object result = method.invoke(target, args);
System.out.println("after invoke");
return result;
}
}
public class Main {
public static void main(String[] args) throws Exception {
Person person = new PersonImpl();
// Q1
Person proxyPerson1 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), person.getClass().getInterfaces(), new CustomInvocation(person));
Person proxyPerson2 = (Person) Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), new CustomInvocation(person));
Person proxyPerson3 = (Person) Proxy.newProxyInstance(Main.class.getClassLoader(), person.getClass().getInterfaces(), new CustomInvocation(person));
// Q2
PersonImpl proxyPerson4 = (PersonImpl) Proxy.newProxyInstance(Person.class.getClassLoader(), person.getClass().getInterfaces(), new CustomInvocation(person));
Person proxyPerson5 = (PersonImpl) Proxy.newProxyInstance(Person.class.getClassLoader(), person.getClass().getInterfaces(), new CustomInvocation(person));
Person proxyPerson6 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), Person.class.getInterfaces(), new CustomInvocation(person));
Person proxyPerson7 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), PersonImpl.class.getInterfaces(), new CustomInvocation(person));
Person proxyPerson8 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, new CustomInvocation(person));
}
}
Q1:ClassLoader 之谜,仿佛无论哪个 ClassLoader 都不报错?
Q2:$Proxy0 cannot be cast to xxxx
,我怎么动不动就报这个这个错!
Q3:InvocationHandler
中的 invoke 方法中的第一个参数 Object proxy 究竟是何方神圣?
Q1 到底填哪个ClassLoader ?
在理清应该填哪个 ClassLoader 之前,我们先理清 a.getClass()
和 A.class
的区别。
a.getClass()
返回运行过程中 a 的 class 类型。比如你有A a = new B();
然后执行a.getClass()
会返回B
。A.class
则是静态的认为是A
。
例如下面这个函数判断一个类的包名是不是默认的,我们需要使用 .getClass()
进行判断。
boolean isDeclaredInDefaultPkg(Object o) {
// 在这里,o 的类型是动态的,我不可能提前预知,所以使用 getClass()。
return o.getClass().getPackage() == null;
}
再比如这个,我们明确的知道我们需要的类是 DayOfWeek.class
,就可以这么写
Set<DayOfWeek> allDays = EnumSet.allOf(DayOfWeek.class);
参考来源:https://programming.guide/java/difference-between-class-and-getclass.html。
回到 Q1,所以我们可以推断出,其实在这里,Person.class
和 person.getClass()
都是可以的。但是如果我们要写一个通用性的动态代理,肯定还是用 getClass()
的办法,毕竟我们不可能提前知道我们要动态代理哪一个类。
接下来我们看看 ClassLoader,到底应该是哪个类的 ClassLoader 呢?事实上,随便你哪个类,都可以,反正都是 AppClassLoader。不信你可以打印看看。
System.out.println(Person.class.getClassLoader());
System.out.println(person.getClass().getClassLoader());
System.out.println(Main.class.getClassLoader());
// 输出结果:
// sun.misc.Launcher$AppClassLoader@18b4aac2
// sun.misc.Launcher$AppClassLoader@18b4aac2
// sun.misc.Launcher$AppClassLoader@18b4aac2
至于为什么,推荐你去看看 https://blog.csdn.net/briblue/article/details/54973413 。
Q2 $Proxy0 cannot be cast to xxxx
这个问题就是我自己尝试写动态代理时最常遇到的问题。出现这个问题意味着你动态代理里面的操作姿势不对。
PersonImpl proxyPerson4 = (PersonImpl) Proxy.newProxyInstance(Person.class.getClassLoader(), person.getClass().getInterfaces(), new CustomInvocation(person));
Person proxyPerson5 = (PersonImpl) Proxy.newProxyInstance(Person.class.getClassLoader(), person.getClass().getInterfaces(), new CustomInvocation(person));
Person proxyPerson6 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), Person.class.getInterfaces(), new CustomInvocation(person));
Person proxyPerson7 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), PersonImpl.class.getInterfaces(), new CustomInvocation(person));
Person proxyPerson8 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, new CustomInvocation(person));
上面所有的示例中,只有 proxyPerson7 和 proxyPerson8 是正确的。
proxyPerson4 错在我转型成了 PersonImpl,因为 JDK 动态代理构造的时候,是根据我传入的 interface 进行构造的,而不是 interface 的实现类,而 PersonImpl 是一个实现类。其实这个操作其实有点类似于把一个 interface 强制转换成一个他的实现类,这怎么可能呢?
proxyPerson5 犯得和 proxyPerson4 是一样的错误。
proxyPerson6 是错在 Proxy.newProxyInstance(xxx)
的第二个参数传入的是 Person 的 interface,问题是 Person 的 interface 是空的,Person 自己本身并没有实现哪一个接口,所以这里返回的是 null,所以构造出来的代理类肯定也是有问题的。
proxyPerson7 和 proxyPerson8 其实就是变了一下写法,两个实质上是一模一样的,
Q3 Invoke 方法中那个 Object proxy 是谁?
public Object invoke(Object proxy, Method method, Object[] args)
,据我们所知,如果要实现动态代理,我们需要实现自己的 InvocationHandler
类。在 InvocationHandler
中,我们需要重写原来的 invoke 方法。最初我有一个疑问,就是 invoke 方法中第一个参数 Object proxy 是谁,因为感觉在动态代理中根本用不到。
经过查找资料,实际上那个 Object proxy 就是代理后的类。下面给大家贴段代码:
public final void sayHello() throws {
try {
this.h.invoke(this, m3, null);
return;
} catch (RuntimeException localRuntimeException) {
throw localRuntimeException;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
这段代码就是 JDK 动态代理生成后的类,sayHello() 就是被代理类中的一个方法,其中 this.h
就是我们自己实现的 InvocationHandler
,我们看到第一个参数传递的是 this,也就是自己(通过动态代理生成后的类)。
更多具体信息可看这篇文章:https://blog.csdn.net/yhl_jxy/article/details/80586785。
原创文章,作者:Smith,如若转载,请注明出处:https://www.inlighting.org/archives/jdk-dynamic-proxy