类加载器:
-
Bootstrap ClassLoader:是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载
%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes
中的类。 -
Extension ClassLoader:用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类。
-
AppClassLoader:加载classpath指定的类,是最常使用的一种加载器。
案例实战
通过一个案例来,探究一番
public class Test {
public static void main(String[] arg) {
//获取Test类的类加载器
ClassLoader appClassLoader = Test.class.getClassLoader();
System.out.println(" Test类的加载器是:" + appClassLoader.getClass().getSimpleName());
//获取AppClassLoader的父类
ClassLoader extClassLoader=appClassLoader.getParent();
System.out.println(" Test类的加载器的父类是:" + extClassLoader.getClass().getSimpleName());
//获取ExtClassLoader的parent
ClassLoader bootstrapClassLoader=extClassLoader.getParent();
System.out.println(" Test类的加载器的父类的父类是:" + bootstrapClassLoader);
}
}
输出结果是:
Test类的加载器是:AppClassLoader
Test类的加载器的父类是:ExtClassLoader
Test类的加载器的父类的父类是:null
从以上可知,加载Test类是AppClassLoader。AppClassLoader的父类是ExtClassLoader。ExtClassLoader的父类BootstrapClassLoder,但却为null?由于Bootstrap Loader是用C++语言写的,依java的观点来看,逻辑上并不存在Bootstrap Loader的类实体。
最终,归纳如下图所示:
委托模型机制:
理解:
当类加载器有载入类的需求时,会先请示其Parent,让Parent使用其提供的路径帮忙载入。若Parent找不到,那么才自己依照自己的搜索路径搜索类。
好处:
因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
自定义ClassLoader
既然JVM已经提供了默认的类加载器,为什么还要定义自已的类加载器呢?
因为Java中提供的默认ClassLoader,只加载指定目录下的jar和class,如果我们想加载其它位置的类或jar时,比如:我要加载网络上的一个class文件,通过动态加载到内存之后,要调用这个类中的方法实现我的业务逻辑。在这样的情况下,默认的ClassLoader就不能满足我们的需求了,所以需要定义自己的ClassLoader。
实战案例:
1.编写额外的类文件
先创建项目工程下,创建一个plugi目录,然后在该目录下创建,一个PluginTest.class文件,如下图所示:
编写以下代码:
package com.xingen.classloader.plugin;
public class PluginTest {
public PluginTest() {
}
public void print() {
System.out.append("Plugin加载,自定义ClassLoader加载class文件");
}
}
该class模拟外部的需要加载的类,与src不在同一路径下。
2. 项目的src下编写相关代码
先来查看下,ClassLoader中的查找类的loadClass()
方法的源码:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
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) {
//先父类parent中查找。
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) {//若parent中没找到,调用findClass()在本身中查找。
// 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;
}
}
从以上可知:
- ClassLoader加载机制是委派双亲机制,会先parent先去查找对应的类,若是没有找到,则在本身中查找。
因此,定义ClassLoader的子类,需要继承ClassLoader外,还需要重写findClass()
先定义ClassLoader的子类,重写findClass()
。
public class HotClassLoader extends ClassLoader {
private static final ClassLoader ParentClassLoader = ClassLoader.getSystemClassLoader();
private String filePath;
public HotClassLoader(String filePath) {
super(ParentClassLoader);
this.filePath = filePath;
}
/**
* 重写父类的findClass()
* <p>
* loadClass()中已经实现搜索类的算法,
* 当loadClass()搜索不到就会调用findClass()
*
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("HotClassLoader 执行 findClass() ");
byte[] bytes = StreamUtils.fileToByteArray(new File(filePath));
//调用ClassLoader的defineClass(),传入byte,从而构建出该类的Class对象。
Class<?> mClass = defineClass(name, bytes, 0, bytes.length);
return mClass;
}
}
编写工具类:将额外的类文件,转成byte。
public class StreamUtils {
/**
* file转成byte
* @param file
* @return
*/
public static byte[] fileToByteArray(File file){
byte[] bytes=null;
FileInputStream fileInputStream=null;
ByteArrayOutputStream byteArrayOutputStream=null;
try {
fileInputStream=new FileInputStream(file);
FileChannel fileChannel=fileInputStream.getChannel();
byteArrayOutputStream=new ByteArrayOutputStream();
WritableByteChannel writableByteChannel = Channels.newChannel(byteArrayOutputStream);
ByteBuffer buffer=ByteBuffer.allocate(1024);
while (true) {
int i = fileChannel.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
writableByteChannel.write(buffer);
buffer.clear();
}
bytes=byteArrayOutputStream.toByteArray();
}catch (Exception e){
bytes=null;
e.printStackTrace();
}finally {
try {
if (byteArrayOutputStream!=null){
byteArrayOutputStream.close();
}
if (fileInputStream!=null){
fileInputStream.close();
}
}catch (Exception e2){
}
}
return bytes;
}
}
编写测试代码:
public class TestClient {
public static void main(String[] args) {
testClassLoader();
}
private static void testClassLoader() {
String dir = System.getProperty("user.dir");
File pluginFile = new File(dir + File.separator + "plugin" + File.separator + "PluginTest.class");
//额外的PluginTest类的路径
final String filePath = pluginFile.getAbsolutePath();
try {
//创建ClassLoader子类对象
HotClassLoader hotClassLoader = new HotClassLoader(filePath);
//包名下的类名
final String className = "com.xingen.classloader.plugin.PluginTest";
//将该类加载到JVM
Class<?> objectClass = hotClassLoader.loadClass(className);
if (objectClass == null) {
System.out.println(" 加载plugin中的类失败");
} else {
//反射调用额外路径下类中的方法
Method printMethod = objectClass.getDeclaredMethod("print");
printMethod.setAccessible(true);
printMethod.invoke(objectClass.newInstance(), null);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
创建ClassLoader的子类,调用loadClass()
,传入类名,获取到对应Class对象,从而调用其相关的方法。
最后,运行案例,控制台输出结果:
HotClassLoader 执行 findClass()
Plugin加载,自定义ClassLoader加载class文件
完整的项目结构如下所示: