------- android培训、java培训、期待与您交流! ----------
每一个class文件 每次我们用的时候 总是要先加载到内存上, 然后再编译成字节码文件.. 这时候涉及到了一个问题. 用什么来加载呢? 本文要叙述的问题就是这个, 关于类加载器
首先了解3个东西, bootStrap. ExtClassLoader , appClassLoader
BootStrap返回的是一个null, BootStrap是跟节点, 如果在BootStrap里找到对应的加载对象, 将返回null
默认的加载器是appClassLoader
类加载器的加载机制是由下往上加载,类加载器的委托机制,为什么说是由下往上, 因为BootStrap先加载上来的, 由BootStrap去加载各个加载器,但是默认的加载器又是appClassLoader, 所以成了由下往上加载, appClassLoader下就是自定义的类加载器, 如下图:
先看一个简单的类加载器配置读取文件的例子
public static void main(String[] args) throws Exception{
//首先我们需要配置config.properties文件className=java.util.HashSet
InputStream ips = new FileInputStream("config.properties"); //其实个人还是比较习惯这种做法, 方便
//java的类加载器提供了一个加载类文件流的方法叫做getResourceAsStream
//getResourceAsStream //在classPath 指定的目录下逐一的查找要加载的文件(张老师官方说法)
//路径前面不能加 "/";
InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");
//classn内部提供了getResourceAsStream直接加载文件的配置,
//底层也是调用了getClassLoader(); 用系统调用的可以省略不写绝对路径
InputStream ips = ReflectTest2.class.getResourceAsStream("resources/config.properties");
// "/" 路径问题,不多做解释, 绝对路径和相对路径都行
InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/resources/config.properties");
Properties props = new Properties(); // Properties可以看成是一个map集合, 返回是key 跟 value ;
props.load(ips); //加载一个文件读取流
ips.close();
String className = props.getProperty("className"); //获取到value
}
鉴于类加载器的委托机制,我们可以得出这样的结论:自定义类加载器也需要类加载器来加载,那么就由默认的类加载器,默认的加载器是appClassLoader,也就是说,自定义类加载器的父加载器是appClassLoader,这里我们需要考虑 到类加载器的委托机制,默认的发起者是appClassLoader。如果有存在继承或者实现接口的话,那么被继承或者被实现的那个类的加载器,是由继承或者实现类的这个类的类加载器来加载(有点拗口),如图, A类引用了B类,B类的加载器是使用A类的加载器比如说A类的加载器被ExtClassLoader加载到,A类继承了B类,那么B类也必须是被ExtClassLoader加载。类加载器还可以指定某个方法去加载某个类,他们的父加载器是app加载器,再次强调,自定义加载器被加载,他们的父加载器是appClassLoader。
看完上面的解释后,再来看下面这个自定义类加载器示例
思路:第一,首先要有一个加密的class文件。
public class ClassLoaderAttachment extends Date {
@Override
public String toString(){
return "hello,itcast";
}
}
第二,自定义类加载器必须继承ClassLoader,并且实现ClassLoader里的findClass方法,ClassLoader的默认加载器是
appClassLoader,所以,当自定义加载器由父加载器加载到子加载器appClassLoader后就不会再找自定义加载器了,换句话说自定义类加载器如果在BootShrap和ExtClassLoader里找不到的话, 一定是由appClassLoader返回结果。 如果appClassLoader也找不到的话,不会再返回自定义加载器了,直接抛出异常,classNotFindException。(码字好累)
第三,在自定义类加载器里定义解密方法。好了,废话说到这,我和小伙伴们都累坏了。上代码
//加密和解密的公用方法
private static void cypher(InputStream ips ,OutputStream ops) throws Exception{
int b = -1;
while((b=ips.read())!=-1){
ops.write(b ^ 0xff);
}
}
public static void main(String[] args) throws Exception {
String srcPath = args[0]; //由我们自己手动设置一个路径,不写死
String destDir = args[1]; //目标目录,也不写死
FileInputStream fis = new FileInputStream(srcPath); //获取到要加密的那个class文件
String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1); //获取到class文件的名字
String destPath = destDir + "\\" + destFileName; //放置在目录下
FileOutputStream fos = new FileOutputStream(destPath); //文件输出流, 不解释。
cypher(fis,fos);
fis.close();
fos.close();
}
以上是加密方法,好了,自定义类加载器读取上来解密,
public class MyClassLoader extends ClassLoader{
private String classDir; //传入刚才那个加密的class文件的目录
public MyClassLoader(){
}
public MyClassLoader(String classDir){ //为了省时,直接构造函数搞起
this.classDir = classDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("我进来了");
//name是解密的文件名
String classFileName = classDir + "\\" + name.substring(name.lastIndexOf('.')+1) + ".class";
// classFileName 读取的路径名, itcastlib\ClassLoaderAttachment.class
System.out.println(classFileName);
try {
FileInputStream fis = new FileInputStream(classFileName); //先读取上来
ByteArrayOutputStream bos = new ByteArrayOutputStream(); //读取到的是字节码文件, 放在字节数组流里面
cypher(fis,bos); //再次异或, 解密操作
fis.close();
byte[] bytes = bos.toByteArray(); //创建一个字节数组
return defineClass(null,bytes, 0, bytes.length); //将一个字节数组转换为 Class 类的实例,此方法由ClassLoader类加载器自带
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
运行看结果:
public static void main(String[] args) throws Exception {
//我们现在要加载指定目录下的class文件来使用, 那么我们就得把java虚拟机编译出来的class文件删掉,
//如果不删掉,会被父加载器给加载到, 如果要让父加载器加密的文件, 我们可以先把那份加密的文件把生成的
//未加密的class给覆盖成加密后的class文件, 然后父加载器加载的必须的带有包名的类
Class clazz = new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachment");
// Class clazz = new MyClassLoader("itcastlib").loadClass("cn.itcast.day2.ClassLoaderAttachment");
Date d1 = (Date)clazz.newInstance();
//这里只能用父类来引用, 虽然说加载上来的实例是ClassLoaderAttachment
//但是ClassLoaderAttachment 加载上来虚拟机也不知道是什么东西,所以用父类来引用,
//父类能被正常的类加载器加载上来,
System.out.println(d1); // 结果是 hello, itcast
}
类加载器的高级应用, 在webService下的应用.
需要搭建servlet
public class classTest extends HttpServlet { //搭建servlet
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8"); //设置作用域的编码,不解释
request.setCharacterEncoding("UTF-8"); //同上
PrintWriter out = response.getWriter();//往页面上输出
ClassLoader loader = this.getClass().getClassLoader(); //获取到当前类的加载器 webApp
while(loader!=null){
out.println(loader.getClass().getName()+"<br/><br/>"); //这里的类加载器由tomcat决定
loader = loader.getParent();
}
out.print("我和小伙伴们都惊呆了<br/>");
out.close();
}
}
注意:如果将这个web项目导成jar包并且放在 lib/ext 目录下,那么被继承的Httpservlet 会报错,错误原因是,类加载器加载不到,这时候需要将HttpServlet 所在的jar包也放在ext下让webExt 类加载器加载即可解决
PS: 我和我的小伙伴真的都累坏了。