自定义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)函数即可。、
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 关键字),将默认由当前线程的类加载器加载该类
【本文章纯粹为个人笔记留存】
下一篇: 品牌推广策划怎么做?如何推广产品?