上一篇中,分析了静态代理和动态代理的区别,还留有一些问题,最核心的就是,为什么JDK动态代理必须要有一个接口,还有就是,定义一个InvocationHandler,并用Proxy.newProxyInstance调用,怎么就可以实现动态代理效果。
想要知道整个调用过程,就得深入源码去看了。先说一下,本来想一篇文章写完的,但是发现遇到了一个知识盲点:weakCache类以及它所采用的弱引用相关理论,而且1.8和1.6的实现逻辑也不一样。就决定这篇先略过weakCache,晚点再写。
先看最Proxy.newProxyInstance方法,就不贴代码了,里面逻辑很简单,画了一个流程图,标红的是核心代码。可以看到这个方法其实就是一个入口方法,用来封装各种操作的。
getProxyClass0这个方法也很简单,调用proxyClassCache.get(loader, interfaces)方法进入到WeakCache类中,在这里进行从缓存中取代理类,如果取不到则生成的功能。而proxyClassCache就是WeakCache的实例。
简单说一下,proxyClassCache类的初始化就包含了两个工厂方法工厂方法,它内部使用了lambda表达式的BiFunction接口进行了调用。
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
ProxyClassFactory类是Proxy的内部类,主要作用就是生成代理类的。里面包含了两个变量和一个方法,这个方法就是实现了BiFunction的apply,用于lambda调用。两个变量是用来给代理类起名字用的,一个是标明类前缀:$Proxy,另一个是原子类AtomicLong,用于累加计数用。两个合起来,就是我们在mybatis中经常看到的$Proxy145之类的日志,这个就是调用的类名。
主要来看apply方法,将大部分英文注释翻译成中文了:
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
//用IdentityHashMap存储代理类,和hashMap的主要区别是他的键可以重复(值可以相同,但是对象地址不同)
//因为在JVM中,所有的对象都是独立的,哪怕是同一个类产生的
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
Class<?> interfaceClass = null;
try {
//通过接口名反射获取接口类,但是不进行实例化。
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
//判断指定的类加载器(loader)加载接口所得到的Class对象(interfaceClass)是否与intf对象相同
//没懂这句话的目的。在什么情况下,会出现不相等。
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
//判断该类是一个接口,所以这就是为什么jdk动态代理必须要有接口类的原因了
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
//判断该接口是否重复
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null; // 定义代理类将要生成的package路径
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
//判断入参传入的所有接口,是否都是public的,如果有一个非public类型的接口,那么
//判断所有接口的包名是否一致,如果不一致,则抛错。一致的话,包名就是这个非public接口的包名
//也就是说要么保证所有的接口都是public,要么保证所有的接口在同一个包下面
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// 如果所有接口都是public的,那么就使用 com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
//根据上面提到的两个变量,生成一个代理类名称
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//创建代理类
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
然后就是调用ProxyGenerator.generateProxyClass方法来生成代理类的class文件。这个类在sun.misc下,据说从1.8起oracle不再放出这个包下的代码。因为我是使用eclipse中的反编译打开的,里面有一堆??,看着有点费事,而且我觉得这个太底层了,也没必要研究这么细,所以源码就不贴了,简单说一下个人理解吧。
就是将生成每个类固有的hashcode,equals,toString等方法,加上接口中定义的方法,以及构造方法等一个类所应该给具有的信息按照字节码的规范生成在内存中。
等返回这个类的对象后,会调用defineClass0这个本地方法(native)去生成对应的实例,最终返回给代理类对象进行调用。