首页 > 学院 > 开发设计 > 正文

JDK动态代理原理剖析(一)

2019-11-08 00:58:47
字体:
来源:转载
供稿:网友

虽然使用了动态代理,但是对其原理还不是十分了解。这段时间剖析了下JDK动态代理的源码,算是搞懂了JDK代理类的生成逻辑。下面,先来回顾下代理类的使用:

1、动态代理的使用

//接口对象public interface LogService {    public void writeLog();}//接口实现类public class LogServiceImpl implements LogService {    @Override    public void writeLog() {        // TODO Auto-generated method stub        System.out.PRintln("-------------记录日志-----------");     }}//处理类public class MyInvocationHandler implements InvocationHandler {        private Object target;        public  MyInvocationHandler(Object t) {        // TODO Auto-generated constructor stub        this.target=t;    }       @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        // 在目标对象的方法执行之前简单的打印一下          System.out.println("------------------before------------------");                 // 执行目标对象的方法          Object result = method.invoke(target, args);                // 在目标对象的方法执行之后简单的打印一下          System.out.println("-------------------after------------------");                   return result;      }      public Object getProxy(){        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),                   target.getClass().getInterfaces(), this);     }}//测试类public class Test {    public static void main(String[] args) {        // TODO Auto-generated method stub        LogService logService=new LogServiceImpl();        MyInvocationHandler myInvocationHandler=new MyInvocationHandler(logService);        LogService logServiceProxy=(LogService) myInvocationHandler.getProxy();        logServiceProxy.writeLog();    }}

执行结果:------------------before-------------------------------记录日志------------------------------after------------------

