为什么要自定义ClassLoader进行类加载
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么要自定义ClassLoader进行类加载相关的知识,希望对你有一定的参考价值。
假如,你开发的java程序都需要从E:classloader1目录下的类文件中加载class,而不是系统指定的系统目录或者classpath目录下加载,则如何解决? 需要自定义classloader。
ClassLoader:加载各种class文件到JVM中。ClassLoader是抽象类。
类的加载过程分为加载阶段、连接阶段、初始化。
- 加载阶段:寻找class文件。
- 连接阶段:验证class文件的正确性;为类的静态变量准备内存,并为其初始化默认值;把类中符号引用转换为直接引用。
- 初始化阶段:为累的静态变量赋予正确的初始值,也就是在代码的值。
JVM目前对类的初始化时一次lazy。所以何时初始化就会根据不同情况而不同,如果主动使用,则会进行类的初始化。
类的6种主动使用情况:
- 通过调用静态变量会导致类的初始化。 但是若是静态常量,则不会。
- 调用静态方法,会导致类的初始化过程。
- 调用class.forName。
- 调用子类会导致父类初始化,但是如果只是通过子类调用父内的静态方法后者变量,则不会导致子类初始化。
- 调用main方法的类也会被初始化。
- 调用 new关键字,必然会进行类的初始化,但是数组除外。
类的被动使用:除了主动的以外,都是被动的。
类的加载过程中阶段,常量是在javac编译阶段就替换了值,所以不会再类的连接阶段进行默认初始化(给相应类变量一个相关类型在没有被设置时候的默认值)。
类的初始化阶段中,赋值动作和静态语句块的执行代码,这个是按照顺序执行。虚拟机会保证这些动作会优先执行,因此父类的静态变量总是能够得到优先赋值。
JVM默认的加载类去寻找特定的几个目录下的类进行加载。比如当前路径,classpath等。若想自定义路径,或者在加载过程中,有些个性化的需求,则需要自定义加载类进行加载。
自定义加载类,还有一个好处,可以根据需求,进行单独卸载和重新加载,这样就可以进行无需停机的热部署。
下面时自定义加载类的使用方法:
- 先编写一个简单的类,用于测试被自定义类加载。
‘‘‘
package classLoaderStudy;
public class ClassLoaderHello {
static
{
System.out.println("hello world");
}
public String welcome()
{
return "Hello world";
}
}
‘‘‘
- 编写自定义加载类,
其中findClass是必须重写的,功能就是读入class文件位字节流,然后调用ClassLoader的自带方法defineClass方法对类在方法区进行定义,这样,才能使用这个自定义类。
‘‘‘
package classLoaderStudy;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class MyClassLoader extends ClassLoader{
private final static Path DEFAULT_CLASS_DIR = Paths.get("E:", "classloader1");
private final Path classDir;
public MyClassLoader()
{
super();
this.classDir = DEFAULT_CLASS_DIR;
}
public MyClassLoader(String classDir)
{
super();
this.classDir = Paths.get(classDir);
}
public MyClassLoader(String classDir,ClassLoader parent)
{
super(parent);
this.classDir = Paths.get(classDir);
}
private byte[] readClassBytes(String name)
throws ClassNotFoundException
{
String classPath = name.replace(".", "/");
Path classFullPath = classDir.resolve(Paths.get(classPath+".class"));
if(!classFullPath.toFile().exists())
{
throw new ClassNotFoundException("The Class "+name+" not found");
}
try(ByteArrayOutputStream baos = new ByteArrayOutputStream())
{
Files.copy(classFullPath, baos);
return baos.toByteArray();
}
catch(IOException e)
{
throw new ClassNotFoundException("Load the class "+ name +" occur error.",e);
}
}
@Override
/*
* (non-Javadoc)
* @see java.lang.ClassLoader#findClass(java.lang.String)
* 必须要重写这个类
*/
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 ");
}
return this.defineClass(name, classBytes, 0,classBytes.length);
}
@Override
public String toString()
{
return "My ClassLoader";
}
public static void main(String[] args) {
MyClassLoader classLoader = new MyClassLoader();
Class<?> aClass =null;
try {
aClass = classLoader.loadClass("classLoaderStudy.ClassLoaderHello");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(aClass.getClassLoader());
try {
Object helloWorld = aClass.newInstance();
System.out.println(helloWorld);
Method welcomeMethod = aClass.getMethod("welcome");
String result = (String) welcomeMethod.invoke(helloWorld);
System.out.println(result);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}catch (Exception e)
{
e.printStackTrace();
}
}
}
‘‘‘
- 测试
将classLoaderStudy\ClassLoaderHello.class
文件拷贝到 E:\classloader1
目录下,
如E:\classloader1\classLoaderStudy\ClassLoaderHello.class
并将工程中的 ClassLoaderHello.java
文件和ClassLoaderHello.class
文件删除,从而确保不会加载工程中的这个类,以便测试自定义加载类。
在Eclipse中运行MyClassLoader
类,结果如下
‘‘‘
My ClassLoader
hello worldbr/>[email protected]
Hello world
‘‘‘
以上是关于为什么要自定义ClassLoader进行类加载的主要内容,如果未能解决你的问题,请参考以下文章