您的位置:首页 > 编程语言 > Java开发

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

2018-01-26 22:09 417 查看
这次我们来讲讲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
fcc2
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());
}




我们从这里不难得知:

App ClassLoader的父类Loader就是Extension ClassLoader

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

转载请注明出处,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: