Deep learning about Java--贯穿Java的反射机制(4)

本文深入探讨了Java中的类加载器(ClassLoader),包括BootstrapClassLoader、ExtensionClassLoader和SystemClassLoader(AppClassLoader)的工作原理及相互关系,并对比分析了JDK Proxy与CGLib动态代理的不同。

这次我们来讲讲ClassLoader和CGLib。

1.3种重要ClassLoader 的简单介绍

上一篇文章中笔者主要是谈到了Dynamic Proxy(动态代理)的源码分析,在关于newProxyInstance的前两个参数时笔者简略地说这是用于创建Proxy这个代理对象的,这并没有问题,但是深入点笔者的疑问就来了–不知道各位有没有想过类加载器到底加载的是哪个?如果我们把传入newProxyInstance的ClassLoader改成由其他接口子类实例传入还能成功么?

要解答这些问题,就和笔者一同进行这个天坑之旅吧!

更前面一篇的文章中,笔者写过三中类加载器,分别是Bootstrap ClassLoader、Extension ClassLoader和System ClassLoader(又称App ClassLoader),要想回答钱前面的问题就必须说说这三种加载器和他们之间的关系。

  • Boostrap Classloader
    这个类加载器主要是加载JDK中的核心类库,我们先看下它的源码,再来把他们输出一遍记录下这些核心类库。

jdk手册

public class Launcher {
56     private static URLStreamHandlerFactory factory = new Factory();
57     private static Launcher launcher = new Launcher();
58     private static String bootClassPath =
59         System.getProperty("sun.boot.class.path");//从这里可以得知Boostrap ClassLoader如何可以得到
60 
61     public static Launcher getLauncher() {
62         return launcher;
63     }
64 
65     private ClassLoader loader;
66 
67     public Launcher() {
68         // Create the extension class loader
69         ClassLoader extcl;
70         try {
71             extcl = ExtClassLoader.getExtClassLoader();//这是Extension ClassLoader
72         } catch (IOException e) {
73             throw new InternalError(
74                 "Could not create extension class loader", e);
75         }
76 
77         // Now create the class loader to use to launch the application
78         try {
79             loader = AppClassLoader.getAppClassLoader(extcl);//这里是App ClassLoader
80         } catch (IOException e) {
81             throw new InternalError(
82                 "Could not create application class loader", e);
83         }
               ......
        /*
120     * The class loader used for loading installed extensions.
121     */
122    static class ExtClassLoader extends URLClassLoader {
               ......
169        private static File[] getExtDirs() {
170            String s = System.getProperty("java.ext.dirs");
171            File[] dirs;
172            if (s != null) {
173                StringTokenizer st =
174                    new StringTokenizer(s, File.pathSeparator);
175                int count = st.countTokens();
176                dirs = new File[count];
177                for (int i = 0; i < count; i++) {
178                    dirs[i] = new File(st.nextToken());
179                }
180            } else {
181                dirs = new File[0];
182            }
183            return dirs;
184        }
               ......
}
               ......
261    static class AppClassLoader extends URLClassLoader {
263        static {
264            ClassLoader.registerAsParallelCapable();
265        }
266
267        public static ClassLoader getAppClassLoader(final ClassLoader extcl)
268            throws IOException
269        {
270            final String s = System.getProperty("java.class.path");
271            final File[] path = (s == null) ? new File[0] : getClassPath(s);
272
273            // Note: on bugid 4256530
274            // Prior implementations of this doPrivileged() block supplied
275            // a rather restrictive ACC via a call to the private method
276            // AppClassLoader.getContext(). This proved overly restrictive
277            // when loading  classes. Specifically it prevent
278            // accessClassInPackage.sun.* grants from being honored.
279            //
280            return AccessController.doPrivileged(
281                new PrivilegedAction<AppClassLoader>() {
282                    public AppClassLoader run() {
283                    URL[] urls =
284                        (s == null) ? new URL[0] : pathToURLs(path);
285                    return new AppClassLoader(urls, extcl);
286                }
287            });
288        }
               ......   
}       
               ......              
}     
@Test
    public void outPutBoostrapClassLoader(){
        System.out.println(System.getProperty("sun.boot.class.path"));
    }
F:\Program Files\Java\jre1.8.0_162\lib\resources.jar;
F:\Program Files\Java\jre1.8.0_162\lib\rt.jar;
F:\Program Files\Java\jre1.8.0_162\lib\sunrsasign.jar;
F:\Program Files\Java\jre1.8.0_162\lib\jsse.jar;
F:\Program Files\Java\jre1.8.0_162\lib\jce.jar;
F:\Program Files\Java\jre1.8.0_162\lib\charsets.jar;
F:\Program Files\Java\jre1.8.0_162\lib\jfr.jar;
F:\Program Files\Java\jre1.8.0_162\classes

接着看下笔者的环境变量的配置:
这里写图片描述
这里写图片描述
从运行结果和环境变量的截图不难知道Boostrap ClassLoader主要加载的资源基本都是F:\Program Files\Java\jre1.8.0_162\lib(也就是%JAVA_HOME%\lib)里的。
这里写图片描述
在这的sunrsasign.jar笔者估计是直接从windows的界面是无法访问和显示的;而classes文件夹应该是对应于每一个project而生成隐含文件夹,windows也是不可以直接访问,如果各位有Dynamic Web Project的静态部署精力就应该会体会过web-inf下的自建classes。

