类加载器,类加载
类加载器,类加载
------- android培训、java培训、java学习型技术博客、期待与您交流 ----------
类加载器
顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:
Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成java.lang.Class
类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的newInstance()
方法就可以创建出该类的一个对象。
Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:
BootStrap,ExtClassLoader,AppClassLoader
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
类加载器之间的父子关系和管辖范围图:
ps:BootStrap负责JDK安装目录下的\jre\lib\rt.jar,也就是java提供的类(注意:它不是一个java类)
因为java类加载器本身也是对象,所以它们也需要被类加载器加载,所以必须有一个类加载器不是java类,这个类加载器就是BootStrap
ExtClassLoader负责JDK安装目录下的\jre1.7.0\lib\ext中的jar包,也就是自己指定的扩展jar包中的类。
AppClassLoader负责classpath指定目录下的jar包或者.class文件。
下面这个程序可以验证以上说法:
package com.itheima.day2;
import java.util.Date;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
//打印负责现在所执行的这个类的类加载器的名字
System.out.println(ClassLoaderTest.class.getClassLoader().getClass()
.getName());
//因为System类是有BootStrapLoader加载的,它不是一个类,不能用getClass方法,当然没有名字
System.out.println(System.class.getClassLoader());
//打印负责本类的类加载器及其“父”类加载器
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while(loader!=null){
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
System.out.println(loader);
}
}
运行结果如下:
sun.misc.Launcher$AppClassLoader
null
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null
如果我们将ClassLoaderTest这个类的class文件生成jar包并放在JDK安装目录下的\jre1.7.0\lib\ext中,那么
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());的执行结果会变成:
sun.misc.Launcher$ExtCassLoader
而且就算当前的classpath下还有,ClassLoaderTest.class文件,结果还是会显示为ExtClassLoader加载的,那么当我们需要使用一个类的时候到底用哪个类加载器呢?这就引
入了类加载器的委托机制
类加载器的委托机制
Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
◆ 先当前线程的类加载器去加载线程中的第一个类。
◆ 如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
◆ 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
◆每个类加载器加载类时,又先委托给其上级类加载器。
◆ 当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的下级类加载器!
ps:其委托顺序也就是:
首先当前线程的类加载器“接过任务”(先不处理)---->交给其上级类加载器(先不处理)---->交给其上级类加载器(先不处理)。。。。------>交给祖宗类加载器
如果祖宗类加载器可以处理则结束--->如果不能处理则交给其下级类加载器------>如果有回到了当前线程的类加载器还是不能处理,则抛出ClassNoFoundException
所以通常不能写java.lang.System这样的类,因为就算写了,当前线程的类加载器会逐级将其交给BootStrapLoader,然后它会加载rt.jar中的System类,所以写了没用
编写自己的类加载器
1) 编程步骤:
2) 编写一个对文件内容进行简单加密的程序。
3) 编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
4) 编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用ClassLoader.load方法之外,还可以使用设
置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。
MyClassLoader类(创建加密方法,覆盖findClass):
代码如下:
package com.itheima.day2;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader{//继承classLoader
/**
* @param args
*/
public static void main(String[] args)throws Exception {
//源路劲
String srcPath = args[0];
//目标路劲
String destDir = args[1];
//定义一个输入流,接受源路劲
FileInputStream fis = new FileInputStream(srcPath);
//通过源路劲获取文件名
String destFileName = srcPath.substring(srcPath.lastIndexOf("\\")+1);
//获取目标路劲
String destPath=destDir+"\\"+destFileName;
FileOutputStream fos = new FileOutputStream(destPath);
//调用加密方法
cypher(fis,fos);
//关闭流
fis.close();
fos.close();
}
//加密方法(同时可以解密)
private static void cypher(InputStream ips ,OutputStream ops)throws Exception{
int b = -1;
while((b=ips.read())!=-1){
//在写入之前进行异或运算
ops.write(b ^ 0xff);
}
}
//定义一个成员变量代表传入的路劲
private String classDir;
//覆盖findCass方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//得到完整的文件路劲
String classFileName = classDir+"\\"+name+".class";
try {
//定义字节流并关联文件
FileInputStream fis = new FileInputStream(classFileName);
//定义一个字节数组流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//将文件解密后存入字节数组流中
cypher(fis,bos);
fis.close();
//得到字节数组
byte[] bytes = bos.toByteArray();
//通过defineClass方法将字节数组变成class对象
return defineClass(bytes, 0, bytes.length);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.findClass(name);
}
//定义两个构造方法
public MyClassLoader(){
}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
}
ClassLoaderAttachment类(用来被加密的,覆盖toString()方法)
代码如下:
package com.itheima.day2;
import java.util.Date;
//继承自Date类,并覆盖toString方法
public class ClassLoaderAttachment extends Date {
public String toString(){
return "hello itheima";
}
}
ClassLoaderTest类(创建MyClassLoader对象,用于测试)
关键代码如下:
Class clazz = new MyClassLoader("itheimalib").loadClass("ClassLoaderAttachment");
//这时候源程序中不能出现ClassLoaderAttachment类的类名,所以转换为Date类型
Date d1 = (Date)clazz.newInstance();
System.out.println(d1);
实验过程:
1,使用加密器对ClassLoaderAttachment对象进行加密,加密后的ClassLoaderAttachment.clas文件存入另一目录"itheimalib"中
2,删除当期classpath下的ClassLoaderAttachment.class对象
3,在ClassLoader类中创建MyClassLoader对象,通过自定义类加载器进行加载
运行结果如下:
hello itheima
运行结果说明自定义类加载器加载成功。
4,通过上级类加载器进行加载
将Class clazz = new MyClassLoader("itheimalib").loadClass("ClassLoaderAttachment");改为:
Class clazz = new MyClassLoader("itheimalib").loadClass("con.itheima.day2.ClassLoaderAttachment");
并将itheima路劲下的ClassLoaderAttachemnt.class文件拷贝至当前classpath下。
运行结果如下:
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 889275713 in class file com/itheima/day2/ClassLoaderAttachment
加载失败,因为已被加密,上级类加载器中没有解密的方法,所以会出现Incompatible magic
------- android培训、java培训、java学习型技术博客、期待与您交流 ----------
相关文章
- 暂无相关文章
用户点评