2、源码分析

           用起来是比较简单,但是如果能知道其背后的原理就再好不过了。重点在类MyInvocationHandler中的getProxy()方法,我们将目光放在下面这一行代码:

Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),target.getClass().getInterfaces(), this); Debug进入这段代码,可以得到如下片段():

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException    {        if (h == null) {            throw new NullPointerException();        }        final SecurityManager sm = System.getSecurityManager();        if (sm != null) {            checkProxyaccess(Reflection.getCallerClass(), loader, interfaces);        }        /*         * 重点在这句话,这句话是生成代理类关键的地方,我们下面来看         */        Class<?> cl = getProxyClass0(loader, interfaces);        try {           /*             这里大致解释一下,在Proxy类的开头定义了一个静态成员             private final static Class[] constructorParams ={ InvocationHandler.class };             因此,这个constructorParams指的就是class数组.           */            //这句就是从代理类中得到以InvocationHandler为参数的构造函数            final Constructor<?> cons = cl.getConstructor(constructorParams);            final InvocationHandler ih = h;            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {                // create proxy instance with doPrivilege as the proxy class may                // implement non-public interfaces that requires a special permission                return AccessController.doPrivileged(new PrivilegedAction<Object>() {                    public Object run() {                        //将构造函数和操作类进行封装,生成代理对象返回,原理是使用cons.newInstance(new Object[] {h} );                        return newInstance(cons, ih);                    }                });            } else {                return newInstance(cons, ih);            }        } catch (NoSuchMethodException e) {            throw new InternalError(e.toString());        }    }

接下来进入getProxyClass0方法,详细看看这块代码是如何实现的,这块代码比较长,分为几个小块进行解释

private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {

接下来进行接口的一些验证,大致可分为以下几步: 

保证接口的数量在65535范围内 以interface的长度为准,建立一个名为interfaceNames字符数组,这个数组是用来作为代理类缓存的key定义一个检测是否存在重复接口的Set<Class<?>> interfaceSet 初始化interfaceNames数组,里面最终将填满接口的名称

            if (interfaces.length > 65535) {               throw new IllegalArgumentException("interface limit exceeded");            }            Class<?> proxyClass = null;            String[] interfaceNames = new String[interfaces.length];            Set<Class<?>> interfaceSet = new HashSet<>();            //初始化interfaceNames数组的代码略去,非核心代码,最终interfaceNames中讲填满接口名称

     接下来这段逻辑可以这么理解,以interfaceNames做key,将代理类缓存起来这样,我们的代码中第二次调用Proxy.newProxyInstance()方法时,就不需要再去动态生成代理类,而是直接去缓存中获取,可大大提高性能。

  这段代码我就觉得sun写的相当棒,在此详细说明一下。以后如果遇到只需动态加载一次类的代码,可参照sun的这段逻辑:

  先定义两个全局成员变量,注意着两个成员变量是在Proxy类中,而不是在getProxyClass0()方法中:

//这是一个缓存map,好好思考下为啥用ClassLoader做key哦,这个写法给我很大的启发。其中这个map的value又是一个map(称为值map)。而这个值map的key为List,就是我们刚提到的interfacenames,而Object,就是我们的缓存对象。通过下面代码的说明,大家很容易就能明白。          private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache = new WeakHashMap<>();         //标志位,标记动态代理类是不是正在生成。     private static Object pendingGenerationMarker = new Object();    

!!下面是这段缓存的主要逻辑,大家仔细看哦:

        //将interfacename转为list,作为loaderCache的key        List key = Arrays.asList(interfaceNames);        /*         * 这才是真正缓存我们代理类的map         */        Map<List, Object> cache;        synchronized (loaderToCache) {           //这段逻辑很清晰,就是先从缓存中取,取不到就自己new一个塞进去            cache = loaderToCache.get(loader);            if (cache == null) {                cache = new HashMap<>();                loaderToCache.put(loader, cache);            }        }        /*         *假设程序中有以下两行代码         *LogService logServiceProxy1=(LogService) myInvocationHandler.getProxy(); (a)          *LogService logServiceProxy2=(LogService) myInvocationHandler.getProxy(); (b)         *OK,这段是核心,仔细看了哦,针对(a)和(b)在下面代码中详细说明         */        synchronized (cache) {            do {                Object value = cache.get(key);                if (value instanceof Reference) {                    //代码(a)顺利执行,那么代码(b)就可以顺利取出proxyclass                    proxyClass = (Class<?>) ((Reference) value).get();                }                if (proxyClass != null) {                    // proxy class already generated: return it                    return proxyClass;                } else if (value == pendingGenerationMarker) {                    //代码(a)不顺利,在动态生成代理类中耗时比较长,那么代码(b)执行到此处,将执行下方的wait方法,等在此处。                    //代码(a)中的动态代理类顺利生成后,代码(b)因为一直执行do while语句,所以一定会取出proxyClass,从而返回。                    try {                        cache.wait();                    } catch (InterruptedException e) {                                          }                    continue;                } else {                    /*代码(a)运行的时候,由于缓存没东西,直接塞入正在生成代理类的标志位,                     *代码(b)就靠识别这个对象的内存地址,判断生成代理类的情况                     */                    cache.put(key, pendingGenerationMarker);                    break;                }            } while (true);        }

      紧接着是一段生成包名的逻辑和生成代理类的逻辑。备注:由于生成代理类的逻辑代码比较复杂,将放在《JDK动态代理原理剖析(二)》    

 try {                String proxyPkg = null;     // package to define proxy class in                //省去生成proxyPkg的逻辑,不是重点,知道会生成这样一个包名即可                String proxyName = proxyPkg + proxyClassNamePrefix + num;                 /*                 * ProxyGenerator.generateProxyClass()这个方法将在《JDK动态代理原理剖析(二)》中详细介绍,此方法比较复杂,                 * 这里只需知道,会生成一个代理类的字节流即可                 */                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(                    proxyName, interfaces);                try {                  /*                  * 将字节流生成类defineClass0是native方法,就不做介绍,只需明白是利用字节流生成代理类即可。                  */                    proxyClass = defineClass0(loader, proxyName,                        proxyClassFile, 0, proxyClassFile.length);                } catch (ClassFormatError e) {                    throw new IllegalArgumentException(e.toString());                }            }            proxyClasses.put(proxyClass, null);        } finally {            /*             *这段代码很好理解,避免死锁             */            synchronized (cache) {                if (proxyClass != null) {                    cache.put(key, new WeakReference<Class<?>>(proxyClass));                } else {                    cache.remove(key);                }                cache.notifyAll();            }        }        return proxyClass;

到这里,动态代理类如何生成的,逻辑一目了然了。最后,做出如下总结:

3、总结

  JDK实现动态代理的过程如下:

克隆传入的接口Class数组查找或者生成实现了接口数组中所有接口的动态代理类的Class利用动态代理类的Class获取Constructor对象Constructor对象利用我们传入的InvocationHandler实现类对象作为输入参数,生成动态代理类的对象

   


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表