  • Extension ClassLoader
    负责JRE的扩展目录中jar包的加载,主要在在jdk中jre的lib目录下ext目录的资源。我们通过同样的方式来输出看一下它加载的资源是哪些:
@Test
    public void outPutExtensionClassLoader(){
        System.out.println(System.getProperty("java.ext.dirs"));
    }
F:\Program Files\Java\jre1.8.0_162\lib\ext;
C:\WINDOWS\Sun\Java\lib\ext

这里写图片描述

  • App ClassLoader
    主要是加载当前Project(也称当前应用)中classpath下所有的类和外加引用的jar。
@Test
    public void outPutAppClassLoader(){
        System.out.println(System.getProperty("java.class.path"));
    }

这是输出

F:\RapidClipseWorkSpace\TestNewJRE\bin;
F:\RapidClipseWorkSpace\utils\cglib\asm-6.0.jar;
F:\RapidClipseWorkSpace\utils\cglib\cglib-3.2.6.jar;
F:\RapidClipseWorkSpace\utils\lombok\lombok.jar;
F:\RapidClipse\plugins\org.junit_4.12.0.v201504281640\junit.jar;
F:\RapidClipse\plugins\org.hamcrest.core_1.3.0.v201303031735.jar;
/F:/RapidClipse/configuration/org.eclipse.osgi/465/0/.cp/;
/F:/RapidClipse/configuration/org.eclipse.osgi/464/0/.cp/

这里写图片描述
我们现在稍微比较了解了这三类ClassLoader各自的作用,但是我们仍然不清楚的是他们之间关系是怎样的,下面笔者就来说说。

2.ClassLoader之间的关系

要讲的清楚,讲得明白ClassLoader之间的关系,就必须先说清楚ClassLoader的加载原理。
ClassLoader就是在jvm需要某个类的时候,通过.class文件(字节码文件,前面提到过的jar就是一堆编译好的.class)来加载并返回这类,而这种加载方式的实现靠的就是双亲委托模型。

