------- android培训、java培训、期待与您交流! ----------
44.类加载器及其委托机制的深入分析
1.类加载器
1)类加载器及其作用:
字节码的原始信息存放在硬盘上的classpath指定的目录下,java程序用到某个类,虚拟机要先
将该类的字节码加载到内存里,进行处理后得到的就是字节码。实现这个过程的机制就是类加载器,
其作用就是加载类。
2)类加载器BootStrap
类加载器也是java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有
第一个不是java类的类加载器,它就是BootStrap.
BootStrp是JVM里的第一个类加载器,它不是java类,不需要被加载,它是嵌套在JVM内核里的,
它是用C++写的二进制代码。
3)java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定
位置的类:
BootStrap,ExtClassLoader,AppClassLoader
4)类加载器的树形结构
注:每个ClassLoader本身只能分别加载特定位置和目录中的类。
<1>用eclipse的打包工具将ClassLoaderTest输出成jre/lib/ext 目录下的itcast.jar包
ClassLoaderTest.java上右键-->Export(输出)-->java--JAR file-->Next-->
JAR file:c:\java\jdk1.6\jre\lib\ext\itcast.jar-->finish
<2>这时classpath目录和ext/itcast.jar包中都有ClassLoaderTest.class,但运行结果却是:
ExtClassLoader,这就有一个优先级的问题。
2.类加载器的委托加载机制
1)当java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
<1>首先当前线程的类加载器去加载线程中的第一个类。
<2>如果类A中引用了类B,java虚拟机将使用加载类A的类装载器来加载类B。
<3>还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
2)每个类加载器加载类时,又先委托给其上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者加载器,还加载不了,则抛出ClassNoFoundException,
而不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那么多儿子,找哪一个呢?
3.面试题:能不能自己写个类叫java.lang.System?
写了也加载不到,因为java中的类加载器采用委托机制,总是保证父类类加载器优先,也就是
总是使用父类们能找到的类,这样就总是使用java系统提供的System.除非自己写个类加载器
来加载自己指定目录下的类。
4.补充:
1)Thread类中方法:
ClassLoader getContextClassLoader()
返回该线程的上下文 ClassLoader。
void setContextClassLoader(ClassLoader cl)
设置该线程的上下文 ClassLoader。
2)ClassLoader类(java.lang)
<1>类加载器是负责加载类的对象。ClassLoader 类是一个抽象类。
每个 Class 对象都包含一个对定义它的 ClassLoader 的引用。
数组类的 Class 对象不是由类加载器创建的,而是由 Java 运行时根据需要自动创建
<2>构造方法摘要
protected ClassLoader()
使用方法 getSystemClassLoader() 返回的 ClassLoader 创建一个新的类加载器,将该加载器作为父类加载器。
protected ClassLoader(ClassLoader parent)
使用指定的、用于委托操作的父类加载器创建新的类加载器。
ClassLoader getParent()
返回委托的父类加载器。
<3>方法摘要:
Class<?> loadClass(String name)
使用指定的二进制名称来加载类。
protected Class<?> loadClass(String name, boolean resolve)
使用指定的二进制名称来加载类。
3) Class类中方法:
ClassLoader getClassLoader()
返回该类的类加载器。
public class a44_类加载器 {
public static void main(String[] args) throws Exception {
System.out.println(a44_类加载器.class.getClassLoader().getClass().getName());
System.out.println(System.class.getClassLoader());
ClassLoader loader=a44_类加载器.class.getClassLoader();
while(loader!=null){
System.out.println(loader.getClass().getName());
loader=loader.getParent();//返回父加载器
}
System.out.println(loader);
Class class1=new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachment");
Date d1=(Date)class1.newInstance();
System.out.println(d1);
}
}
45.自定义类加载器的编写原理分析:
1.原理分析:
ClassLoader中的loadClass()方法是保证委托机制流程的方法,故只能继承,不能复写,查找类要用到
的findClass需要复写,将得到的class文件的内容转成字节码的方法是defineClass().也不需要复写。
2.类 ClassLoader(java.lang)
1)构造方法摘要
protected ClassLoader()
使用方法 getSystemClassLoader() 返回的 ClassLoader 创建一个新的类加载器,将该加载器作为父类加载器。
protected ClassLoader(ClassLoader parent)
使用指定的、用于委托操作的父类加载器创建新的类加载器。
2)方法摘要
protected Class<?> defineClass(byte[] b, int off, int len)
已过时。 由 defineClass(String, byte[], int, int) 取代
protected Class<?> defineClass(String name, byte[] b, int off, int len)
将一个 byte 数组转换为 Class 类的实例。
protected Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain)
使用可选的 ProtectionDomain 将一个 byte 数组转换为 Class 类的实例。
protected Class<?> defineClass(String name, ByteBuffer b, ProtectionDomain protectionDomain)
使用可选的 ProtectionDomain 将 ByteBuffer 转换为 Class 类的实例。
protected Class<?> findClass(String name)
使用指定的二进制名称查找类。
protected Class<?> findLoadedClass(String name)
如果 Java 虚拟机已将此加载器记录为具有给定二进制名称的某个类的启动加载器,则返回该二进制名称的类。
ClassLoader getParent()
返回委托的父类加载器。
static ClassLoader getSystemClassLoader()
返回委托的系统类加载器。
Class<?> loadClass(String name)
使用指定的二进制名称来加载类。
protected Class<?> loadClass(String name, boolean resolve)
使用指定的二进制名称来加载类。
46.编写对class文件进行加密的工具类
1.编写自己的类加载器
1)知识讲解:
<1>自定义的类加载器必须继承ClassLoader
<2>loadClass方法与findClass方法
<3>defineClass方法
2)编程步骤:
<1>编写一个对文件内容进行简单加密的程序。
<2>编写了一个自己的类加载器,可实现对加密过的类进行装载和解密。
<3>编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,
因为编译器无法识别这个类,程序中可以除了使用ClassLoader.load方法之外,
还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName.
3)实验步骤:
<1>对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如:
java MyClassLoader MyTest.class F:\itcast
<2>运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为
AppClassLoader:java MyClassLoader MyTest F:\itcast
<3>用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,
错误说明是AppClassLoader类加载器失败。
<4>删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。
2.在eclipse中编写代码的实践操作步骤:
1)创建类加载器MyClassLoader类,同时也作为加密工具。
2)创建要被加密的类ClassLoaderAttachment(设置其父类为Date).
3)创建新文件夹itcastlib,用于放置加密后的文件:
工程-->右键-->New-->Folder-->Folder name:itcastlib-->Finish
4)设置类加载器MyClassLoader的运行参数:
右键-->Run As-->Run Configurations-->Arguments-->Program arguments:分别为ClassLoaderAttachment
所在的路径(要加引号)及加密后放置文件的目录:itcastlib
注:同时检查主函数Main的设置是不是当前类
获得路径的简单方式:打开方件所在目录-->打开运行-->将文件拉到运行中复制即可。
5)在ClassLoaderTest类中运行ClassLoaderAttachment类的toString()方法--->hello,itcast
6)用itcastlib文件夹中的加密后的ClassLoaderAttachment.class 复盖掉加密前的同名该文件。
7)再在CLassLoaderTest类中运行,则报错xception in thread "main" java.lang.ClassFormatError: Incompatible magic value
47.编写和测试自己编写的解密类加载器
1.相关知识点:
1)ByteArrayOutputStream类(java.io)
<1>此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。
可使用 toByteArray() 和 toString() 获取数据。 关闭 ByteArrayOutputStream 无效。
此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException
<2>构造方法摘要
ByteArrayOutputStream()
创建一个新的 byte 数组输出流。
ByteArrayOutputStream(int size)
创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量(以字节为单位)。
<3>方法摘要
void close()
关闭 ByteArrayOutputStream 无效。
byte[] toByteArray()
创建一个新分配的 byte 数组。
String toString()
使用平台默认的字符集,通过解码字节将缓冲区内容转换为字符串。
void write(byte[] b, int off, int len)
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此 byte 数组输出流。
void write(int b)
将指定的字节写入此 byte 数组输出流。
2)类 ByteArrayInputStream(java.io)
<1>ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。
内部计数器跟踪 read 方法要提供的下一个字节。 关闭 ByteArrayInputStream 无效。
此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
<2>构造方法摘要
ByteArrayInputStream(byte[] buf)
创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组。
ByteArrayInputStream(byte[] buf, int offset, int length)
创建 ByteArrayInputStream,使用 buf 作为其缓冲区数组。
<3>方法摘要
void close()
关闭 ByteArrayInputStream 无效。
int read()
从此输入流中读取下一个数据字节。
int read(byte[] b, int off, int len)
将最多 len 个数据字节从此输入流读入 byte 数组。
3)loadClass()会自动调用findClass()方法。
48.类加载器的一个高级问题的实验分析
在eclipse中的演示步骤:
1.创建web项目itcastweb
右键--->New-->Web Project-->itcastweb
2.创建web中的类Servlet(web中的类是特殊的类,其父类是Servlet,故可直接创建Servlet)
在src上右键-->New-->Servlet-->
package:cn.itcast.itcastweb.web.servlets(界面层)
Name: MyServlet
Superclass:javax.servlet.http.HttpServlet
(只勾选doGet做演示)
3.配置服务器
选择web工程-->点击Project Deployments(在工具栏环表箭头的图标)-->Add-->Server:Tomcat 6.x
-->Finish-->OK
(配置完后在目录apache-tomcat-6.0\webapps下就有itcastweb文件夹)
4.启动Tomcat,即双击startup.bat
5.在浏览器中访问Tomcat服务器
http://localhost:8080/itcastweb/servlet/MyServlet(这是项目内的路径)
(查看内容:itcastweb-->webRoot-->WEB-INF-->web.xml-->source)
-->访问的结果打印到了后台
6.重载Tomcat,则会在浏览器显示Tomcat自己的类加载器:WebappClassLoader
7.将MyServlet.java打成jar包输出到JDK下
MyServlet.java-->右键-->Export-->java--JAR file-->Next-->Next-->Finish
(输出的JDK版本要和Tomcat配置的JDK一致,可查看Tomcat的配置,即将startup.bat放在
编辑器中打开:set JAVA_HOME=c:\java\JDK6.0)
8.重启Tomcat
9.在浏览器中再次查看,报错:没找到HttpServlet.
(这是因为,这时的发起者加载器是ExtClassLoader,而HttpServlet不在Ext目录下)
10.将HttpServlet的jar包也放在Ext目录下。
HttpServlet是Tomcat提供的,在apache-tomcat-6.0\lib\servlet-api.jar,复制到JDK6.0\jre\lib\ext下。
11.重启Tomcat(即将JVM重启)
12.再在浏览器中访问
此时显示的加载器便是ExtClassLoader了。
注:要特别掌握配置服务器