1.【RTFSC】 【Java Proxy】缓存的实现浅析

大炸好~我是什么也不会的MoreRoom。

今天是开门的第一篇,希望今后的日子自己能够坚持下来维护这个系列吧,争取能够让RTFSC成为一个专题。


第一篇 Java Proxy 

一、简介和Demo

    前段时间本来想看看Spring AOP相关的知识,顺气自然的就拐到了动态代理,关于Spring的话我准备等自认为有一些小小新的的时候再去写写吧,今天我们还是先聊一聊动态代理中的Java Proxy。

    说到Java Proxy,相信大家都比较熟悉了,基于接口生成对应的代理类,进而实现代理,一般来说就是用于实现切面,应用场景就譬如说,日志打印啦,权限校验啦,事物啦等等。

    具体怎么用网上有一大堆的例子,在这里也给出一个简单的例子,免去看官们一些搜demo的时间哈。

public interface IBoyService {
    public Boolean isBoy();
}
public interface IUserService {

    public String getUserName();

    public String sayHello(String name);

}
public class UserServiceImpl implements IUserService, IBoyService {

    /**
     * 0 = girl
     * 1 = boy
     */
    private int sex;

    public UserServiceImpl(int sex) {
        this.sex = sex;
    }

    @Override
    public String getUserName() {
        System.out.println("execute method getUserName~");
        return "userName";
    }

    @Override
    public String sayHello(String name) {
        System.out.println("execute method sayHello.");
        System.out.println("say hello to " + name);
        return name;
    }

    @Override
    public Boolean isBoy() {
        System.out.println("execute method isBoy?");
        return sex == 1;
    }
}
public static void main(String[] args) {
    IUserService userService = new UserServiceImpl(1);
    IUserService proxyUserService = (IUserService) Proxy.newProxyInstance(
        Thread.currentThread().getContextClassLoader(),userService.getClass().getInterfaces(), new InvocationHandler() {
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("this is before~");
            Object result = method.invoke(userService, args);
            System.out.println("this is after~");
            return result;
        }
    });
    proxyUserService.getUserName();
    proxyUserService.sayHello("MoreRoom");
}

    这是一个标准的代理实现,当代码执行时 返回的结果如下:

this is before~
execute method getUserName~
this is after~
this is before~
execute method sayHello.
say hello to MoreRoom
this is after~

    在这里假设我们在获取接口的时候使用的工厂模式,那么就有一个很完美的时机来返回一个代理接口,当调用其中的方法时,实际上底层通过反射调用了method的invoke方法来真正的调用我们的接口,在调用invoke之前,我们想要实现什么样的业务和功能,当然是各位自己说了算啦。

二、代理类字节码

    相信在我说出代理类中是通过反射实现的时候,有的同学就会问啦,为啥呀,怎么知道的呢,这里呢,只需要在Demo的main函数中加入一条配置属性:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

    然后再次执行,就可以看到项目中生成了这样一个目录:com/sun/proxy 

    里面存放了我们生成的代理类字节码文件,$Proxy0.class,长这个样子。

