文档章节

自定义类加载器

IronWong
 IronWong
发布于 2015/09/21 15:38
字数 1314
阅读 60
收藏 0

一、类加载器的概念

        在《Java类加载机制》这篇文章里详细介绍了加载的整个过程以及类加载器的特点。这里再简要总结一下。

        类加载过程的大部分阶段都是由虚拟机自行完成的。唯一的特例是在加载过程的“加载”阶段,即加载过程的第一个阶段,通过一个类的全限定名来获取定义此类的二进制字节流。实现这个动作的模块被称为“类加载器”,而“类加载器”可以分为两类,一类是虚拟机自带的类加载器,启动类加载器(Bootstrap ClassLoader),另一部分则位于虚拟机外部,全都继承自抽象类java.lang.ClassLoader,包括扩展类加载器(Extension ClassLoader),应用程序类加载器(Application ClassLoader)。

        不同的类加载器有各种的命名空间,这些命名空间的关系如下:

    • 同一个命名空间内的类是相互可见的。

    • 子加载器的命名空间包含所有父加载器的命名空间。因此子加载器加载的类能看见父加载器加载的类。例如系统类加载器加载的类能看见根类加载器加载的类。

    • 由父加载器加载的类不能看见子加载器加载的类。

    • 如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见。

    • 当两个不同命名空间内的类相互不可见时,可以采用Java的反射机制来访问实例的属性和方法。

二、为什么要定义类加载器

        1. 加密。为了防止java代码被反编译,可以先将编译后的代码加密,而自定义的类加载器负责把这段加密后的代码还原。这里有个问题,如何保证类加载器不被反编译?

        2. 从非标准来源加载类。假如部分字节码并不位于本地,而是在数据库或是网络上,那可以自定义类加载器来加载。

        3. 动态创建或修改。多个类的字节码都存放在一个文件中,加载类时根据需求读取一部分字节码进行加载类,或者根据实际情况修改部分字节码再载入内存。

三、如何自定义类加载器

        自定义类加载器需要完成以下两个个步骤:

    • 继承抽象类ClassLoader

    • 覆盖findClass(String)方法,获取到类的字节码,并调用ClassLoader的definClass把字节码转换为Class对象。

        一个简单的自定义类加载器如下:

MyClassLoader.java

package classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader {
    // 加载类的路径
    private String path = "";
    private final String fileType = ".class";

    public MyClassLoader() {
        super();// 让系统加载器成为该类的加载器的父类加载器
    }

    /**
     * 获取Class对象
     */
    @Override
    public Class<?> findClass(String name) {
        byte[] data = loaderClassData(name);
        return this.defineClass(name, data, 0, data.length);
    }

    /**
     * 读取class文件作为二进制流放入到byte数组中去
     */
    private byte[] loaderClassData(String name) {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        name = name.replace(".", "/");
        try {
            is = new FileInputStream(new File(path + name + fileType));
            int c = 0;
            while (-1 != (c = is.read())) {
                baos.write(c);
            }
            data = baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

        为了验证自定义类加载器是否能正常工作,编写下面的类:

package classloader;

public class MyClass {
    static {
        System.out.println("MyClass init!");
    }
}

        编译成功后,重命名类名(为了防止加载时系统的类加载器能够找到类路径),在D盘下新建文件夹classloader,找到MyClass.class复制到该文件夹内。再编写测试类:

package classloader;

public class Test {

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        MyClassLoader loader = new MyClassLoader();
        loader.setPath("d://");
        Class<?> clazz = loader.loadClass("classloader.MyClass");  
        clazz.newInstance();
    }
}

运行,可以看到控制台输出“MyClass init!”。

四、能不能自己写一个类叫java.lang.System/String

        这个问题写在这里,主要是因为可能会在面试中遇到。实际过程中基本不需要自己去写一个包名跟类名都和系统类完全一样的类吧。

        首先,可以把代码写出来,而且如果不去调用,完全不会有问题。假如自己写了一个java.lang.String,然后在其他类中使用的话,会发现用的还是系统的类。因为根据双亲委派机制,遇到需要加载String时,是调用父加载器进行加载的。而父加载器在系统目录中找到String类以后,就不会再去加载自己写的String了。

        其次,在自定义的java.lang.String中添加main函数,运行时,会提示“java.lang.NoSuchMethodError: main " ,说明此时加载的还是系统的String。

        再次,用自定义的类加载器加载自定义的java.lang.String,也会出现错误提示。“java.lang.SecurityException: Prohibited package name: java.lang”。

        最后,假如不覆盖系统类,只是把类的包名定义成java.lang呢?这也是不行的,报错和上一条一样。

        所以,结论是,一般不能加载进内存,假如仍然强行加载会报错。


© 著作权归作者所有

IronWong
粉丝 0
博文 18
码字总数 20236
作品 0
杭州
程序员
私信 提问
自定义类加载器和双亲委派模型

其实,双亲委派模型并不复杂。自定义类加载器也不难!随便从网上搜一下就能搜出一大把结果,然后一下就能用。但是,如果每次想自定义类加载器就必须搜一遍别人的文章,然后复制,这样显然不行...

hello-树先生
2018/02/08
0
0
双亲委派模型和自定义类加载器

其实,双亲委派模型并不复杂。自定义类加载器也不难!随便从网上搜一下就能搜出一大把结果,然后一下就能用。但是,如果每次想自定义类加载器就必须搜一遍别人的文章,然后复制,这样显然不行...

洋哥6
2018/01/16
0
0
Java的classloader

类加载器的基本概念 类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被...

huzorro
2012/12/18
0
0
浅谈双亲委派模型

本文浅析了双亲委派的基本概念、实现原理、和自定义类加载器的正确姿势。 对于更细致的加载loading过程、初始化initialization顺序等问题,文中暂不涉及,后面整理笔记时有相应的文章。 JDK版...

猴子007
2018/01/04
0
0
JVM系列之类加载流程-自定义类加载器

JVM系列之类加载流程-自定义类加载器 老实说,类加载流程作者还是比较熟悉而且有实战经验的,因为有过一次自定义类加载器的实战经验(文章最后会和大家分享),虽然大部分小伙伴觉得这部分对c...

我又不是架构师
2017/12/01
0
0

没有更多内容

加载失败,请刷新页面

加载更多

高并发场景下的缓存有哪些常见的问题?

一、缓存一致性问题 当数据时效性要求很高时,需要保证缓存中的数据与数据库中的保持一致,而且需要保证缓存节点和副本中的数据也保持一致,不能出现差异现象。 这就比较依赖缓存的过期和更新...

别打我会飞
17分钟前
1
0
List list = new ArrayList()为何父类引用指向子类对象(多态)

态:要有继承,方法的重写,父类引用指向子类对象 疑问一:父类引用指向子类对象 与指向父类对象 Animal cat = new Cat(); //向上转型。 父类引用指向子类对象,该引用不能再访问子类新增加的...

architect刘源源
17分钟前
0
0
分而治之-快速排序

快速排序的思想: 快速排序首先在数组中确定1个枢纽项(比如数组中的第一个元素),将大于该枢纽项的元素放到右侧,小于该枢纽项的元素放到左侧,这样枢纽项将数组划分成两部分。接着继续对划...

万山红遍
今天
4
0
Qt编写自定义控件9-导航按钮控件

前言 导航按钮控件,主要用于各种漂亮精美的导航条,我们经常在web中看到导航条都非常精美,都是html+css+js实现的,还自带动画过度效果,Qt提供的qss其实也是无敌的,支持基本上所有的CSS2属...

飞扬青云
今天
3
0
Python开发工具:pyJasper

原文:https://www.oschina.net/p/pyjasper 前言 pyJasper是 JasperReports 网络服务器的 Python 客户端。 pyJasper 是一组 Python 基础工具,可以用来处理 JasperReports 报表 。因为 Jasper...

A_裙232550246
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部