  • 双亲委托模型
    在这个模型中,每一个ClassLoader实例都有一个父类Loader的引用。它的工作流程是这样的:当一个ClassLoader实例在被jvm要求下去加某一个类的时候,它会先去缓存中查找这个是否已经加载过了,如果是,那它就可以直接返回这个类给jvm;否则,它就会先委托给自己的父类Loader,让父类Loader去检查它(指的父类)有没有加载过这个类,如果还没有就一直委托到最高一级的Loader–Boostrap ClassLoader(可能是好几代的父类Loader了)去做同样的事;如果仍是没有,就反过来通知下一级让它自己去找,没找着就继续这个往下级通知的过程,直到通知到最开始的那个ClassLoader实例。最后找着了就进行加载,没找着报ClassNotFoundException。
    用一幅图来描述一下:
    这里写图片描述
    我们通过源码来加深下理解:
401     protected Class<?> loadClass(String name, boolean resolve)
402         throws ClassNotFoundException
403     {
404         synchronized (getClassLoadingLock(name)) {//当前的对象加锁
405             // First, check if the class has already been loaded
                //首先检查这个类是否加载过
406             Class<?> c = findLoadedClass(name);
407             if (c == null) {//如果没有,就委托父类
408                 long t0 = System.nanoTime();
409                 try {
410                     if (parent != null) {//如果父类存在,委托父类进行加载
411                         c = parent.loadClass(name, false);
412                     } else {
413                         c = findBootstrapClassOrNull(name);
414                     }
415                 } catch (ClassNotFoundException e) {
416                     // ClassNotFoundException thrown if class not found
417                     // from the non-null parent class loader
418                 }
419 
420                 if (c == null) {//如果父类也无法加载,就转告子类让它去寻找
421                     // If still not found, then invoke findClass in order
422                     // to find the class.
423                     long t1 = System.nanoTime();
424                     c = findClass(name);
425 
426                     // this is the defining class loader; record the stats
427                     sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
428                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
429                     sun.misc.PerfCounter.getFindClasses().increment();
430                 }
431             }
432             if (resolve) {//如果找到了就进行加载
433                 resolveClass(c);
434             }
435             return c;
436         }
437     }
1032    protected final Class<?> findLoadedClass(String name) {
1033        if (!checkName(name))
1034            return null;
1035        return findLoadedClass0(name);
1036    }
1037
1038    private native final Class<?> findLoadedClass0(String name);

所以, 这个委托的层次是这样的:自己定义的ClassLoader < App ClassLoader < Extension ClassLoader < Boostrap ClassLoader。

注意:当前应用在run/debug(启动)的时候,加载ClassLoader的顺序是刚好和委托的方向相反的。

我们来使用代码来获取一下它们的父类Loader:

@Test
    public void outPutTestObjClassLoader(){
        System.out.println(new TestObj().getClass().getClassLoader());
        System.out.println(new TestObj().getClass().getClassLoader().getParent());
        System.out.println("======================================");
        System.out.println(new TestObj().getClass().getClassLoader().getParent());
        System.out.println(new TestObj().getClass().getClassLoader().getParent().getParent());
        System.out.println("======================================");
        System.out.println(new TestObj().getClass().getClassLoader().getParent().getParent());
        System.out.println(new TestObj().getClass().getClassLoader().getParent().getParent().getParent());
    }

这里写图片描述
我们从这里不难得知:

  1. App ClassLoader的父类Loader就是Extension ClassLoader
  2. Extension ClassLoader的父类Loader居然是null
    其实,这个null就是Boostrap ClassLoader,不过它并不是Java中常规的类,而是使用c++实现、属于jvm层次的一种类。正因如此,它不能被常规意义上地继承,而只能说是被引用,而且它也不是某个更高级Loader的子类。

注意:如果需要自定义ClassLoader,那么你需要继承ClassLoader这个类,并重载(@Override)它的findClass方法,最后在findClass中调用defineClass方法(这个方法主要是将字节码文件.class转换为Class对象进行加载)。

827     protected final Class<?> defineClass(String name, java.nio.ByteBuffer b,
828                                          ProtectionDomain protectionDomain)
829         throws ClassFormatError
830     {
831         int len = b.remaining();
832 
833         // Use byte[] if not a direct ByteBufer:
834         if (!b.isDirect()) {
835             if (b.hasArray()) {
836                 return defineClass(name, b.array(),
837                                    b.position() + b.arrayOffset(), len,
838                                    protectionDomain);
839             } else {
840                 // no array, or read-only array
841                 byte[] tb = new byte[len];
842                 b.get(tb);  // get bytes out of byte buffer.
843                 return defineClass(name, tb, 0, len, protectionDomain);
844             }
845         }
846 
847         protectionDomain = preDefineClass(name, protectionDomain);
848         String source = defineClassSourceLocation(protectionDomain);
849         Class<?> c = defineClass2(name, b, b.position(), len, protectionDomain, source);
850         postDefineClass(c, protectionDomain);
851         return c;
852     }
853 
854     private native Class<?> defineClass0(String name, byte[] b, int off, int len,
855                                          ProtectionDomain pd);
856 
857     private native Class<?> defineClass1(String name, byte[] b, int off, int len,
858                                          ProtectionDomain pd, String source);
859 
860     private native Class<?> defineClass2(String name, java.nio.ByteBuffer b,
861                                          int off, int len, ProtectionDomain pd,
862                                          String source);

总结下来,这三类加载器的关系是一种类似与一层一层委托的关系,其中我们还得知了App ClassLoader是所有当前应用中所有类的加载器,这就够了。
我们之前的JDK Proxy源码是这样的:

 /**
         * 原本,正常的emp实现的接口功能是这样的
         */
        Person e = new Emp(666L);
        e.testMethod();
        e.testMethod2();
        e.testMethod3("shawn");

        System.out.println("---------------------");
        /**
         * 使用代理(proxy)之后,可以增添新的功能,相当于装饰,而不需要去改变原本的类
         */
        TestInvocationHandler t = new TestInvocationHandler(e);
        Person eProxy = (Person)Proxy.newProxyInstance(e.getClass().getClassLoader(), 
                e.getClass().getInterfaces(), t);
        eProxy.testMethod();
        eProxy.testMethod2();
        eProxy.testMethod3("shawn");

我们稍改一下