public final class $Proxy0 extends Proxy implements IUserService, IBoyService {
    private static Method m1;
    private static Method m5;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    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 Boolean isBoy() throws  {
        try {
            return (Boolean)super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String sayHello(String var1) throws  {
        try {
            return (String)super.h.invoke(this, m4, 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 String getUserName() throws  {
        try {
            return (String)super.h.invoke(this, m3, (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"));
            m5 = Class.forName("com.zzz.retry.test.itface.IBoyService").getMethod("isBoy");
            m4 = Class.forName("com.zzz.retry.test.itface.IUserService").getMethod("sayHello", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.zzz.retry.test.itface.IUserService").getMethod("getUserName");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

    大家可以简单的看一下哈,实际上没有太多难点,基本一眼略过,下面我将对源码部分进行一波讲解,受限于个人的能力,大家且看且思考吧,有不对的地方请指出探讨哈。

三、源码解析前的总结

    首先我先对这次自我学习得出的结论做出一个总结。

    Java Proxy是基于接口实现的动态代理,生成新的Class对象,底层业务通过反射来调用被代理的方法。而其源码的实现方面,主要是围绕着其自身的缓存来实现的。

    Java Proxy的缓存为两级接口(实际上就是两层的ConcurrentHashMap),按照各位大佬的习惯,我也称之为一级缓存和二级缓存,一级缓存的Key为ClassLoader,Value为二级缓存所对应的ConcurrentHashMap。二级缓存的Key值是通过KeyFactory的Apply方法,根据ClassLoader以及接口的数量来确定的,其中接口数量为1,2,以及更多的时候都会有不同的生成方式,但是这里先不做讨论啦。二级缓存的Value则比较特殊,当缓存刚刚存入的时候,Value为Factory,Factory实现了Supplier接口,翻译为提供者,而当要从缓存中取出对应的代理类Class对象时,则调用Supplier的get方法,同时将缓存的值替换为CacheValue。

    之后Proxy类获取代理Class的构造函数,生成并且返回对应的代理对象,这样一个正向的流程就完成啦,之后便着重提一下缓存的失效,说起来,既然是缓存,那么就肯定会有失效的情况。缓存类的描述,我们可以得出一级缓存的key是弱引用,是可能会被回收掉的,而二级缓存则不会,不过当一级缓存已经失效的情况下,二级缓存仿佛存在的意义也不大了。

    当一级缓存的Key失效的时候,其Value,也就是二级缓存实际上依然是存在的,那么在每一次获取动态代理Class对象之前,均会将失效的一级缓存值清空,具体实现我们在看源码的过程中再聊吧。

四、源码解析

    根据我们平时使用的入口。

    Proxy.newProxyInstance

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 
    throws IllegalArgumentException {
    // 1. 判斷代理处理器是否存在
    Objects.requireNonNull(h);
    // 2. 克隆获取所有需要代理的接口
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }
    // 获取代理类Class对象
    Class<?> cl = getProxyClass0(loader, intfs);
    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);
    }
}

    这里只需要留意加粗部分,那我们继续深入,先判断一下接口数量,呃。。。反正不会有这么多接口的,之后就是从缓存中获取对应的代理类Class对象了,那么,我们在进入get方法之前呢,我们要先介绍一下这个缓存。

private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    return proxyClassCache.get(loader, interfaces);
}

    这个proxyClassCache是一个私有的常量,结构为双层的ConcurrentMap(实现是用的ConcurrentHashMap),首先是声明

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

    这里出现了三个没见过的类,WeakCache<K,P,V>, KeyFactory, ProxyClassFactory。

    其中需要着重关注的就是WeakCache,整个缓存的逻辑都是在这个类中实现的,那么我们继续介绍一下他的结构。

// 引用队列,配合一级缓存作为弱引用使用
private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();
// 缓存本体没错了,两层map,第一层为一级缓存,Key=ClassLoader,value=Map。
// 二级缓存 Key为ClassLoader+interfaces value=(Factory / CacheValue)
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();
// 配合业务用的临时map,基本不用关注。
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>();
// Key值生成器
private final BiFunction<K, P, ?> subKeyFactory;
// value生成器
private final BiFunction<K, P, V> valueFactory;

    结构如上所示,其实事实上,我在注释中写的Key=ClassLoader也好 Key=ClassLoader+interfaces也罢,都不是特别准确,应该是用classsloader生成或者叫做classloader+interfaces生成吧,一级缓存的Key实际上是WeakCache中的内部类CacheKey,这个是一个弱引用,因此当没有地方引用他的话,在垃圾回收发生时,将会对其进行回收,只不过CacheKey是通过ClassLoader生成的,那么在回收之前,要首先回收ClassLoader,所以过期的可能性不是很大吧,在下面的篇幅中我会演示一下如何让缓存失效。二级缓存的Key要根据接口的数量来生成,就简单的看一下吧。     

public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
    switch (interfaces.length) {
        case 1: return new Key1(interfaces[0]); // the most frequent
        case 2: return new Key2(interfaces[0], interfaces[1]);
        case 0: return key0;
        default: return new KeyX(interfaces);
    }
}

    那么我们现在进入proxyClassCache的get方法

    进入后源码如下:

public V get(K key, P parameter) {
    Objects.requireNonNull(parameter);
    expungeStaleEntries();
    // 获取一级缓存的key
    Object cacheKey = CacheKey.valueOf(key, refQueue);
    // 然后在一级缓存中查找二级缓存。 这里二级缓存可能是空。
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
    if (valuesMap == null) {
        // 获取二级缓存   
        ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());
        if (oldValuesMap != null) {
            valuesMap = oldValuesMap;
        }
    }
    // 获取二级缓存的Key
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    // 然后取出二级缓存的值
    // 翻译为提供者
    Supplier<V> supplier = valuesMap.get(subKey);
    Factory factory = null;
    // 此循环会执行多次  因此按照标号来读
    while (true) {
        // B_如果提供者不是空 那么便获取代理类Class 返回
        if (Supplier != null) {
            V value = supplier.get();
            if (value != null) {
                return value;
            }
        }
        // A_如果工厂为空的话 则创建一个新的工厂
        if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }
        // A_二级缓存为空 那么将Factory放入缓存_此时缓存中的值是Factory
        if (supplier == null) {
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) { 
                supplier = factory;
            }
        } else {
            if (valuesMap.replace(subKey, supplier, factory)) {
                supplier = factory;
            } else {
                supplier = valuesMap.get(subKey);
            }
        }
    }
}

    中间涉及到看起来怪怪的逻辑,是因为这个缓存是个常量,那么这里面所有的操作都是暴露在多个线程之下的,不过鄙人才疏学浅,多线程的情况还是交给各位吧,我会在之后学习一下多线程来弥补一下吧。

    之后呢 其中有一句话。

expungeStaleEntries();
    这个就是用于清理过期缓存的,我们点进去看一下。

private void expungeStaleEntries() {
    CacheKey<K> cacheKey;
    while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {
        cacheKey.expungeFrom(map, reverseMap);
    }
}

    这里用到了之前说到的引用队列,当这里的CacheKey失效的时候,则会进入refQueue,这时候通过遍历这个队列,将失效的CacheKey对应的二级缓存清空掉。

    那么进入expungeFrom这个方法。

void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map,
                 ConcurrentMap<?, Boolean> reverseMap) {
    ConcurrentMap<?, ?> valuesMap = map.remove(this);
    if (valuesMap != null) {
        for (Object cacheValue : valuesMap.values()) {
            reverseMap.remove(cacheValue);
        }
    }
}

    这里通过判断一级缓存的Key是否与this相等(这个方法在CacheKey里面),然后通过map的remove方法来进性删除(实际是用null来进性replace),replace方法是通过判断实体的hashCode是否与原值相同,那么CacheKey便复写了hashCode方法。

@Override
public int hashCode() {
    return hash;
}

而这个hash在构造函数中赋值。

private CacheKey(K key, ReferenceQueue<K> refQueue) {
    super(key, refQueue);
    this.hash = System.identityHashCode(key);  // compare by identity
}

也就是当这个对象创建的一瞬间,其hash值就已经确定了,因此在缓存的Key被回收(或者说CacheKey中的reference被回收),也能够定位到对应的二级缓存来将其清理掉。

    最后再放一个会出现缓存失效的代码,具体有兴趣的朋友可以贴上去执行一下。无非就是按着我上面说的执行以下而已。

public class Main {
    /**
     * 自定义的ClassLoader
     */
    private static MClassloader classLoader = new MClassloader();

    /**
     * 执行的main函数
     *
     * @param args
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Main main = new Main();
        main.executeProxy4GC();
        Proxy.isProxyClass(new Object().getClass());
        main.executeProxy();
        Proxy.isProxyClass(new Object().getClass());
        classLoader = null;
        System.gc();
        Proxy.isProxyClass(new Object().getClass());
        main.executeProxy();
    }

    /**
     * 用于GC用的代理执行逻辑
     *
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public void executeProxy4GC() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        classLoader.loadClass("proxy.service.IUserService");
        classLoader.loadClass("proxy.service.IBoyService");
        Class c = classLoader.loadClass("proxy.service.impl.UserServiceImpl");
        IUserService userService = (IUserService) c.newInstance();
        IUserService userServiceProxy = (IUserService) Proxy.newProxyInstance(classLoader, userService.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = method.invoke(userService, args);
                return result;
            }
        });
    }

    /**
     * 代理执行逻辑
     *
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public void executeProxy() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        IUserService userService = new UserServiceImpl();
        IUserService userServiceProxy = (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = method.invoke(userService, args);
                return result;
            }
        });
    }

    /**
     * 自定义的类加载器
     */
    private static final class MClassloader extends ClassLoader {

    }

}

最后的最后呢,有劳各位花费时间来看一个这么冗长的帖子。有什么不对的地方也请各位指出吧。

拜谢了。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值