自定义ClassLoader

                                                             自定义ClassLoader加载指定资源(非集群模式)

1、自定义classLoader的背景:

  • 我们需要的类不一定都存放在已经设置好的classPath下(由系统类加载器AppClassLoader加载的路径),对于自定义路径中的class类文件的加载,我们需要自定义ClassLoader去加载
  • 有时我们不一定是从类文件中读取类,也有可能是从网络的输入流中读取类,这就需要做一些加密和解密操作,这就需要自己实现加载类的逻辑,当然其他的特殊处理也同样适用。
  • 可以定义类的实现机制,实现类的热部署,如OSGi中的bundle模块就是通过实现自己的ClassLoader实现的。

2、ClassLoader类的结构

1,加载class文件

ClassLoader的loadClass采用双亲委托模型(概念文末有解释),因为我们实现的ClassLoader都继承于java.lang.ClassLoader类,父加载器都是AppClassLoader,所以在上层逻辑中依旧要保证该模型,所以一般不覆盖loadClass函数

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            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;
        }
    }

从源码中,我们可以看到在父加载器不能完成加载任务时,会调用findClass(name)函数,这个就是我们自己实现的ClassLoader的查找类文件的规则,所以在继承后,我们只需要覆盖findClass()这个函数,实现我们在本加载器中的查找逻辑,而且还不会破坏双亲委托模型。

2,加载资源文件(URL)

我们有时会用Class.getResource():URL来获取相应的资源文件。如果仅仅使用上面的ClassLoader是找不到这个资源的,相应的返回值为null。 
下面我们来看Class.getResource()的源码:


public java.net.URL getResource(String name) {
        name = resolveName(name);//解析资源
        ClassLoader cl = getClassLoader();//获取到当前类的classLoader
        if (cl==null) {//如果为空,那么利用系统类加载器加载
            // A system class.
            return ClassLoader.getSystemResource(name);
        }
        //如果获取到classLoader,利用指定的classLoader加载资源
        return cl.getResource(name);
    }

我们发现Class.getResource()是通过委托给ClassLoader的getResource()实现的,所以我们来看classLoader对于资源文件的获取的具体实现如下:

public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);//这里
        }
        return url;
    }

通过代码显而易见,也是双亲委派模型的实现,在不破坏模型的前提下,我们发现我们需要覆写的只是findResource(name)函数即可。

3、ClassLoader的实现

以下的实现基于ClassLoader抽象类的继承(只给出对于findClass的覆写,因为常理上处理逻辑基本一致)

加载自定义路径下的class文件

package com.cloud.web.myclassloader;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;

/**
 * @author: jack
 * @create: 2019-06-20
 * @description: 自定义类加载器加载指定资源
 **/
public class MyClassLoader extends ClassLoader {

    private String classpath;

