写在前面:但愿你们能够踊跃发言,你们阅读后有什么感觉或者心得体会以及建议均可以在下方留言板小程序内留言的,根据你的留言,郭老师会及时调整讲解内容作一些优化和改进,得不到你们的反馈,不知道讲解是否有问题,是否符合你们的口味以及可否帮助到你们java
首先要明白一个问题:为何要写本身的Class Loader?无论那么多不是同样好好在用吗?通常状况确实是这样,不过有些时候为了一些特殊需求,咱们会用到本身定制的Class Loader。好比1998年,Sun内部为完成JDK1.2忙得热火朝天,我也在里面打酱油,咱们有一个小组提供一个工具,给Java生成的字节码加密,缘由是字节码太规整,用一些工具很容易反编译,反编译以后的结果很容易供人看懂(比许多程序员手工编的程序还容易懂),致使知识产权保护不力。因而就想着把.class文件加密,可是一个加密以后的.class文件确定又不能被正确加载。怎么办呢?就要本身作一个Class Loader,拿到.class文件,先进行一步解密,解密以后就成了正常的字节码,就能够用普通的application class loader的方式去继续加载了。程序员
还有一些别的场景,也须要咱们自定义Class Loader,如动态生成的class文件,如多版本class运行。编程
咱们先来看,从哪里下手作这个工做。小程序
从大的过程,对象的建立分红两大步骤,一个是类级别的工做,一个是对象的实例化。显然,咱们要在类级别工做着一个步骤动脑筋。而在类级别的工做中,分红加载Loading,连接Linking,初始化Initialization三步。根据上面讲解的一些知识,咱们应该在Loading过程当中搞一点名堂。数组
回到ClassLoader的定义,咱们来看提供了哪些方法。网络
public Class<?> loadClass(String name, boolean resolve) throwsClassNotFoundException
这个loadClass()方法根据类的全名加载类。既然是这样,那就应该从这里下手了。咱们看看源码:app
protectedClass<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized(getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name,false);
} else {
c =findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown ifclass not found
// from the non-null parent classloader
}
if (c == null) {
// If still not found, then invokefindClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
咱们能够看到这个过程,先看这个类是否是已经加载了,若是没有就让上层的class loader去加载,最后由本身加载findClass(name)。框架
事情就追到findClass(name)方法了。看它的定义:ide
protected Class<?> findClass(String name) throwsClassNotFoundException
在ClassLoader里面,它只是一个空的方法,没有作实现。那么咱们就能够实现它来进行咱们本身的工做了。工具
好,咱们在findClass()里面作什么呢?天然先要从外部如文件系统或者网络获取.class字节流,而后呢?咱们本身把它弄成类模型吗?理论上是,可是实际上咱们不须要。接着找ClassLoader为咱们提供了什么,咱们能够看到有一个defineClass()方法,定义以下:
protected final Class<?>defineClass( String name, byte[] b, intoff, int len) throws ClassFormatError
这个方法就是用于将字节流转成类的。
而且这个方法是final的,咱们用它而且只能用它。
有了这个方法,咱们本身的工做就简单了,只须要对字节流进行处理后交给defineClass()就行了。
咱们能够动手编程序了。
先作一个类,MyClass.java,代码以下:
package com.demo;
public class MyClass {
public MyClass() {
}
public void show() {
System.out.println("show test!");
}
}
很简单,不解释了。
接着咱们写本身的classloader,为了简单起见,咱们的这个class loader不进行任何加密处理,只是简单地读取.class文件,生成类,模仿标准的AppClassLoader的行为。MyClassLoader.java代码以下:
package com.demo;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class MyClassloader extends ClassLoader {
@Override
protectedClass<?> findClass(String name) throws ClassNotFoundException {
StringclassFileName = name.replace(".","/") + ".class";
byte[] cLassBytes =null;
Path path = null;
try {
path =Paths.get(getResource(classFileName).toURI());
cLassBytes =Files.readAllBytes(path);
} catch (IOException |URISyntaxException e) {
e.printStackTrace();
}
Class<?> clazz = defineClass(name,cLassBytes, 0, cLassBytes.length);
return clazz;
}
}
正如上面根据原理分析的,咱们本身写的class loader override了findClass(),这里面咱们作了几步:
一是根据class名字拼出文件名,StringclassFileName = name.replace(".","/") + ".class";;
二是根据名字定位到文件路径,path = Paths.get(getResource(classFileName).toURI());;
三是把文件读到字节数组中,cLassBytes= Files.readAllBytes(path);;
四是调用defineClass()生成类,Class<?>clazz = defineClass(name, cLassBytes, 0, cLassBytes.length);。
有了这个自定义的classloader以后,咱们写一个测试类,MyClassTest.java,代码以下:
package com.demo;
import java.lang.reflect.Method;
public class MyClassTest {
public static voidmain(String[] args) throws ClassNotFoundException {
MyClassloader loader =new MyClassloader();
Class<?> aClass= loader.findClass("com.demo.MyClass");
try {
Object obj =aClass.newInstance();
Method method =aClass.getMethod("show");
method.invoke(obj);
} catch (Exception e){
e.printStackTrace();
}
}
}
在程序中,咱们新建一个自定义的类加载器MyClassloader loader = new MyClassloader();
而后调用findClass()去加载类Class<?>aClass = loader.findClass("com.demo.MyClass");
以后根据类定义建立新的对象实例并调用方法:
Object obj =aClass.newInstance();
Method method =aClass.getMethod("show");
method.invoke(obj);
试着运行一下,程序正确出告终果。
到了这一步,恭喜你!你已经知道如何作Class Loader了,We Made It!
再回到加密的问题,这一讲不讲加密自己,可是咱们要根据上面的代码知道如何处理加密。看咱们本身的findClass()的实现,里面有两句话:
cLassBytes = Files.readAllBytes(path);
Class<?> clazz = defineClass(name, cLassBytes, 0, cLassBytes.length);
在defineClass以前,咱们要准备出一个正常的字节数组,所以对于一个加密的.class文件,咱们只须要在以前处理进行解密便可:
cLassBytes = decrypt(Files.readAllBytes(path));
Class<?> clazz = defineClass(name, cLassBytes, 0,cLassBytes.length);
假定有一个decrypt()方法,咱们就能够了作到了。
之前在Sun公司的时候,一个同事曾经说过class loader就像是一个汤匙,用它来抓汤圆吃。这个比喻真有点像。不过这个比喻不够,由于class loader不光是加载类,还规定了命名空间,不一样的class loader加载的类是不能互相访问的(正由于这样才会有同一个类的多版本支持的技术)。咱们能够把class loader圈起来的这片空间理解为class园地。所以,class loader更加像是一个碗,把汤圆盛到里头了。
这个话题超越了进阶阶段,先按下不表。不过到了这里,咱们应该能感觉到Class Loader的强大了,从心底里对Java的发明人一次次投以敬佩的目光,他们二十几年前居然有如此深入的洞察。对咱们Java程序员来说,James Gosling就是咱们的上帝。在将来的讲座里,我还会跟你们一次次展现出造物者的匠心独运。
学习的过程,是一步步深刻的。不少人工做多年,并无深刻,有些时候也不是由于不努力,而是由于他们在的编程任务老是在无休止地对付客户需求的变动,没有坐下来好好整理。而从应用的层面,学习技术就成了追逐新的热点,不断地换最新推出的环境框架工具。而写应用程序,大致上用不到深刻的内容,非专业的人突击学几个月以后也能一块儿作编程了,有的时候比本专业的人作得还要好。那么学习本专业的价值在哪里?这是许多年轻人曾经的疑惑。或许与国家的发展阶段有关,前些年都是拿别人现成的平台框架工具,或开源或盗版,本身针对客户须要快速搭建应用系统,赚快钱,如今慢慢认识到基础的重要性了,真正愿意深刻理解技术的人和公司也多起来了。这对于但愿掌握和使用更深技术的程序员是一件好事情。
从事技术工做是一个良心活儿,学习技术则是一个慢工夫,急不来,就得要老老实实一个课题一个课题解决掉。只要方向对,扎扎实实,总能有成。
先贤语录:不积跬步,无以致千里。