Deep learning about Java--贯穿Java的反射机制(4)
2018-01-26 22:09
417 查看
这次我们来讲讲ClassLoader和CGLib。
要解答这些问题,就和笔者一同进行这个天坑之旅吧!
在更前面一篇的文章中,笔者写过三中类加载器,分别是Bootstrap ClassLoader、Extension ClassLoader和System ClassLoader(又称App ClassLoader),要想回答钱前面的问题就必须说说这三种加载器和他们之间的关系。
Boostrap Classloader
这个类加载器主要是加载JDK中的核心类库,我们先看下它的源码,再来把他们输出一遍记录下这些核心类库。
jdk手册
接着看下笔者的环境变量的配置:
从运行结果和环境变量的截图不难知道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目录的资源。我们通过同样的方式来输出看一下它加载的资源是哪些:
App ClassLoader
主要是加载当前Project(也称当前应用)中classpath下所有的类和外加引用的jar。
这是输出
我们现在稍微比较了解了这三类ClassLoader各自的作用,但是我们仍然不清楚的是他们之间关系是怎样的,下面笔者就来说说。
ClassLoader就是在jvm需要某个类的时候,通过.class文件(字节码文件,前面提到过的jar就是一堆编译好的.class)来加载并返回这类,而这种加载方式的实现靠的就是双亲委托模型。
双亲委托模型
在这个模型中,每一个ClassLoader实例都有一个父类Loader的引用。它的工作流程是这样的:当一个ClassLoader实例在被jvm要求下去加某一个类的时候,它会先去缓存中查找这个是否已经加载过了,如果是,那它就可以直接返回这个类给jvm;否则,它就会先委托给自己的父类Loader,让父类Loader去检查它(指的父类)有没有加载过这个类,如果还没有就一直委托到最高一级的Loader–Boostrap ClassLoader(可能是好几代的父类Loader了)去做同样的事;如果仍是没有,就反过来通知下一级让它自己去找,没找着就继续这个往下级通知的过程,直到通知到最开始的那个ClassLoader实例。最后找着了就进行加载,没找着报ClassNotFoundException。
用一幅图来描述一下:
我们通过源码来加深下理解:
所以, 这个委托的层次是这样的:自己定义的ClassLoader < App ClassLoader < Extension ClassLoader < Boostrap ClassLoader。
注意:当前应用在run/debug(启动)的时候,加载ClassLoader的顺序是刚好和委托的方向相反的。
我们来使用代码来获取一下它们的父类Loader:
我们从这里不难得知:
App ClassLoader的父类Loader就是Extension ClassLoader
Extension ClassLoader的父类Loader居然是null
其实,这个null就是Boostrap ClassLoader,不过它并不是Java中常规的类,而是使用c++实现、属于jvm层次的一种类。正因如此,它不能被常规意义上地继承,而只能说是被引用,而且它也不是某个更高级Loader的子类。
注意:如果需要自定义ClassLoader,那么你需要继承ClassLoader这个类,并重载(@Override)它的findClass方法,最后在findClass中调用defineClass方法(这个方法主要是将字节码文件.class转换为Class对象进行加载)。
总结下来,这三类加载器的关系是一种类似与一层一层委托的关系,其中我们还得知了App ClassLoader是所有当前应用中所有类的加载器,这就够了。
我们之前的JDK Proxy源码是这样的:
我们稍改一下
再修改一下
上面创建JDK Proxy的方法我们再稍微改造,进行以下封装(虽然使用框架后就没什么用了!)
这次就先到这了!这是一篇难写的blog!
想看ClassLoader自己实现的就看这篇blog。
转载请注明出处,谢谢!
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。
转载请注明出处,谢谢!
相关文章推荐
- Deep learning about Java--贯穿Java的反射机制(3)
- Deep learning about Java--贯穿Java的反射机制(1)
- java中的反射机制浅析(转帖)
- 【转载】JAVA利用反射机制访问private成员和方法
- 居于反射机制Java动态调用jar方法
- Java学习之反射机制学习总结--1
- 深入理解Java:类加载机制及反射
- java的反射机制
- java类反射机制创建对象
- java的反射机制浅谈(转)
- JAVA中的反射机制
- JAVA中反射机制一
- JAVA中反射机制二
- JAVA中的反射机制详解
- Java通过反射机制修改类中的私有属性的值
- Java 反射机制详解及实例
- JAVA的反射机制(reflect)
- Java的反射机制
- JAVA中的反射机制
- java 反射机制