    public MyClassLoader(String classpath) {
        this.classpath = classpath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String fileName = getClassFile(name);
        byte[] classByte = null;
        try {
            classByte = getClassBytes(fileName);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //利用自身的加载器加载类
        Class retClass = defineClass(null, classByte, 0, classByte.length);
        if (retClass != null) {
            System.out.println("由我加载");
            return retClass;
        }
        //System.out.println("非我加载");
        //在classPath中找不到类文件,委托给父加载器加载,父类会返回null,因为可加载的话在
        //委派的过程中就已经被加载了
        return super.findClass(name);
    }

    /**
     * @Date: 2019-06-20
     * @Param: fileName
     * @return: byte[]
     * @Description: 获取指定类文件的字节数组
     */
    private byte[] getClassBytes(String name) throws IOException {
        FileInputStream fileInput = new FileInputStream(name);
        FileChannel channel = fileInput.getChannel();
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        WritableByteChannel byteChannel = Channels.newChannel(output);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        try {
            int flag;
            while ((flag = channel.read(buffer)) != -1) {
                if (flag == 0) {
                    break;
                }
                //将buffer写入byteChannel
                buffer.flip();
                byteChannel.write(buffer);
                buffer.clear();
            }
        } catch (IOException e) {
            System.out.println("can't read!");
            throw e;
        }
        fileInput.close();
        channel.close();
        byteChannel.close();
        return output.toByteArray();
    }

    /***
     * 获取当前操作系统下的类文件合法路径
     * @param name
     * @return 合法的路径文件名
     */
    private String getClassFile(String name) {
        //利用StringBuilder将包形式的类名转化为Unix形式的路径
        StringBuilder sb = new StringBuilder(classpath);
        sb.append("/")
                .append(name.replace('.', '/'))
                .append(".class");
        return sb.toString();
    }

    public static void main(String[] args) {
        MyClassLoader myClassLoader = new MyClassLoader("E:/MyClassLoaderTest");
        try {
            myClassLoader.loadClass("java.io.InputStream");
            myClassLoader.loadClass("ClassLoaderTest");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

运行结果如下:

G:\JDK_1.8\bin\java.exe "-javaagent:G:\IntellijIDEA\ideaIU-2018.3.3\IntelliJ IDEA 2018.3.3\lib\idea_rt.jar=54778:G:\IntellijIDEA\ideaIU-2018.3.3\IntelliJ IDEA 2018.3.3\bin" -Dfile.encoding=UTF-8 -classpath G:\JDK_1.8\jre\lib\charsets.jar;G:\JDK_1.8\jre\lib\deploy.jar;G:\JDK_1.8\jre\lib\ext\access-bridge-64.jar;G:\JDK_1.8\jre\lib\ext\cldrdata.jar;G:\JDK_1.8\jre\lib\ext\dnsns.jar;G:\JDK_1.8\jre\lib\ext\jaccess.jar;G:\JDK_1.8\jre\lib\ext\jfxrt.jar;G:\JDK_1.8\jre\lib\ext\localedata.jar;G:\JDK_1.8\jre\lib\ext\nashorn.jar;G:\JDK_1.8\jre\lib\ext\sunec.jar;G:\JDK_1.8\jre\lib\ext\sunjce_provider.jar;G:\JDK_1.8\jre\lib\ext\sunmscapi.jar;G:\JDK_1.8\jre\lib\ext\sunpkcs11.jar;G:\JDK_1.8\jre\lib\ext\zipfs.jar;G:\JDK_1.8\jre\lib\javaws.jar;G:\JDK_1.8\jre\lib\jce.jar;G:\JDK_1.8\jre\lib\jfr.jar;G:\JDK_1.8\jre\lib\jfxswt.jar;G:\JDK_1.8\jre\lib\jsse.jar;G:\JDK_1.8\jre\lib\management-agent.jar;G:\JDK_1.8\jre\lib\plugin.jar;G:\JDK_1.8\jre\lib\resources.jar;G:\JDK_1.8\jre\lib\rt.jar;G:\IDEAWorkSpaces_BeeCredit\z_cloud_web\target\classes;G:\Maven-LocalRepository\org\springframework\boot\spring-boot-starter-tomcat\2.0.1.RELEASE\spring-boot-starter-tomcat-2.0.1.RELEASE.jar;G:\Maven-LocalRepository\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;G:\Maven-LocalRepository\org\apache\tomcat\embed\tomcat-embed-core\8.5.29\tomcat-embed-core-8.5.29.jar;G:\Maven-LocalRepository\org\apache\tomcat\embed\tomcat-embed-el\8.5.29\tomcat-embed-el-8.5.29.jar;G:\Maven-LocalRepository\org\apache\tomcat\embed\tomcat-embed-websocket\8.5.29\tomcat-embed-websocket-8.5.29.jar;G:\Maven-LocalRepository\org\springframework\boot\spring-boot-starter-web\2.0.1.RELEASE\spring-boot-starter-web-2.0.1.RELEASE.jar;G:\Maven-LocalRepository\org\springframework\boot\spring-boot-starter\2.0.1.RELEASE\spring-boot-starter-2.0.1.RELEASE.jar;G:\Maven-LocalRepository\org\springframework\boot\spring-boot\2.0.1.RELEASE\spring-boot-2.0.1.RELEASE.jar;G:\Maven-LocalRepository\org\springframework\boot\spring-boot-autoconfigure\2.0.1.RELEASE\spring-boot-autoconfigure-2.0.1.RELEASE.jar;G:\Maven-LocalRepository\org\springframework\boot\spring-boot-starter-logging\2.0.1.RELEASE\spring-boot-starter-logging-2.0.1.RELEASE.jar;G:\Maven-LocalRepository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;G:\Maven-LocalRepository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;G:\Maven-LocalRepository\org\apache\logging\log4j\log4j-to-slf4j\2.10.0\log4j-to-slf4j-2.10.0.jar;G:\Maven-LocalRepository\org\apache\logging\log4j\log4j-api\2.10.0\log4j-api-2.10.0.jar;G:\Maven-LocalRepository\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;G:\Maven-LocalRepository\org\yaml\snakeyaml\1.19\snakeyaml-1.19.jar;G:\Maven-LocalRepository\org\springframework\boot\spring-boot-starter-json\2.0.1.RELEASE\spring-boot-starter-json-2.0.1.RELEASE.jar;G:\Maven-LocalRepository\com\fasterxml\jackson\core\jackson-databind\2.9.5\jackson-databind-2.9.5.jar;G:\Maven-LocalRepository\com\fasterxml\jackson\core\jackson-annotations\2.9.0\jackson-annotations-2.9.0.jar;G:\Maven-LocalRepository\com\fasterxml\jackson\core\jackson-core\2.9.5\jackson-core-2.9.5.jar;G:\Maven-LocalRepository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.9.5\jackson-datatype-jdk8-2.9.5.jar;G:\Maven-LocalRepository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.9.5\jackson-datatype-jsr310-2.9.5.jar;G:\Maven-LocalRepository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.9.5\jackson-module-parameter-names-2.9.5.jar;G:\Maven-LocalRepository\org\hibernate\validator\hibernate-validator\6.0.9.Final\hibernate-validator-6.0.9.Final.jar;G:\Maven-LocalRepository\javax\validation\validation-api\2.0.1.Final\validation-api-2.0.1.Final.jar;G:\Maven-LocalRepository\org\jboss\logging\jboss-logging\3.3.2.Final\jboss-logging-3.3.2.Final.jar;G:\Maven-LocalRepository\com\fasterxml\classmate\1.3.4\classmate-1.3.4.jar;G:\Maven-LocalRepository\org\springframework\spring-web\5.0.5.RELEASE\spring-web-5.0.5.RELEASE.jar;G:\Maven-LocalRepository\org\springframework\spring-beans\5.0.5.RELEASE\spring-beans-5.0.5.RELEASE.jar;G:\Maven-LocalRepository\org\springframework\spring-webmvc\5.0.5.RELEASE\spring-webmvc-5.0.5.RELEASE.jar;G:\Maven-LocalRepository\org\springframework\spring-expression\5.0.5.RELEASE\spring-expression-5.0.5.RELEASE.jar;G:\Maven-LocalRepository\org\springframework\spring-core\5.0.5.RELEASE\spring-core-5.0.5.RELEASE.jar;G:\Maven-LocalRepository\org\springframework\spring-jcl\5.0.5.RELEASE\spring-jcl-5.0.5.RELEASE.jar;G:\Maven-LocalRepository\org\springframework\boot\spring-boot-starter-actuator\2.0.1.RELEASE\spring-boot-starter-actuator-2.0.1.RELEASE.jar;G:\Maven-LocalRepository\org\springframework\boot\spring-boot-actuator-autoconfigure\2.0.1.RELEASE\spring-boot-actuator-autoconfigure-2.0.1.RELEASE.jar;G:\Maven-LocalRepository\org\springframework\boot\spring-boot-actuator\2.0.1.RELEASE\spring-boot-actuator-2.0.1.RELEASE.jar;G:\Maven-LocalRepository\io\micrometer\micrometer-core\1.0.3\micrometer-core-1.0.3.jar;G:\Maven-LocalRepository\org\hdrhistogram\HdrHistogram\2.1.10\HdrHistogram-2.1.10.jar;G:\Maven-LocalRepository\org\latencyutils\LatencyUtils\2.0.3\LatencyUtils-2.0.3.jar;G:\Maven-LocalRepository\org\springframework\boot\spring-boot-starter-aop\2.0.1.RELEASE\spring-boot-starter-aop-2.0.1.RELEASE.jar;G:\Maven-LocalRepository\org\springframework\spring-aop\5.0.5.RELEASE\spring-aop-5.0.5.RELEASE.jar;G:\Maven-LocalRepository\org\aspectj\aspectjweaver\1.8.13\aspectjweaver-1.8.13.jar;G:\Maven-LocalRepository\org\springframework\boot\spring-boot-starter-thymeleaf\2.0.1.RELEASE\spring-boot-starter-thymeleaf-2.0.1.RELEASE.jar;G:\Maven-LocalRepository\org\thymeleaf\thymeleaf-spring5\3.0.9.RELEASE\thymeleaf-spring5-3.0.9.RELEASE.jar;G:\Maven-LocalRepository\org\thymeleaf\thymeleaf\3.0.9.RELEASE\thymeleaf-3.0.9.RELEASE.jar;G:\Maven-LocalRepository\org\attoparser\attoparser\2.0.4.RELEASE\attoparser-2.0.4.RELEASE.jar;G:\Maven-LocalRepository\org\unbescape\unbescape\1.1.5.RELEASE\unbescape-1.1.5.RELEASE.jar;G:\Maven-LocalRepository\org\thymeleaf\extras\thymeleaf-extras-java8time\3.0.1.RELEASE\thymeleaf-extras-java8time-3.0.1.RELEASE.jar;G:\IDEAWorkSpaces_BeeCredit\z_cloud_commons\target\classes;G:\Maven-LocalRepository\org\springframework\spring-context\5.0.5.RELEASE\spring-context-5.0.5.RELEASE.jar;G:\Maven-LocalRepository\com\alibaba\boot\dubbo-spring-boot-starter\0.2.0\dubbo-spring-boot-starter-0.2.0.jar;G:\Maven-LocalRepository\com\alibaba\dubbo\2.6.2\dubbo-2.6.2.jar;G:\Maven-LocalRepository\org\javassist\javassist\3.20.0-GA\javassist-3.20.0-GA.jar;G:\Maven-LocalRepository\org\jboss\netty\netty\3.2.5.Final\netty-3.2.5.Final.jar;G:\Maven-LocalRepository\org\apache\zookeeper\zookeeper\3.4.9\zookeeper-3.4.9.jar;G:\Maven-LocalRepository\jline\jline\0.9.94\jline-0.9.94.jar;G:\Maven-LocalRepository\io\netty\netty\3.10.5.Final\netty-3.10.5.Final.jar;G:\Maven-LocalRepository\org\apache\curator\curator-framework\2.12.0\curator-framework-2.12.0.jar;G:\Maven-LocalRepository\org\apache\curator\curator-client\2.12.0\curator-client-2.12.0.jar;G:\Maven-LocalRepository\com\alibaba\boot\dubbo-spring-boot-autoconfigure\0.2.0\dubbo-spring-boot-autoconfigure-0.2.0.jar;G:\Maven-LocalRepository\com\alibaba\simpleimage\1.2.3\simpleimage-1.2.3.jar;G:\Maven-LocalRepository\commons-io\commons-io\1.2\commons-io-1.2.jar;G:\Maven-LocalRepository\commons-lang\commons-lang\2.4\commons-lang-2.4.jar;G:\Maven-LocalRepository\log4j\log4j\1.2.12\log4j-1.2.12.jar;G:\Maven-LocalRepository\commons-logging\commons-logging\1.0.4\commons-logging-1.0.4.jar;G:\Maven-LocalRepository\org\slf4j\slf4j-log4j12\1.7.25\slf4j-log4j12-1.7.25.jar;G:\Maven-LocalRepository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;G:\Maven-LocalRepository\com\alibaba\easyexcel\1.1.2-beta5\easyexcel-1.1.2-beta5.jar;G:\Maven-LocalRepository\org\apache\poi\poi\3.17\poi-3.17.jar;G:\Maven-LocalRepository\commons-codec\commons-codec\1.11\commons-codec-1.11.jar;G:\Maven-LocalRepository\org\apache\commons\commons-collections4\4.1\commons-collections4-4.1.jar;G:\Maven-LocalRepository\org\apache\poi\poi-ooxml\3.17\poi-ooxml-3.17.jar;G:\Maven-LocalRepository\org\apache\poi\poi-ooxml-schemas\3.17\poi-ooxml-schemas-3.17.jar;G:\Maven-LocalRepository\org\apache\xmlbeans\xmlbeans\2.6.0\xmlbeans-2.6.0.jar;G:\Maven-LocalRepository\stax\stax-api\1.0.1\stax-api-1.0.1.jar;G:\Maven-LocalRepository\com\github\virtuald\curvesapi\1.04\curvesapi-1.04.jar;G:\Maven-LocalRepository\cglib\cglib\3.1\cglib-3.1.jar;G:\Maven-LocalRepository\org\ow2\asm\asm\4.2\asm-4.2.jar;G:\Maven-LocalRepository\com\google\guava\guava\21.0\guava-21.0.jar com.cloud.web.myclassloader.MyClassLoader
由我加载

Process finished with exit code 0

从结果我们看,因为我们加载的类的父加载器是系统加载器,所以调用双亲委托的loadClass,会直接加载掉java.io.InputStream类,只有在加载双亲中没有的ClassLoaderTest类,才会用到我们自己的findClass加载逻辑加载指定路径下的类文件,满足双亲委派模型,热部署和加密解密的ClassLoader实现,大同小异。只是findClass的逻辑发生改变而已。

 

附:                                        类加载器双亲委派模型

 

                                            

  • BootStrapClassLoader:启动类加载器,该ClassLoader是jvm在启动时创建的,用于加载 $JAVA_HOME/jre/lib下面的类库(或者通过参数-Xbootclasspath指定)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不能直接通过引用进行操作。
  • ExtClassLoader:扩展类加载器,该ClassLoader是在sun.misc.Launcher里作为一个内部类ExtClassLoader定义的(即 sun.misc.Launcher$ExtClassLoader),ExtClassLoader会加载 $JAVA_HOME/jre/lib/ext下的类库(或者通过参数-Djava.ext.dirs指定)。
  • AppClassLoader:应用程序类加载器,该ClassLoader同样是在sun.misc.Launcher里作为一个内部类AppClassLoader定义的(即 sun.misc.Launcher$AppClassLoader),AppClassLoader会加载java环境变量CLASSPATH所指定的路径下的类库,而CLASSPATH所指定的路径可以通过System.getProperty("java.class.path")获取;当然,该变量也可以覆盖,可以使用参数-cp,例如:java -cp 路径 (可以指定要执行的class目录)。
  • CustomClassLoader:自定义类加载器,该ClassLoader是指我们自定义的ClassLoader,比如tomcat的StandardClassLoader属于这一类;当然,大部分情况下使用AppClassLoader就足够了。

委托机制的意义:

防止内存中出现多份同样的字节码 

 

关于类加载有几个重要的知识点:

  • 比如两个类A和类B都要加载System类:
  • 如果不用委托而是自己加载自己的,那么类A就会加载一份System字节码,然后类B又会加载一份System字节码,这样内存中就出现了两份System字节码。 
  • 如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下。这里的System就能在Bootstrap中找到然后加载,如果此时类B也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回内存中的System即可而不需要重新加载,这样内存中就只有一份System的字节码了。
  • 在 Java 中我们用完全类名来标识一个类,而在 JVM 层面,使用完全类名 + CloassLoader 对象实例 ID 作为唯一标识,因此使用不同实例的类加载器,加载的两个同名的类,他们的类实例是不同的,并且不能强制转换
  • 在双亲委派机制中,类加载器查找类时,是一层层往父类加载器查找的,最后才查看自己,如果都找不到则会抛出异常,而不是一层层往下找的
  • 每个运行中的线程都有一个 CloassLoader,并且会从父线程中继承(默认是应用类加载器),在没有显式声明由哪个类加载器加载类时(比如 new 关键字),将默认由当前线程的类加载器加载该类

                                                                                                                      【本文章纯粹为个人笔记留存】   

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值