前言

最近,看了一下对于RMI(Remote Method Invocation)相干的常识,遇到了一个动静代理的问题,而后就决定探索一下动静代理。

这里先科普一下RMI。

RMI

像咱们平时写的程序,对象之间相互调用办法都是在同一个JVM中进行,而RMI能够实现一个JVM上的对象调用另一个JVM上对象的办法,即近程调用。

接口定义

定义一个近程对象接口,实现Remote接口来进行标记。

public interface UserInterface extends Remote {    void sayHello() throws RemoteException;} 

近程对象定义

定义一个近程对象类,继承UnicastRemoteObject来实现Serializable和Remote接口,并实现接口办法。

public class User extends UnicastRemoteObject implements UserInterface {    public User() throws RemoteException {}    @Override    public void sayHello() {        System.out.println("Hello World");    }} 

服务端

启动服务端,将user对象在注册表上进行注册。

public class RmiServer {    public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {        User user = new User();        LocateRegistry.createRegistry(8888);        Naming.bind("rmi://127.0.0.1:8888/user", user);        System.out.println("rmi server is starting...");    }} 

启动服务端:

客户端

从服务端注册表获取近程对象,在服务端调用sayHello()办法。

public class RmiClient {    public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {        UserInterface user = (UserInterface) Naming.lookup("rmi://127.0.0.1:8888/user");        user.sayHello();    }} 

服务端运行后果:至此,一个简略的RMI demo实现。

动静代理

提出问题

看了看RMI代码,感觉UserInterface这个接口有点多余,如果客户端应用Naming.lookup()获取的对象不强转成UserInterface,间接强转成User是不是也能够,于是试了一下,就报了以下谬误:似曾相识又有点生疏的$Proxy0,翻了翻尘封的笔记找到了是动静代理的知识点,寥寥几笔带过,所以决定梳理一下动静代理,重新整理一份笔记。

动静代理Demo

接口定义

public interface UserInterface {    void sayHello();} 

实在角色定义

public class User implements UserInterface {    @Override    public void sayHello() {        System.out.println("Hello World");    }} 

调用解决类定义

代理类调用实在角色的办法时,其实是调用与实在角色绑定的解决类对象的invoke()办法,而invoke()调用的是实在角色的办法。

这里须要实现 InvocationHandler 接口以及invoke()办法。

public class UserHandler implements InvocationHandler {    private User user;    public UserProxy(User user) {        this.user = user;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        System.out.println("invoking start....");        method.invoke(user);        System.out.println("invoking stop....");        return user;    }} 

执行类

public class Main {    public static void main(String[] args) {        User user = new User();        // 解决类和实在角色绑定        UserHandler userHandler = new UserHandler(user);        // 开启将代理类class文件保留到本地模式,平时能够省略        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");        // 动静代理生成代理对象$Proxy0        Object o = Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{UserInterface.class}, userHandler);        // 调用的其实是invoke()        ((UserInterface)o).sayHello();    } 

运行后果:这样动静代理的根本用法就学完了,可是还有好多问题不明确。

  1. 动静代理是怎么调用的invoke()办法?
  2. 解决类UserHandler有什么作用?
  3. 为什么要将类加载器和接口类数组当作参数传入newProxyInstance?

如果让你去实现动静代理,你有什么设计思路?

猜测

动静代理,是不是和动态代理,即设计模式的代理模式有相同之处呢?

简略捋一捋代理模式实现原理:实在角色和代理角色独特实现一个接口并实现形象办法A,代理类持有实在角色对象,代理类在A办法中调用实在角色对象的A办法。在Main中实例化代理对象,调用其A办法,间接调用了实在角色的A办法。

「实现代码」

// 接口和实在角色对象就用下面代码// 代理类,实现UserInterface接口public class UserProxy implements UserInterface {   // 持有实在角色对象    private User user = new User();    @Override    public void sayHello() {        System.out.println("invoking start....");        // 在代理对象的sayHello()里调用实在角色的sayHello()        user.sayHello();        System.out.println("invoking stop....");    }}// 运行类public class Main {    public static void main(String[] args) {       // 实例化代理角色对象        UserInterface userProxy = new UserProxy();        // 调用了代理对象的sayHello(),其实是调用了实在角色的sayHello()        userProxy.sayHello();    } 

拿开始的动静代理代码和动态代理比拟,接口、实在角色都有了,区别就是多了一个UserHandler解决类,少了一个UserProxy代理类。

接着比照一下两者的解决类和代理类,发现UserHandler的invoke()和UserProxy的sayHello()这两个办法的代码都是一样的。那么,是不是新建一个UserProxy类,而后实现UserInterface接口并持有UserHandler的对象,在sayHello()办法中调用UserHandler的invoke()办法,就能够动静代理了。

「代码大略就是这样的」

// 猜测的代理类构造,动静代理生成的代理是com.sun.proxy.$Proxy0public class UserProxy implements UserInterface{   // 持有解决类的对象    private InvocationHandler handler;    public UserProxy(InvocationHandler handler) {        this.handler = handler;    }    // 实现sayHello()办法,并调用invoke()    @Override    public void sayHello() {        try {            handler.invoke(this, UserInterface.class.getMethod("sayHello"), null);        } catch (Throwable throwable) {            throwable.printStackTrace();        }    }}// 执行类public static void main(String[] args) {        User user = new User();        UserHandler userHandler = new UserHandler(user);        UserProxy proxy = new UserProxy(userHandler);        proxy.sayHello();    } 

输入后果:

下面的代理类代码是写死的,而动静代理是当你调用Proxy.newProxyInstance()时,会依据你传入的参数来动静生成这个代理类代码,如果让我实现,会是以下这个流程。

  1. 依据你传入的Class[]接口数组,代理类会来实现这些接口及其办法(这里就是sayHello()),并且持有你传入的userHandler对象,应用文件流将事后设定的包名、类名、办法名等一行行代码写到本地磁盘,生成$Proxy0.java文件
  2. 应用编译器将编译成Proxy0.class
  3. 依据你传入的ClassLoader将$Proxy0.class加载到JMV中
  4. 调用Proxy.newProxyInstance()就会返回一个$Proxy0的对象,而后调用sayHello(),就执行了外面userHandler的invoke()

以上就是对动静代理的一个猜测过程,上面就通过debug看看源码是怎么实现的。

在困惑的日子里学会拥抱源码

拥抱源码

调用流程图

这里先用PPT画一个流程图,能够跟着流程图来看前面的源码。

流程图

「从newProxyInstance()设置断点」

newProxyInstance()

newProxyInstance()代码分为高低两局部,上局部是获取类,下局部是通过反射构建Proxy0对象。

「上局部代码」

newProxyInstance()

从名字看就晓得getProxyClass0()是外围办法,step into

getProxyClass0()

getProxyClass()

外面调用了WeakCache对象的get()办法,这里暂停一下debug,先讲讲WeakCache类。

WeakCache

顾名思义,它是一个弱援用缓存。那什么是是弱援用呢,是不是还有强援用呢?

弱援用

WeakReference就是弱援用类,作为包装类来包装其余对象,在进行GC时,其中的包装对象会被回收,而WeakReference对象会被放到援用队列中。

举个栗子:

 // 这就是强援用,只有不写str1 = null,str1指向的这个字符串不就会被垃圾回收 String str1 = new String("hello"); ReferenceQueue referenceQueue = new ReferenceQueue(); // 只有垃圾回收,这个str2外面包装的对象就会被回收,然而这个弱援用对象不会被回收,即word会被回收,然而str2指向的弱援用对象不会 // 每个弱援用关联一个ReferenceQueue,当包装的对象被回收,这个弱援用对象会被放入援用队列中 WeakReference<String> str2 = new WeakReference<>(new String("world"), referenceQueue); // 执行gc System.gc(); Thread.sleep(3); // 输入被回收包装对象的弱援用对象:java.lang.ref.WeakReference@2077d4de // 能够debug看一下,弱援用对象的referent变量指向的包装对象曾经为null System.out.println(referenceQueue.poll()); 

WeakCache的构造

其实整个WeakCache的都是围绕着成员变量map来工作的,构建了一个一个<K,<K,V>>格局的二级缓存,在动静代理中对应的类型是<类加载器, <接口Class, 代理Class>>,它们都应用了弱援用进行包装,这样在垃圾回收的时候就能够间接回收,缩小了堆内存占用。

// 寄存已回收弱援用的队列private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();// 应用ConcurrentMap实现的二级缓存构造private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();// 能够不关注这个,这个是用来标识二级缓存中的value是否存在的,即Supplier是否被回收private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>();// 包装传入的接口class,生成二级缓存的Keyprivate final BiFunction<K, P, ?> subKeyFactory = new KeyFactory();// 包装$Proxy0,生成二级缓存的Valueprivate final BiFunction<K, P, V> valueFactory = new ProxyClassFactory(); 

WeakCache的get()

回到debug,接着进入get()办法,看看map二级缓存是怎么生成KV的。

