资讯专栏INFORMATION COLUMN

自定义类加载器

clasnake / 2346人阅读

摘要:未加载返回已加载返回类对象系统计时器的当前值纳秒父根链接一个指定的类由自定义类加载器重载体现了以上所述的类加载逻辑。为自定义类加载器提供了入口。

这周在看《深入理解Java虚拟机 JVM高级特性与最佳实践(高清完整版)》,就地取材写写第7章中提到的类加载器。以下源码截自java8。

delegation model
 * 

The ClassLoader class uses a delegation model to search for * classes and resources. Each instance of ClassLoader has an * associated parent class loader. When requested to find a class or * resource, a ClassLoader instance will delegate the search for the * class or resource to its parent class loader before attempting to find the * class or resource itself. The virtual machine"s built-in class loader, * called the "bootstrap class loader", does not itself have a parent but may * serve as the parent of a ClassLoader instance.

截取自源码开篇注释。“delegation model”大部分文章译为“双亲委派模型”(个人感觉不是很贴切,“双”字很容易产生误解),阐述了一种类加载顺序关系。请求查找类或资源时,ClassLoader实例会先交给父级类加载器处理(组合实现,非继承),依次类推直到"bootstrap class loader",父级无法处理(在其范围内找不到对应类/资源)了再由自己加载。据说这样可以避免同名类引发的安全隐患。类加载顺序如下图。

loadClass --> findClass
/**
 * Loads the class with the specified binary name.
 * This method searches for classes in the same manner as the {@link
 * #loadClass(String, boolean)} method.  It is invoked by the Java virtual
 * machine to resolve class references.  Invoking this method is equivalent
 * to invoking {@link #loadClass(String, boolean) loadClass(name,
 * false)}.
 *
 * @param  name
 *         The binary name of the class
 *
 * @return  The resulting Class object
 *
 * @throws  ClassNotFoundException
 *          If the class was not found
 */
public Class loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

/**
 * Loads the class with the specified binary name.  The
 * default implementation of this method searches for classes in the
 * following order:
 *
 * 
    * *
  1. Invoke {@link #findLoadedClass(String)} to check if the class * has already been loaded.

  2. * *
  3. Invoke the {@link #loadClass(String) loadClass} method * on the parent class loader. If the parent is null the class * loader built-in to the virtual machine is used, instead.

  4. * *
  5. Invoke the {@link #findClass(String)} method to find the * class.

  6. * *
* *

If the class was found using the above steps, and the * resolve flag is true, this method will then invoke the {@link * #resolveClass(Class)} method on the resulting Class object. * *

Subclasses of ClassLoader are encouraged to override {@link * #findClass(String)}, rather than this method.

* *

Unless overridden, this method synchronizes on the result of * {@link #getClassLoadingLock getClassLoadingLock} method * during the entire class loading process. * * @param name * The binary name of the class * * @param resolve * If true then resolve the class * * @return The resulting Class object * * @throws ClassNotFoundException * If the class could not be found */ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded // VM未加载返回null;已加载返回类对象 Class c = findLoadedClass(name); if (c == null) { // 系统计时器的当前值(纳秒) long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); // 父 } else { c = findBootstrapClassOrNull(name); // 根 } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } // 链接一个指定的类 if (resolve) { resolveClass(c); } return c; } } /** * Finds the class with the specified binary name. * This method should be overridden by class loader implementations that * follow the delegation model for loading classes, and will be invoked by * the {@link #loadClass loadClass} method after checking the * parent class loader for the requested class. The default implementation * throws a ClassNotFoundException. * * @param name * The binary name of the class * * @return The resulting Class object * * @throws ClassNotFoundException * If the class could not be found * * @since 1.2 */ protected Class findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); // 由自定义类加载器重载 }

体现了以上所述的类加载逻辑。findClass为自定义类加载器提供了入口。

