自定义的类加载
自定义的类加载器很牛逼,也是因为他们,有了强大的系统,例如Tomcat的ClassLoader, 因为作为一个Web服务器需要考虑到许多,例如:
1、部署在同一个服务器上的两个Web应用程序所使用的Java类库可以实现相互隔离。这是最基本的要求,两个不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求一个类库在一个服务器中只有一份,服务器应当保证两个应用程序的类库可以互相使用
2、部署在同一个服务器上的两个Web应用程序所使用的Java类库可以相互共享。这个需求也很常见,比如相同的Spring类库10个应用程序在用不可能分别存放在各个应用程序的隔离目录中
3、支持热替换,我们知道JSP文件最终要编译成.class文件才能由虚拟机执行,但JSP文件由于其纯文本存储特性,运行时修改的概率远远大于第三方类库或自身.class文件,而且JSP这种网页应用也把修改后无须重启作为一个很大的优势看待
因为这些问题,Java提供给用户使用的ClassLoader就无法满足需求, 也需要我们自己研究
一个自定义类加载器需要继承于ClassLoader此抽象类,下面我们贴个源码分析下
/**
* Loads the class with the specified <a href="#name">binary name</a>.
在方法注释中写到:以指定的二进制名字的文件加载类,即是我们的.class文件
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//获取锁, 类在内存中只有一份, 不可多线程操作
synchronized (getClassLoadingLock(name)) {
// 首先, 我们要检查此类是否已经被加载了,若是已经被加载,直接返回
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//使用父类加载器加载此类, 这也说明了此父委托机制的重点
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//使用根类加载器加载, 若能加载即返回对象,否则返回null
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
//如果任然未找到,即用自个定义的findclass去找
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(String name)方法是我们自定义类加载器至关重要的一步,并且它是用protected所修饰的,也就是提醒我们赶紧重写
//这是ClassLoader中的findClass(String name), 啥都没, 就是提供重写
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
下面来个基于磁盘的自定义类加载器的例子
package com.pjh.concurrent.part2.classLoader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class MyClassLoader extends ClassLoader {
//定义一个默认的存放路径
private final static Path DEFAULE_CLASSPATH = Paths
.get("E:", "javaResource\\ThreadLearn\\src");
private final Path classpathDir;
public MyClassLoader(){
super();
this.classpathDir = DEFAULE_CLASSPATH;
}
public MyClassLoader(String classpath, ClassLoader parent){
super(parent);
this.classpathDir = Paths.get(classpath);
}
//传入指定class路径
public MyClassLoader(String classpath){
this.classpathDir = Paths.get(classpath);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classBytes = this.readClassBytes(name);
if(null == classBytes || classBytes.length == 0){
throw new ClassNotFoundException("Can not load the class " + name);
}
return this.defineClass(name, classBytes, 0, classBytes.length);
}
//以二进制形式读取文件至内存中\
private byte[] readClassBytes(String name) throws ClassNotFoundException {
//将报名分隔符转换为文件路径分隔符号
String classpath = name.replace(".", "/");
//完整路径
Path fullPath = classpathDir.resolve(Paths.get(classpath + ".class"));
if (!fullPath.toFile().exists()) {
throw new ClassNotFoundException("The class " + name + " not found");
}
//二进制数组输出流
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
Files.copy(fullPath, baos);
} catch (IOException e) {
}
return baos.toByteArray();
}
}
下面来一个实体类
package com.pjh.concurrent.part2.classLoader;
public class HelloWorld {
public HelloWorld() {
}
public String welcome() {
return "welcome !!!!!!!!!!";
}
static {
System.out.println("Hello world");
}
}
再来个测试类
package com.pjh.concurrent.part2.classLoader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MyClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> aClass = myClassLoader.loadClass("com.pjh.concurrent.part2.classLoader.HelloWorld");
System.out.println(aClass.getClassLoader());
Object helloWorld = aClass.newInstance();
System.out.println(helloWorld);
Method method = aClass.getMethod("welcome");
String result = (String) method.invoke(helloWorld);
System.out.println("Result: " + result);
}
}
输出结果
com.pjh.concurrent.part2.classLoader.MyClassLoader@74a14482
Hello world
com.pjh.concurrent.part2.classLoader.HelloWorld@677327b6
Result: welcome !!!!!!!!!!
但是要把目录下的HelloWorld.java删除了 否则会使用application加载器加载
如果需要破坏双亲委托机制, 重写loadClass即可
参考博客:https://www.cnblogs.com/szlbm/p/5504631.html
参考书籍:<<Java高并发详解>>汪文君著