 public V get(K key, P parameter) {        Objects.requireNonNull(parameter);        // 遍历refQueue,而后将缓存map中对应的生效value删除        expungeStaleEntries();        // 以ClassLoader为key,构建map的一级缓存的Key,是CacheKey对象        Object cacheKey = CacheK.valueOf(key, refQueue);        // 通过Key从map中获取一级缓存的value,即ConcurrentMap        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);        if (valuesMap == null) {         // 如果Key不存在,就新建一个ConCurrentMap放入map,这里应用的是putIfAbsent         // 如果key曾经存在了,就不笼罩并返回外面的value,不存在就返回null并放入Key         // 当初缓存map的构造就是ConCurrentMap<CacheKey, ConCurrentMap<Object, Supplier>>            ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());            // 如果其余线程曾经创立了这个Key并放入就能够复用了            if (oldValuesMap != null) {                valuesMap = oldValuesMap;            }        }        // 生成二级缓存的subKey,当初缓存map的构造就是ConCurrentMap<CacheKey, ConCurrentMap<Key1, Supplier>>        // 看前面的<生成二级缓存Key>!!!        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));        // 依据二级缓存的subKey获取value        Supplier<V> supplier = valuesMap.get(subKey);        Factory factory = null;        // !!!直到实现二级缓存Value的构建才完结,Value是弱援用的$Proxy0.class!!!        while (true) {           // 第一次循环:suppiler必定是null,因为还没有将放入二级缓存的KV值           // 第二次循环:这里suppiler不为null了!!!进入if            if (supplier != null) {                // 第二次循环:真正生成代理对象,                // 往后翻,看<生成二级缓存Value>,外围!!!!!                // 看完前面回到这里:value就是弱援用后的$Proxy0.class                V value = supplier.get();                if (value != null) {             // 本办法及上局部的最初一行代码,跳转最初的<构建$Proxy对象>                    return value;                }            }          // 第一次循环:factory必定为null,生成二级缓存的Value            if (factory == null) {                factory = new Factory(key, parameter, subKey, valuesMap);            }         // 第一次循环:将subKey和factory作为KV放入二级缓存            if (supplier == null) {                supplier = valuesMap.putIfAbsent(subKey, factory);                if (supplier == null) {                    // 第一次循环:赋值之后suppiler就不为空了,记住!!!!!                    supplier = factory;                }            }            }        }    } 

生成二级缓存Key

在get()中调用subKeyFactory.apply(key, parameter),依据你newProxyInstance()传入的接口Class[]的个数来生成二级缓存的Key,这里咱们就传入了一个UserInterface.class,所以就返回了Key1对象。

KeyFactory.apply()

不论是Key1、Key2还是KeyX,他们都继承了WeakReference,都是包装对象是Class的弱援用类。这里看看Key1的代码。

Key1

生成二级缓存Value

在下面的while循环中,第一次循环只是生成了一个空的Factory对象放入了二级缓存的ConcurrentMap中。

在第二次循环中,才开始通过get()办法来真正的构建value。

别回头,接着往下看。

Factory.get()生成弱援用value

「CacheValue」类是一个弱援用,是二级缓存的Value值,包装的是class,在这里就是$Proxy0.class,至于这个类如何生成的,依据上面代码正文始终看完Class文件的生成

public synchronized V get() {            // 查看是否被回收,如果被回收,会继续执行下面的while循环,从新生成Factory            Supplier<V> supplier = valuesMap.get(subKey);            if (supplier != this) {                return null;            }            // 这里的V的类型是Class            V value = null;            // 这行是外围代码,看前面<class文件的生成>,记住这里返回的是Class            value = Objects.requireNonNull(valueFactory.apply(key, parameter));            // 将Class对象包装成弱援用            CacheValue<V> cacheValue = new CacheValue<>(value);            // 回到下面<WeakCache的get()办法>V value = supplier.get();            return value;        }    } 

CacheValue

Class文件的生成

包名类名的定义与验证

进入valueFactory.apply(key, parameter)办法,看看class文件是怎么生成的。

 private static final String proxyClassNamePrefix = "$Proxy"; public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);            // 遍历你传入的Class[],咱们只传入了UserInterface.class            for (Class<?> intf : interfaces) {                Class<?> interfaceClass = null;                 // 获取接口类                interfaceClass = Class.forName(intf.getName(), false, loader);                 // 这里就很明确为什么只能传入接口类,不是接口类会报错                if (!interfaceClass.isInterface()) {                    throw new IllegalArgumentException(                        interfaceClass.getName() + " is not an interface");                }            String proxyPkg = null;             int accessFlags = Modifier.PUBLIC | Modifier.FINAL;            for (Class<?> intf : interfaces) {                int flags = intf.getModifiers();                // 验证接口是否是public,不是public代理类会用接口的package,因为只有在同一包内能力继承                // 咱们的UserInterface是public,所以跳过                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");                    }                }            }         // 如果接口类是public,则用默认的包            if (proxyPkg == null) {                // PROXY_PACKAGE = "com.sun.proxy";                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";            }         // 原子Int,此时num = 0            long num = nextUniqueNumber.getAndIncrement();            // com.sun.proxy.$Proxy0,这里包名和类名就呈现了!!!            String proxyName = proxyPkg + proxyClassNamePrefix + num;         // !!!!生成class文件,查看前面<class文件写入本地> 外围!!!!            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);            // !!!看完上面再回来看这行!!!!            // 获取了字节数组之后,获取了class的二进制流将类加载到了JVM中            // 并且返回了$Proxy0.class,返回给Factory.get()来包装            return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);                       }        }    } 

defineClass0()是Proxy类自定义的类加载的native办法,会获取class文件的二进制流加载到JVM中,以获取对应的Class对象,这一块能够参考JVM类加载器。

class文件写入本地

generateProxyClass()办法会将class二进制文件写入本地目录,并返回class文件的二进制流,应用你传入的类加载器加载,「这里你晓得类加载器的作用了么」

 public static byte[] generateProxyClass(final String name,                                            Class[] interfaces)    {        ProxyGenerator gen = new ProxyGenerator(name, interfaces);        // 生成class文件的二进制,查看前面<生成class文件二进制>        final byte[] classFile = gen.generateClassFile();      // 将class文件写入本地          if (saveGeneratedFiles) {            java.security.AccessController.doPrivileged(            new java.security.PrivilegedAction<Void>() {                public Void run() {                    try {                        FileOutputStream file =                            new FileOutputStream(dotToSlash(name) + ".class");                        file.write(classFile);                        file.close();                        return null;                    } catch (IOException e) {                        throw new InternalError(                            "I/O exception saving generated file: " + e);                    }                }            });        }      // 返回$Proxy0.class字节数组,回到下面<class文件生成>        return classFile;    } 

生成class文件二进制流

generateClassFile()生成class文件,并存放到字节数组,「能够顺便学一下class构造,这里也体现了你传入的class[]的作用」

 private byte[] generateClassFile() {      // 将hashcode、equals、toString是三个办法放入代理类中        addProxyMethod(hashCodeMethod, Object.class);        addProxyMethod(equalsMethod, Object.class);        addProxyMethod(toStringMethod, Object.class);        for (int i = 0; i < interfaces.length; i++) {            Method[] methods = interfaces[i].getMethods();            for (int j = 0; j < methods.length; j++) {             // 将接口类的办法放入新建的代理类中,这里就是sayHello()                addProxyMethod(methods[j], interfaces[i]);            }        }        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {            checkReturnTypes(sigmethods);        }        // 给代理类减少构造方法        methods.add(generateConstructor());        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {            for (ProxyMethod pm : sigmethods) {                   // 将下面的四个办法都封装成Method类型成员变量                    fields.add(new FieldInfo(pm.methodFieldName,                        "Ljava/lang/reflect/Method;",                         ACC_PRIVATE | ACC_STATIC));                    // generate code for proxy method and add it                    methods.add(pm.generateMethod());                }            }      // static动态块结构        methods.add(generateStaticInitializer());        cp.getClass(dotToSlash(className));        cp.getClass(superclassName);        for (int i = 0; i < interfaces.length; i++) {            cp.getClass(dotToSlash(interfaces[i].getName()));        }        cp.setReadOnly();        ByteArrayOutputStream bout = new ByteArrayOutputStream();        DataOutputStream dout = new DataOutputStream(bout);      // !!!外围点来了!这里就开始构建class文件了,以下都是class的构造,只写一部分        try {               // u4 magic,class文件的魔数,确认是否为一个能被JVM承受的class            dout.writeInt(0xCAFEBABE);            // u2 minor_version,0            dout.writeShort(CLASSFILE_MINOR_VERSION);            // u2 major_version,主版本号,Java8对应的是52;            dout.writeShort(CLASSFILE_MAJOR_VERSION);            // 常量池            cp.write(dout);            // 其余构造,可参考class文件构造            dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);            dout.writeShort(cp.getClass(dotToSlash(className)));            dout.writeShort(cp.getClass(superclassName));            dout.writeShort(interfaces.length);            for (int i = 0; i < interfaces.length; i++) {                dout.writeShort(cp.getClass(                    dotToSlash(interfaces[i].getName())));            }            dout.writeShort(fields.size());            for (FieldInfo f : fields) {                f.write(dout);            }            dout.writeShort(methods.size());                       for (MethodInfo m : methods) {                m.write(dout);            }            dout.writeShort(0);         } catch (IOException e) {            throw new InternalError("unexpected I/O Exception", e);        }        // 将class文件字节数组返回        return bout.toByteArray();    } 

构建$Proxy对象

newProxyInstance()上半局部通过下面层层代码调用,获取了$Proxy0.class,接下来看下局部代码:

newInstance

cl就是下面获取的Proxy0.class,h就是下面传入的userHandler,被当做结构参数来创立$Proxy0对象。而后获取这个动静代理对象,调用sayHello()办法,相当于调用了UserHandler的invoke(),「这里就是UserHandler的作用」

$Proxy.class文件

咱们开启了将代理class写到本地目录的性能,在我的项目下的com/sum/proxy目录下找到了$Proxy0的class文件。

「看一下反编译的class」

package com.sun.proxy;import com.test.proxy.UserInterface;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements UserInterface {    private static Method m1;    private static Method m3;    private static Method m2;    private static Method m0;    public $Proxy0(InvocationHandler var1) throws  {        super(var1);    }    public final boolean equals(Object var1) throws  {        try {            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});        } catch (RuntimeException | Error var3) {            throw var3;        } catch (Throwable var4) {            throw new UndeclaredThrowableException(var4);        }    }    public final void sayHello() throws  {        try {            super.h.invoke(this, m3, (Object[])null);        } catch (RuntimeException | Error var2) {            throw var2;        } catch (Throwable var3) {            throw new UndeclaredThrowableException(var3);        }    }    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);        } catch (RuntimeException | Error var2) {            throw var2;        } catch (Throwable var3) {            throw new UndeclaredThrowableException(var3);        }    }    static {        try {            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));            m3 = Class.forName("com.test.proxy.UserInterface").getMethod("sayHello");            m2 = Class.forName("java.lang.Object").getMethod("toString");            m0 = Class.forName("java.lang.Object").getMethod("hashCode");        } catch (NoSuchMethodException var2) {            throw new NoSuchMethodError(var2.getMessage());        } catch (ClassNotFoundException var3) {            throw new NoClassDefFoundError(var3.getMessage());        }    }} 

