黑马程序员——类加载器

------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


 

 

Java虚拟机中可以安装多个类加载器,系统默认有三个主要的类加载器,每个类加载器负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader

类加载器也是Java类,因此Java类的类加载器本身也要被其他的类加载器加载,显然必须有第一个类加载器不是Java类,它就是BootStrap类加载器。

代码示例:

package com.itheima.day02;

public class ClassLoaderTest {

    public staticvoidmain(String[] args) {

        System.out.println(ClassLoaderTest.class.getClassLoader().

        getClass().getName());

        System.out.println(System.class.getClassLoader());

    }

}

运行结果:

sun.misc.Launcher$AppClassLoader

null

由上面的示例可以看到ClassLoaderTest类是由AppClassLoader类加载器加载的。

System类是由BootStrap类加载器加载的。

注意:

JVM内核启动的时候,BootStrap就已经被加载了,它是内嵌在JVM内核中的,是用C++语言编写的二进制代码,因此不需要其他类加载器加载。

Java虚拟机中的所有类装载器采用了具有父子关系的树形结构进行组织。

代码示例:

package com.itheima.day02;

public class ClassLoaderTest {

    public staticvoidmain(String[] args) {

        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

sun.misc.Launcher$ExtClassLoader

null

由上面的示例可以看到AppClassLoader类加载器的父级别类加载器是ExtClassLoader类加载器,ExtClassLoader类加载器的父级别类加载器是BootStrap类加载器。

在实例化每个类加载器对象时,需要为其指定一个父级类加载器对象或者默认采用系统类加载器为其父级类加载。

构造方法摘要

protected

ClassLoader()
使用方法 getSystemClassLoader() 返回的ClassLoader 创建一个新的类加载器,将该加载器作为父类加载器。

protected

ClassLoader(ClassLoader parent)
使用指定的、用于委托操作的父类加载器创建新的类加载器。

类加载器之间的父子关系和管辖范围图

 

小实验:将ClassLoaderTest类的class文件,打成jar包,放置在JRE/lib/ext/*.jar,它会由哪个类加载器加载呢?

右击ClassLoaderTest.java-->点击Export

点击Java/JAR file-->点击Next

点击Browse...

存放在JDK目录下的JRE/lib/ext目录下,并且jar包取名为heima.jar

然后再执行ClassLoaderTest类就会出现如下结果:

sun.misc.Launcher$ExtClassLoader

null

也就是说ClassLoaderTest类现在由ExtClassLoader类加载器加载。

类加载器的委托机制

Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?

首先当前线程的类加载器去加载线程中的第一个类。

如果类A中引用了类BJava虚拟机将使用加载类A的类加载器来加载类B

还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

每个类加载器加载类时,又先委托给其上级类加载器。

当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢。

注意:

每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类加载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类加载器去进行真正的加载。当回退到最初的类载器时,如果它自己也不能完成类的加载,那就会告ClassNotFoundException异常。

面试题,能不能自己写个类叫java.lang.System

答案是不能,即使写了也不会被类加载器加载。为了不让我们写System类,类加载机制采用委托机制,这样可以保证父级类加载器优先,也就是总是使用父级类加载器能找到的类,结果就是总是使用Java系统自身提供的System类,而不会使用我们自己所写的System类。

自定义类加载器的编写原理分析

类加载器中的loadClass方法内部实现了父类委托机制,因此我们没有必要自己覆盖loadClass,否则需要自己去实现父类委托机制。我们只需要覆盖findClass方法。loadClass方法中调用了findClass方法,使用的是模板设计模式。我们得到了Class文件后,就可以通过defineClass方法将二进制数据转换成字节码。这就是自定义类加载器的编写原理。

API文档中的例子:

 

需求:

编写一个对文件内容进行简单加密的程序。

步骤:

1.对生成的字节码文件进行一个字节一个字节读取,然后对读取到的字节进行加密运算。

2.把加密后的字节读取到另外一个字节码文件中。

对字节码文件进行加密,代码示例:

package com.itheima.day02;

import java.util.Date;

public class ClassLoaderAttachment extends Date{

    public String toString(){

        return "黑马程序员";

    }

}

package com.itheima.day02;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.InputStream;

import java.io.OutputStream;

public class MyClassLoader{

    public staticvoidmain(String[] args) throws Exception{

        String srcPath = args[0];

        String destDir = args[1];

        FileInputStream fip =newFileInputStream(srcPath);

        StringdestFileName=srcPath.substring(srcPath.lastIndexOf('\\')+1);

        String destPath = destDir+"\\"+destFileName;

        FileOutputStream fop =newFileOutputStream(destPath);

        encrypt(fip,fop);

        fip.close();

        fop.close();

    }

//对输入流数据进行加密

    public staticvoidencrypt(InputStream ips,OutputStream ops) throws Exception{

        int by = 0;

        while((by=ips.read())!=-1){

            ops.write(by ^ 0xff);

        }

    }

}

package com.itheima.day02;

public class ClassLoaderTest2 {

    public staticvoidmain(String[] args) {

        System.out.println(new ClassLoaderAttachment().toString());

    }

}

执行MyClassLoader类中的main方法,首先先设置传入的参数:

 

注意:在运行的时候一定要注意运行的Main方法所在的类是否是MyClassLoader

执行MyClassLoader类中的main方法后,按F5刷新目录,则可以看到编码后的ClassLoaderAttachment.class文件。

 

将此class文件,覆盖项目中bin\com\itheima\day02目录下的ClassLoaderAttachment.class文件,重新执行ClassLoaderTest类中的main方法,就会报错。这是因为AppClassLoader已经无法正确加载编码后的class文件了,只有我们自己写一个带有解码功能的类加载器才可以。

Exception in thread"main" java.lang.ClassFormatError: Incompatible magic value 889275713in class file com/itheima/day2/ClassLoaderAttachment

at java.lang.ClassLoader.defineClass1( Native Method)

at java.lang.ClassLoader.defineClass( ClassLoader.java:620)

at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)

at java.net.URLClassLoader.defineClass( URLClassLoader.java:260)

at java.net.URLClassLoader.access$000( URLClassLoader.java:56)

at java.net.URLClassLoader$1.run( URLClassLoader.java:195)

at java.security.AccessController.doPrivileged( Native Method)

at java.net.URLClassLoader.findClass( URLClassLoader.java:188)

at java.lang.ClassLoader.loadClass( ClassLoader.java:306)

at sun.misc.Launcher$AppClassLoader.loadClass( Launcher.java:276)

at java.lang.ClassLoader.loadClass( ClassLoader.java:251)

at java.lang.ClassLoader.loadClassInternal( ClassLoader.java:319)

at com.itheima.day2.ClassLoaderTest.main( ClassLoaderTest.java:6)

编写和测试自己编写的解密类加载器

需求:

编写一个自己的类加载器,可实现对加密过的类进行加载和解密。

步骤:

1、自定义的类加载器必须继承ClassLoader

2、覆盖findClass方法。

3、在findClass方法中对字节码文件进行解密。

4、调用自定义的类加载器对字节码文件进行解密。

自定义类加载器解密,代码示例:

package com.itheima.day02;

import java.util.Date;

public class ClassLoaderAttachment extends Date{

    public String toString(){

        return "黑马程序员";

    }

}

package com.itheima.day02;

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{

    /**

    * @param args

    */

    public staticvoidmain(String[] args) throws Exception {

        // TODO Auto-generated method stub

        String srcPath = args[0];

        String destDir = args[1];

        FileInputStreamfis = newFileInputStream(srcPath);

        String destFileName =srcPath.substring(srcPath.lastIndexOf('\\')+1);

        String destPath = destDir + "\\" + destFileName;

        FileOutputStream fos = newFileOutputStream(destPath);

        cypher(fis,fos);

        fis.close();

        fos.close();

    }

    private staticvoidcypher(InputStream ips ,OutputStream ops) throws Exception{

        int b = -1;

        while((b=ips.read())!=-1){

            ops.write(b ^ 0xff);

        }

    }

    private String classDir;

    @Override

    protected Class<?> findClass(String name) throws ClassNotFoundException {

        // TODO Auto-generated method stub

        String classFileName = classDir + "\\"  + name.substring(name.lastIndexOf('.')+1) + ".class";

        try {

            FileInputStream fis = newFileInputStream(classFileName);

            ByteArrayOutputStream  bos = new ByteArrayOutputStream();

            cypher(fis,bos);

            fis.close();

            System.out.println("aaa");

            byte[] bytes = bos.toByteArray();

            return defineClass(bytes, 0, bytes.length);

        } catch (Exception e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

        return null;

    }

    public MyClassLoader(){

    }

    public MyClassLoader(String classDir){

        this.classDir = classDir;

    }

}

package com.itheima.day02;

import java.util.Date;

public class ClassLoaderTest {

public static void main(String[] args) throws Exception {

    Class clazz = new MyClassLoader("ClassLoaderLib").loadClass("cn.itheima.day2.ClassLoaderAttachment");

    Date d1 = (Date)clazz.newInstance();

    System.out.println(d1);}

}

注意:

1、之所以让ClassLoaderAttachment类继承Date是因为,如果直接写

ClassLoaderAttachment d =(ClassLoaderAttachment)clazz.newInstance();这条语句,编译的时候就会报错,因为此时的ClassLoaderAttachmentAppClassLoader加载进内存后就无法识别。所以需要通过借助一个父类对象绕过编译器。也就是:Date d1 = (Date)clazz.newInstance();

2、如果想让父类加载器AppClassLoader加载ClassLoaderAttachment类,则需要执行下面的语句:Class clazz =

newMyClassLoader("ClassLoaderLib").loadClass("com.itheima.day2.ClassLoaderAttachment");

但是父类加载可能会出错。

类加载器的一个高级问题的实验分析

编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassloader。把MyServlet.class文件打成jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。

servlet.jar也放到ext目录中,问题解决了,打印的结果是ExtClassLoader

具体步骤:新建一个Web工程。

项目名称命名为itheimaweb-->点击Finish

右击src-->New-->Servlet,创建一个Servlet

命名包名和类名,并且只创建doGet方法。点击Next

点击Finish

Web代码示例:

package com.itheima.itheimaweb.web.servlets;

import java.io.IOException;

importjava.io.PrintWriter;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

importjavax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public classMyServletextendsHttpServlet {

    public voiddoGet(HttpServletRequest request, HttpServletResponse response)

    throwsServletException, IOException {

        response.setContentType("text/html");

        PrintWriter out = response.getWriter();

        ClassLoader loader =this.getClass().getClassLoader();

        while(loader!=null){

            out.println(loader.getClass().getName()+"<br/>");

            loader = loader.getParent();

        }

        out.close();

    }

}

选择要运行的文件,右击,Run As ,MyEclipse Server Application

跳出对话框,选择一个Tomcat服务器,点击OK

运行结果如下:

附:由于关于tomcatServlet部分的知识点后面的课程都会涉及,因此这里不详述。

在浏览器中输入访问MyServlet的地址,可以看到MyServlet是由WebappClassLoader类加载器加载的。

MyServletjar包的方式导出到jdk\jre\lib\ext目录下,jar包名称为itheima.jar

重启Tomcat服务器,并且通过浏览器访问,可以看到错误信息提示无法加载HttpServlet

父级类加载器加载的类无法引用只能被子级类加载器加载的类,原理如下图:

错误原因:

在没有将MyServlet导出为jar包到jdk\jre\lib\ext目录以前,MyServlet以及其父类都是由WebAppClassLoader

载的。但是导出到jdk\jre\lib\ext目录以后,就由ExtClassLoader类加载器加载MyServlet,但是HttpServlet却不

ext目录下,所以无法加载到。而且MyServletHttpServlet是同一线程的,这也就意味着他们两个是由同一

个类加载器加载的。ExtClassLoader类加载器加载不了,就会直接报错,而不会再交给WebAppClassLoader

类加载器再加载。

解决方法:将HttpServlet所在的jar包拷贝到jdk\jre\lib\ext目录下,重新启动tomcat服务器,重新在浏览器中访问MyServlet

结果:可以看到此时加载MyServlet的类加载器是ExtClassLoader类加载器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值