其实这篇文章已经写完很久了,但是最近沉迷于JUC源码,所以一直放在草稿箱没有发。这几天把AQS以及相关的子类撸了一遍,写了几篇JUC源码解读,准备发到公号上面来,于是乎决定把草稿箱的这篇文章先解决了
我上一篇文章已经通过代购的例子讨论了静态代理模式,也知道了它不满足开闭原则,扩展性差的缺点,因此,衍生出了动态代理。动态代理分两种,一种是基于接口的,也是jdk本身支持的,还有一种是基于类的,需引入第三方类cglib来实现,今天我们主要讨论基于接口的动态代理(看这篇之前建议看看我前一篇讲静态代理的文章,代购小哥发家史还是值得学习的)
一. 概述
其实动态代理的类之间的关系和静态代理是一样
JDK的动态代理类需要依靠两个类(接口)来实现,Proxy静态类,InvocationHandler接口。Proxy类的职责是产生代理Class对象与实例;InvocationHandle接口的职责是负责调用服务和增强服务的(这两个类与接口都严格的遵守了设计模式中的单一职责原则)
是不是有点懵逼,我们还是继续通过代购小哥来理解吧(不了解代购小哥发家史的,可以看看我公号文章:“通过代购理解:静态代理”)
二. 如何使用动态代理
代购小哥生意越做越大,一个人已经忙不过来了,所以他决定召集周围的代购同行,成立一个代购公司。以后来一个顾客,代购公司就分配一个代购小哥哥给他,这样,就可以满足更多妹子的买买买需求啦
这个代购公司的员工就是由Proxy静态类来生成,员工所需要执行的服务则是由实现InvocationHandler接口的类来确定的,我们看下代码吧。
首先我们建立包包商店和鞋鞋商店的接口与实现类
public interface IBagShop {
void buyBag(String num);
}
public class BagShop implements IBagShop {
@Override
public void buyBag(String money) {
System.out.println("买了一个价值"+money+"限量款包包!");
}
}
public interface IShoesShop {
void buyShoes(String num);
}
public class ShoesShop implements IShoesShop{
@Override
public void buyShoes(String money) {
System.out.println("买了一双价值" + money + "的乔丹!!");
}
}
然后我们再看看代理公司类咋写
public class PurchasingAgentCompany implements InvocationHandler {
private Object shop;
public void setShop(Object shop) {
this.shop = shop;
}
//通过Proxy获取动态代理的对象(代购小哥)
public Object getProxyInstance() {
return Proxy.newProxyInstance(shop.getClass().getClassLoader(), shop.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代购售前服务:砍价");
Object ret = method.invoke(shop, args);
System.out.println("代购售后服务:送小礼品");
return ret;
}
}
我们可以看到代购公司PurchasingAgentCompany类继承了InvocationHandler接口,并实现了其invoke方法来调用服务和增强服务
而getProxyInstance方法中使用了Prxoy静态类的newProxyInstance方法来获取代理对象
最后我们创建一个妹子类来找代购公司买东西
public class Girl {
public static void main(String[] args) {
// 包包商店 与 鞋鞋商店
IBagShop bagShop = new BagShop();
IShoesShop shoesShop = new ShoesShop();
// 成立一家代购公司
PurchasingAgentCompany company = new PurchasingAgentCompany();
// 一个顾客买包(所以代购公司需要引入实际的服务提供者:包包商店)
company.setShop(bagShop);
// 代购公司分配了一个包包商店的代购bagShopProxy
IBagShop bagShopProxy = (IBagShop) company.getProxyInstance();
// 代购买包包
bagShopProxy.buyBag("10万");
System.out.println("------------------------------------");
// 一个顾客买鞋子(所以代购公司需要引入实际的服务提供者:谢谢商店)
company.setShop(shoesShop);
// 代购公司分配了一个鞋鞋商店的代购shoesShopProxy
IShoesShop shoesShopProxy = (IShoesShop) company.getProxyInstance();
// 代购买鞋鞋
shoesShopProxy.buyShoes("1万");
}
}
我们可以看到,如果我们顾客还需要代购公司代购家具,我们只需要扩展家具接口与其实现类,妹子就可以直接通过代购公司类来购买了,而代购公司的代码不需要动一下。所以动态代理类很好的保证了开闭原则。我们看下输出结果
通过上面的例子与代码,相信大家已经知道如何通过Proxy静态类和InvocationHandler接口来实现动态代理了。
但大家现在一定还是会一脸懵逼,Proxy是如何创造出代理对象的呢?代理对象又是怎么通过实现InvocationHandler的invoke方法来调用服务呢?动态代理用起来是很简单的,但如何实现动态代理才是我们需要去关注的。下面我们通过源码来看看是如何实现动态代理的。
三. 源码解读
前面一节我们知道,方法newProxyInstance是生成动态代理对象的,而生成动态代理对象可以分为两步,首先我们需要生成Class对象,然后再利用反射技术通过Class对象和InvocationHandler对象来生成Class对象的实例对象,即动态代理对象。那我们按照步骤来分析源码吧
我们看下Proxy静态类的newProxyInstance方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// 省略 ...
final Class<?>[] intfs = interfaces.clone();
// 省略 ...
Class<?> cl = getProxyClass0(loader, intfs);
// 省略 ...
final Constructor<?> cons = cl.getConstructor(constructorParams);
// 省略 ...
return cons.newInstance(new Object[]{h});
}
可以看到,上面就4行源码,为什么只有4行呢?因为与生成代理对象直接相关的源码就这4行啊,其他都是安全检查校验之类的,所以我们抓主干,其他的大家有兴趣可以自己看看(我反正是没太多兴趣,哈哈,至少我现阶段是没必须要去追求这种细枝末节与主干无关的逻辑)我们先解释下这4行代码是干啥的:
-
我们首先将传入的接口通过clone方法复制一份(即代购实例中的IShoesShop与IBagShop接口),
-
通过传入类加载器与接口(被代理对象的类加载器与其实现的接口)给getProxyClass0方法生成代理Class对象(即代购例子中的ShoesShop和BagShop类的代理Class对象),
-
通过Class对象获得构造器
-
通过构造器生成Class对象的实例对象(构造器的参数是实现了InvocationHandler接口的对象,即代购例子中的代购公司PurchasingAgentCompany)
所以第1、2行代码是生成我们代理Class对象的,我们先把重点放在弄清楚如何生成代理Class对象,并且知道这个代理Class对象到底是长啥样的,只有清楚这两点,我们才能理解3、4行代码。
既然要知道如何生成代理Class对象,我们就要进入getProxyClass0方法
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
getProxyClass0方法首先会检查下接口长度,如果没超过65535字节,则会通过调用WeakCache类中的get方法。proxyClassCache即为WeakCache的实例。
我们继续进入proxyClassCache.get方法
public V get(K key, P parameter) {
// 省略 ...
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
// 省略 ...
}
subKeyFactory是Proxy静态类中的内部静态类ProxyClassFactory的实例,我们看这个名字就知道(工厂类啊),最终生成的代理Class对象应该就在这个内部静态类ProxyClassFactory中的某个方法中,没错,就是apply方法中。好,我们进入subKeyFactory.apply方法中看看
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
// 省略 ...
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这个方法吗,只要有代理类名字(代理类的名字是apply方法中生成的,代码被我省略掉了)和被代理类所继承的接口(为啥需要接口,因为接口里面有我们需要的方法呀),我们就可以通过ProxyGenerator.generateProxyClass方法产生二进制数据类型的Class文件(ProxyGenerator这个类是sun公司提供的sun.misc包中的一个类,这里我们就不展开了)
再通过defineClass0方法将此class文件通过类加载器生成Class对象放在元空间(1.8之前叫方法区),看到没,Class对象就是这样产生的
我们一般都是先写java文件,然后编译成class文件,最后再生成class对象及其实例。但是我们生成的代理Class文件是直接通过generateProxyClass方法在内存中生成的,它没有java文件,所以我们不知道这个代理对象到底长啥样,这里我们可以用反编译将生成的class文件反编译成java文件
我们写了一个小工具类来反编译生成BagShop的代理Class文件的java文件(工具代码和反编译网址贴在最后了)
import com.yy.demo16_proxyPattern.IBagShop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class IbagShop extends Proxy implements IBagShop {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public IbagShop(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void buyBag(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.yy.demo16_proxyPattern.IBagShop").getMethod("buyBag", new Class[]{Class.forName("java.lang.String")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到,这个类继承了Proxy类,实现了IBagShop接口。我们还可以看到一个有参构造方法,参数是InvocationHandler类型,然后我们再看buyBag这个方法,里面方法的调用语句为:
super.h.invoke(this, m3, new Object[]{var1});
这不就是h通过invoke来调用方法吗(对invoke不了解的可以补一下反射相关的知识)。那h是啥,不就是构造函数传进来的InvocationHander吗?我们再回到最开始的newProxyInstance方法,看下它的第3、4行代码:
final Constructor<?> cons = cl.getConstructor(constructorParams);
return cons.newInstance(new Object[]{h});
上面第一行代码是获取代理Class对象的构造器,第二行代码就是通过构造器生成代理Class对象的实例对象,即我们的动态代理对象。
最后总结一下生成动态代理对象的步骤
-
sun.misc.generateProxyClass类的generateProxyClass方法根据传入的被代理对象继承的接口信息直接生成代理对象的Class文件
-
defineClass0方法通过被代理对象类加载器将代理对象的Class文件加载成Class对象
-
我们通过反射技术获得Class对象的有参构造器,参数是实现了InvocationHandler接口的代理类
-
最后通过构造器生成动态代理对象
最后的最后,我把前文提到的工具代码和反编译网站贴出来
package com.yy.demo16_proxyPattern;
import com.yy.demo1_mapStruct.User;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @Author: 24只羊
* @Description:
* @Date: 2020-01-17
*/
public class ProxyGeneratorUtil {
private static String DEFAULT_CLASS_NAME = "$Proxy";
public static byte [] saveGenerateProxyClass(String proxyName, Class clazz) {
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, new Class[]{clazz});
FileOutputStream out = null;
try {
String filePath = "这里填写自己" + proxyName + ".class";
out = new FileOutputStream(filePath);
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return classFile;
}
public static void main(String[] args) {
Class<?> interfaces [] = {User.class};
saveGenerateProxyClass("程序员进阶之路")
}
}
在线反编译链接地址:http://javare.cn/
(完)
欢迎大家关注我的公众号 “程序员进阶之路”,里面记录了一个非科班程序员的成长之路