结语

下面就是动静代理源码的调试过程,与之前的猜测的代理类的生成过程比拟,动静代理是间接生成class文件,省去了java文件和编译这一块。

刚开始看可能比拟绕,跟着正文及跳转指引,急躁多看两遍就明确了。动静代理波及的知识点比拟多,我本人看的时候,在WeakCache这一块纠结了一阵,其实把它当成一个两层的map看待即可,只不过外面所有的KV都被弱援用包装。

心愿看到这篇文章的每个程序员最终都能成为头发繁茂的码农;

举荐浏览

为什么阿里巴巴的程序员成长速度这么快,看完他们的内部资料我懂了

字节跳动总结的设计模式 PDF 火了,完整版凋谢下载

刷Github时发现了一本阿里大神的算法笔记!标星70.5K

程序员50W年薪的常识体系与成长路线。

月薪在30K以下的Java程序员,可能听不懂这个我的项目;

字节跳动总结的设计模式 PDF 火了,完整版凋谢分享

对于【暴力递归算法】你所不晓得的思路

开拓鸿蒙,谁做零碎,聊聊华为微内核

 
=

看完三件事❤️

如果你感觉这篇内容对你还蛮有帮忙,我想邀请你帮我三个小忙:

点赞,转发,有你们的 『点赞和评论』,才是我发明的能源。

关注公众号 『 Java斗帝 』,不定期分享原创常识。

同时能够期待后续文章ing????