defineClass
/**
 * Converts an array of bytes into an instance of class Class,
 * with an optional ProtectionDomain.  If the domain is
 * null, then a default domain will be assigned to the class as
 * specified in the documentation for {@link #defineClass(String, byte[],
 * int, int)}.  Before the class can be used it must be resolved.
 *
 * 

The first class defined in a package determines the exact set of * certificates that all subsequent classes defined in that package must * contain. The set of certificates for a class is obtained from the * {@link java.security.CodeSource CodeSource} within the * ProtectionDomain of the class. Any classes added to that * package must contain the same set of certificates or a * SecurityException will be thrown. Note that if * name is null, this check is not performed. * You should always pass in the binary name of the * class you are defining as well as the bytes. This ensures that the * class you are defining is indeed the class you think it is. * *

The specified name cannot begin with "java.", since * all classes in the "java.* packages can only be defined by the * bootstrap class loader. If name is not null, it * must be equal to the binary name of the class * specified by the byte array "b", otherwise a {@link * NoClassDefFoundError NoClassDefFoundError} will be thrown.

* * @param name * The expected binary name of the class, or * null if not known * * @param b * The bytes that make up the class data. The bytes in positions * off through off+len-1 should have the format * of a valid class file as defined by * The Java™ Virtual Machine Specification. * * @param off * The start offset in b of the class data * * @param len * The length of the class data * * @param protectionDomain * The ProtectionDomain of the class * * @return The Class object created from the data, * and optional ProtectionDomain. * * @throws ClassFormatError * If the data did not contain a valid class * * @throws NoClassDefFoundError * If name is not equal to the binary * name of the class specified by b * * @throws IndexOutOfBoundsException * If either off or len is negative, or if * off+len is greater than b.length. * * @throws SecurityException * If an attempt is made to add this class to a package that * contains classes that were signed by a different set of * certificates than this class, or if name begins with * "java.". */ protected final Class defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError { protectionDomain = preDefineClass(name, protectionDomain); String source = defineClassSourceLocation(protectionDomain); Class c = defineClass1(name, b, off, len, protectionDomain, source); postDefineClass(c, protectionDomain); return c; }

defineClass:接收以字节数组表示的类字节码,并把它转换成 Class 实例,该方法转换一个类的同时,会先要求装载该类的父类以及实现的接口类。重写findClass将使用到。

public class Test {
    private static String link = "rebey.cn";
    static {
        System.out.println("welcome to: "+link);
    }
    
    public void print() {
        System.out.println(this.getClass().getClassLoader());
    }
}

将这段java代码编译成.class文件(可通过javac指令),放在 了E:201706下。同时在我的测试项目下也有一个/201705/src/classLoader/Test.java,代码相同。区别就是一个有包名一个没有包名。如果class文件中源码包含package信息,届时可能会抛出java.lang.NoClassDefFoundError (wrong name)异常。

package classLoader;

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

public class CustomClassLoader extends ClassLoader{
    private String basedir; // 需要该类加载器直接加载的类文件的基目录
    
    public CustomClassLoader(String basedir) {
        super(null);
        this.basedir = basedir;
    } 
    
    protected Class findClass(String name) throws ClassNotFoundException {
        Class c = findLoadedClass(name);
        if (c == null) {
            byte[] bytes = loadClassData(name);
            if (bytes == null) {    
                throw new ClassNotFoundException(name);    
            }
            c = defineClass(name, bytes, 0, bytes.length);
        }
        return c; 
    }
    