       * 原本,正常的emp实现的接口功能是这样的
         */
        Person e = new Emp(666L);
        e.testMethod();
        e.testMethod2();
        e.testMethod3("shawn");

        System.out.println("---------------------");
        /**
         * 使用代理(proxy)之后,可以增添新的功能,相当于装饰,而不需要去改变原本的类
         */
        TestInvocationHandler t = new TestInvocationHandler(e);
        Person eProxy = (Person)Proxy.newProxyInstance(this.getClass().getClassLoader(), 
                e.getClass().getInterfaces(), t);
        eProxy.testMethod();
        eProxy.testMethod2();
        eProxy.testMethod3("shawn");

这里写图片描述
再修改一下

@Test
    public void testFunc() throws Throwable{
        /**
         * 原本,正常的emp实现的接口功能是这样的
         */
        Person e = new Emp(666L);

        System.out.println("---------------------");
        System.out.println(this.getClass() + "====" + this.getClass().getClassLoader());
        System.out.println(this.getClass() + "====" + new TestObj().getClass().getClassLoader());
        System.out.println(e.getClass() + "====" + e.getClass().getClassLoader());
        /**
         * 使用代理(proxy)之后,可以增添新的功能,相当于装饰,而不需要去改变原本的类
         */
        TestInvocationHandler t = new TestInvocationHandler(e);
        Person eProxy = (Person)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), //这个线程中的类加载器,每个线程中都有自己类加载器,它也是通过Adpp ClassLoader加载的
                e.getClass().getInterfaces(), t);
        eProxy.testMethod();
        eProxy.testMethod2();
        eProxy.testMethod3("shawn");
    }
@Test
    public void outPutTestObjClassLoader(){
        System.out.println(new TestObj().getClass().getClassLoader());
        System.out.println(new TestObj().getClass().getClassLoader().getParent());
        System.out.println("======================================");
        System.out.println(new TestObj().getClass().getClassLoader().getParent());
        System.out.println(new TestObj().getClass().getClassLoader().getParent().getParent());
        System.out.println("======================================");
        System.out.println(new TestObj().getClass().getClassLoader().getParent().getParent());
        //System.out.println(new TestObj().getClass().getClassLoader().getParent().getParent().getParent());//这里去掉注释是因为得不到这个加载器会报异常
    }

这里写图片描述

上面创建JDK Proxy的方法我们再稍微改造,进行以下封装(虽然使用框架后就没什么用了!)

package com.unicorn.reflect.pojo;

public interface ProxyFactory {
    //工厂仍是以接口的方式使用,加快加载的速度
    public <T> T getProxy(final Object tar);
}
package com.unicorn.reflect.pojo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class JDKProxyFactory implements ProxyFactory {

    @Override
    public Object getProxy(final Object tar) {
        return Proxy.newProxyInstance(tar.getClass().getClassLoader(),tar.getClass().getInterfaces(), new InvocationHandler() {//使用匿名内部类实现

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // TODO Auto-generated method stub
                        System.out.println("before " + method.getName());

                        Object result = null;
                        try {
                            result = method.invoke(tar, args);
                        } catch (NullPointerException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }

                        System.out.println("after " + method.getName());
                        return result;
                    }
                });
    }

}

3.cglib

cglib的动态代理是和JDK Proxy很不一样的,这里就先不做源码的分析了,直接上一个封装(这个也是用框架后就没什么用了的!都是annotation的动态注入了!只能自己凑合着玩!)

package com.unicorn.reflect.pojo;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxyFactory implements ProxyFactory {

    private final Enhancer en = new Enhancer();

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getProxy(Object tar) {
        // TODO Auto-generated method stub

        //进行代理
        en.setSuperclass(tar.getClass());
        en.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

                Object result = null;
                try{
                    //前置通知
                    result = methodProxy.invokeSuper(o, args);
                    //返回通知, 可以访问到方法的返回值
                    System.out.println(String.format("after method:%s execute", method.getName()));
                } catch (Exception e){
                    e.printStackTrace();
                    //异常通知
                }
                //后置通知
                System.out.println("[after]");
                return result;
            }
        });
        //生成代理实例
        return (T)en.create();
    }
}

这次就先到这了!这是一篇难写的blog!
想看ClassLoader自己实现的就看这篇blog
转载请注明出处,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值