java 动态特性 -- ClassLoader 类加载器
简介:
类加载器就是加载其他类的类,它负责将字节码文件加载到内存,创建class 对象。在大部分应用编程中,我们需要自己实现ClassLoader.不过ClassLoader 一般是系统提供的,不需要自己实现。不过,通过创建自定义的ClassLoader,可以实现一些强大灵活的功能。比如:
(1) 热部署。在不重启Java程序的情况下,动态替换类的实现,比如java Web开发的jsp技术就利用自定义的ClassLoader 实现修改jsp代码立即生效。
(2) 应用模块化和相互隔离。不同的ClassLoader可以加载相同的类但互相隔离。
(3)从不同的地方灵活加载。系统默认一般是从本地的.class文件或jar包文件中加载字节码文件,通过自定义ClassLoader,我们可以从共享的web服务器,数据库,缓存服务器等其它地方加载字节码文件。
类加载的基本机制和过程:
类加载器有三个,分别是如下:
(1) 启动类加载器(Bootstrap ClassLoader):这个加载器是java 虚拟机实现的一部分,不是java语言实现的,一般是c++实现,它负责加载java的基础类,主要是<JAVA_HOME>/lib/rt.jar,我们常用的类库,String,集合都是在这个jar包中。
(2) 扩展类加载器(Extension ClassLoader):这个类加载器的实现类是sun.misc.launcher$ExtClassLoader,它加载java的一些扩展类,一般都是
<JAVA_HOME>/lib/ext目录中的jar包。
(3) 应用程序加载器(Application ClassLoader):这个加载器的实现类是sun.misc.launcher$AppClassLoader,它负责加载应用程序的类,包括自己写的和引入的第三方类库,即所有在类路径中指定的类。
这三个类的关系可以认为是父子关系,Application --> Extension -->bootstrap.注意不是继承关系,而是父子委派关系,子ClassLoader 有一个变量parent 指向父ClassLoader,子ClassLoader 加载类时,一般首先会通过父ClassLoader加载,具体流程如下:
ClassLoader 常用方法:
方法 | 返回值 | 说明 |
---|---|---|
getClassLoader() | ClassLoader | 获取当前对象实际加载它的ClassLoader |
getParent() | ClassLoader | 获取父ClassLoader,父loader 是bootstrap ClassLoader 则返回null |
getSystemClassLoader | ClassLosder | 获取系统默认的系统加载器 |
loadClass(String name) | Class<?> cls | 加载类对象,不过不会执行类的初始化代码 |
示例:
public class ClassLoaderDemo {
public static void main(String[] args) {
//getClassLoader() 获取当前类的类加载器,运行运用程序获取的类加载器一般都是Application ClassLoader
ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
while(classLoader != null){
System.out.println(classLoader.getClass().getName());
//getParent() 获取父类类加载器
classLoader = classLoader.getParent();
}
//如果ClassLoader 是 Bootstrap ClassLoader ,则返回null,因为Bootstrap ClassLoader 不是java语言写的,没有class
System.out.println(String.class.getClassLoader());
//ClassLoader的静态方法getSystemClassLoader(),用于获取默认的系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader.getClass().getName());
try {
Class<?> cls = systemClassLoader.loadClass("java.util.ArrayList");
//由于是委派机制,所以这里返回的loader 不一定是调用的 systemClassLoader,这里加载的是ArrayList,所以返回的是null
ClassLoader loader = cls.getClassLoader();
System.out.println(loader);
}catch (ClassNotFoundException e){
e.printStackTrace();
}
}
public void say(){
System.out.println("ClassLoad");
}
}
探讨反射Class对象的两个forName()方法:
(1)forName(String name):使用系统类加载器加载,会执行类的初始化代码(static 语句块)
(2)forName(String name,boolean initialize,ClassLoader loader):执行了类加载器,它是通过loadclass()方法加载类对象的,不会执行类的初始化代码。
使用loadClass()方法为啥会不执行类 初始化代码呢,一起来看下面源码:
public class ClsForNameDemo {
static {
System.out.println("say hello!");
}
public static void main(String[] args) throws ClassNotFoundException {
//这里会直接打印出 say hello!
Class<?> cls =Class.forName("ClassLoader.ClsForNameDemo");
System.out.println("------------");
//按理说这里initialize参数是为true,为啥没有打印出say hello!
Class<?> clsL = Class.forName("ClassLoader.ClsForNameDemo",true,ClassLoader.getSystemClassLoader());
//下面这样也打印不出来,说明指定ClassLoad的forName方法 直接调用就是下面这样,下面来看下loadClass()
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
String className = ClsForNameDemo.class.getName();
//默认的参数initialize就为false,调用父类的也是默认为false,
Class<?> c = classLoader.loadClass(className);
}
}
//loadClass 会调用ClassLoader 的另外一个loadClass 方法
public Class<?> loadClass(String name) throws ClassNotFoundException {
return this.loadClass(name, false);
}
//继续往下看逻辑
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized(this.getClassLoadingLock(name)) {
//首先检查类是否已经加载了
Class<?> c = this.findLoadedClass(name);
if (c == null)
long t0 = System.nanoTime();
//如果没有加载,先委派父ClassLoader或者Bootstrap ClassLoader去加载
try {
if (this.parent != null) {
//委派父ClassLoader,resolve参数固定为false
c = this.parent.loadClass(name, false);
} else {
c = this.findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException var10) {
//没找到,就自己尝试加载
}
if (c == null) {
long t1 = System.nanoTime();
//自己去加载,findclass才是当前ClassLoader 的真正记载方法
c = this.findClass(name);
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//链接,执行static语句块
this.resolveClass(c);
}
return c;
}
}
类加载的应用:可配置的策略
/**
* 类加载器的应用:可配置的策略
*当一个接口有多个实现类时,适用于不同的场合,具体使用哪个在配置文件中配置,通过更改配置
* 不用修改代码,就可以改变程序的行为,在设计模式中,这是一种策略模式
* 下面是一个简单的例子
*/
public class ClassLoadApplication {
public static <T> T createService(String fileName) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
Properties properties = new Properties();
properties.load(new FileInputStream(fileName));
String className = properties.getProperty("service");
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?> cls = classLoader.loadClass(className);
return (T)cls.newInstance();
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
Iservice iservice = createService("C:\\Users\\96979\\Desktop\\gittest\\test1\\src\\main\\resources\\config\\config.properties");
iservice.action();
}
}
自定义ClassLoader:
package ClassLoader;
import java.io.*;
/**
* 自定义ClassLoader
* 自定义的Classloader 可以实现Tomcat应用隔离,支持JSP,OSGI实现动态模块化的基础
* 自定义ClassLoader需重写findclass,使用自己的逻辑寻找class文件字节码的字节形式
* 然后使用defineClass(String name, byte[] b, int off, int len) 方法来转换为class 对象
*/
public class MineDefineClassLoader extends ClassLoader {
private static final String BASE_DIR = "ClassLoader/application/Test/";
public MineDefineClassLoader() {
}
/**
* 获取指定文件夹下的Class对象
* @param name
* @return
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String fileName = name.replaceAll("\\.","/");
fileName = BASE_DIR + fileName + ".class";
Class<?> cls = null;
try {
byte[] bytes = MineDefineClassLoader.readFileToByteArray(fileName);
System.out.println(bytes.length);
//将字节码转为Class对象
cls = defineClass("ClassLoader.application.Test", bytes, 0, bytes.length);
}catch (IOException e){
e.printStackTrace();
}
return cls;
}
/**
* 指定父类类加载器
* @param parent
*/
protected MineDefineClassLoader(ClassLoader parent) {
super(parent);
}
public static byte[] readFileToByteArray(String fileName) throws IOException {
InputStream inputStream = new FileInputStream(fileName);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try{
copy(inputStream,byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
}finally {
inputStream.close();
}
}
public static void copy(InputStream inputStream, OutputStream outputStream) throws IOException {
byte [] buf = new byte[4096];
int byteRead = 0;
while((byteRead = inputStream.read()) != -1){
outputStream.write(buf,0,byteRead);
}
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
MineDefineClassLoader mineDefineClassLoader = new MineDefineClassLoader(ClassLoader.getSystemClassLoader().getParent().getParent());
Class cls = mineDefineClassLoader.findClass("ClassLoaderDemo");
ClassLoaderDemo classLoaderDemo =(ClassLoaderDemo) cls.newInstance();
classLoaderDemo.say();
//自定义ClassLoader 可以创建多个,对于同一个类,每个自定义的ClassLoader 对象都就可以加载一次,
//得到同一个类的不同class对象
MineDefineClassLoader mineDefineClassLoader1 = new MineDefineClassLoader();
String className = "Test";
Class<?> cls1 = mineDefineClassLoader1.loadClass(className);
MineDefineClassLoader mineDefineClassLoader2 = new MineDefineClassLoader();
Class<?> cls2 = mineDefineClassLoader2.loadClass(className);
if (cls1 != cls2){
System.out.println("different classes");
}
}
}
通过自定义的ClassLoader,可以通过创建一个新的自定义loader就可以加载一次Class,而且得到的Class对象是最新的,从而实现动态更新,实现热部署。
总结:
本文介绍了Java中的类加载机制,包括Java加载类的基本过程,类ClassLoader的用法,以及如何创建自定义的 ClassLoader.通过本文,希望能帮助小伙伴更好的掌握java动态特性的理解。