    // 摘自网络
    public byte[] loadClassData(String name) {
        try {
            name = name.replace(".", "//");
            FileInputStream is = new FileInputStream(new File(basedir + name + ".class"));
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;
            while ((b = is.read()) != -1) {
                baos.write(b);
            }
            is.close();
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

编写自定义的类加载器,继承ClassLoader,重写了findClass方法,通过defineClass将读取的byte[]转为Class。然后通过以下main函数调用测试:

package classLoader;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Loader {
    public static void main(String[] arg) throws NoSuchMethodException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
        // 走自定义加载器
        CustomClassLoader ccl = new CustomClassLoader("E://201706//");
        Class clazz = ccl.findClass("Test");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("print", null);
        method.invoke(obj, null);
        
        System.out.println("--------------我是分割线-------------------");
        
        // 走委派模式
        // 隐式类加载
        Test t1 = new Test();
        t1.print();
        
        System.out.println("--------------我是分割线-------------------");
        
        // 显式类加载
        Class t2 = Class.forName("classLoader.Test");
        Object obj2 = t2.newInstance();
        Method method2 = t2.getDeclaredMethod("print", null);
        method2.invoke(obj2, null);
        
        System.out.println("--------------我是分割线-------------------");
        
        Class t3 = Test.class;
        Object obj3 = t3.newInstance();
        Method method3 = t3.getDeclaredMethod("print", null);
        method3.invoke(obj3, null);
    }
}

输出结果:

welcome to: rebey.cn
classLoader.CustomClassLoader@6d06d69c
--------------我是分割线-------------------
welcome to: rebey.cn
sun.misc.Launcher$AppClassLoader@73d16e93
--------------我是分割线-------------------
sun.misc.Launcher$AppClassLoader@73d16e93
--------------我是分割线-------------------
sun.misc.Launcher$AppClassLoader@73d16e93

静态代码块随着类加载而执行,而且只会执行一次,所以这里t2、t3加载完成是并没有再输出。

说点什么

ClassLoader线程安全;

同个类加载器加载的.class类实例才相等;

Class.forName(xxx.xx.xx) 返回的是一个类, .newInstance() 后才创建实例对象 ;

Java.lang.Class对象是单实例的;

执行顺序:静态代码块 > 构造代码块 > 构造函数

1、父类静态变量和静态代码块(先声明的先执行);

2、子类静态变量和静态代码块(先声明的先执行);

3、父类的变量和代码块(先声明的先执行);

4、父类的构造函数;

5、子类的变量和代码块(先声明的先执行);

6、子类的构造函数。

应用

通过自定义加载类,我们可以:

①加载指定路径的class,甚至是来自网络(自定义类加载器:从网上加载class到内存、实例化调用其中的方法)、DB(自定义的类装载器-从DB装载class(附上对类装载器的分析));

②给代码加密;(如何有效防止Java程序源码被人偷窥?)

③装逼(- -);

更多有意思的内容,欢迎访问笔者小站: rebey.cn

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67249.html

相关文章

  • Java加载定义

    摘要:自定义类加载器示例代码类加载器获取的字节流字节流解密被加载的类测试代码以上代码,展示了自定义类加载器加载类的方法。这就需要自定义类加载器,以便对加载的类库进行隔离,否则会出现问题对于非的文件,需要转为类,就需要自定义类加载器。 Java类加载器的作用是寻找类文件,然后加载Class字节码到JVM内存中,链接(验证、准备、解析)并初始化,最终形成可以被虚拟机直接使用的Java类型。sho...

    hiyang 评论0 收藏0
  • Java加载机制

    摘要:当前类加载器和所有父类加载器都无法加载该类时,抛出异常。加载两份相同的对象的情况和不属于父子类加载器关系,并且各自都加载了同一个类。类加载机制与接口当虚拟机初始化一个类时,不会初始化该类实现的接口。 类加载机制 概念 类加载器把class文件中的二进制数据读入到内存中,存放在方法区,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。 1、加载: 查...

    aaron 评论0 收藏0
  • 加载以及双亲委派模型

    摘要:宗主引导类加载器。双亲委派模型是如何使用的我们在自定义加载器中查找是否有需要加载的文件,如果已经加载过,直接返回字节码。 作者:毕来生微信:878799579 1、小故事理解类加载器以及双亲委派模型 首先我们来描述一个小说场景,通过这个场景在去理解我们相关的类加载器的执行以及双亲委派模型。 上古时代有逍遥派和万魔宗两个宗派,互相对立。逍遥派比万魔门更加强势。巅峰战力更高。 有一天万魔宗...

    曹金海 评论0 收藏0
  • Java加载详解

    摘要:当一个文件是通过网络传输并且可能会进行相应的加密操作时,需要先对文件进行相应的解密后再加载到内存中,这种情况下也需要编写自定义的并实现相应的逻辑 Java虚拟机中的类加载有三大步骤:,链接,初始化.其中加载是指查找字节流(也就是由Java编译器生成的class文件)并据此创建类的过程,这中间我们需要借助类加载器来查找字节流. Java虚拟机默认类加载器 Java虚拟机提供了3种类加载器...

    Baaaan 评论0 收藏0
  • JVM加载过程 & 双亲委派模型

    摘要:类加载过程双亲委派模型声明文章均为本人技术笔记,转载请注明出处类加载过程类加载机制将类描述数据从文件中加载到内存,并对数据进行,解析和初始化,最终形成被直接使用的类型。深入理解虚拟机高级特性与最佳实践加载加载阶段由类加载器负责,过程见类加载 JVM类加载过程 & 双亲委派模型 声明 文章均为本人技术笔记,转载请注明出处https://segmentfault.com/u/yzwall ...

    happen 评论0 收藏0
  • 定义加载-从.class和.jar中读取

    摘要:在没有指定自定义类加载器的情况下,这就是程序的默认加载器。自定义类加载器双亲委派模型避免由于字节码被多次加载。首先自定义类加载器,最重要的就是先继承这个类。 一. 类加载器 JVM中的类加载器:在jvm中,存在两种类加载器,a) Boostrap ClassLoader:这个是由c++实现的,所以在方法区并没有Class对象的实例存在。用于加载JAVA_HOME/bin目录...

    Jackwoo 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<