java高*之类热加载

时间:2021-03-26 17:19:45

java高*之类热加载

有时候我们会遇到热加载的需求,即在修改了类的代码后,不重启的情况下动态的加载这个类。

先介绍一个java的类加载器 之前写过一篇关于类加载器的,请访问此地址:

http://blog.csdn.net/maosijunzi/article/details/42555617

看到这个问题,我想到有两种方式来实现:(能力有限,不知道还有没有其他的方案)

1:把原来的类信息卸载掉,然后重新加载此类。

2:新建一个类加载器(new),重新加载此类,不管原来的类信息,等待垃圾回收它。

第一种方案是行不通的,因为java并没有给我们提供手动卸载类信息的功能,也就是运行时方法区内的类信息是不能卸载的,

除非这个类已经不再使用,这时GC会自动把它回收掉。所以我们只能通过第二种方案来实现。

几个问题

在使用这种方案实现之前我们考虑几个问题,注意:下面的问题是在这种方案下提出的,也在这种方案下回答,不适用与其他方案。

1:是不是所有的类都能进行热加载呢?

我们程序的入口类都是系统类加载器加载的,也就是AppClassLoader加载的。当你重新使用系统类加载器加载这个类的时候

是不会被重新加载的。因为虚拟机会检测这个类是否被加载过,如果已经被加载过,那么就不会重新加载。所以由系统类加载器加载的

类,是不能进行热加载的。只有使用我们自定义的类加载器加载的类才能热加载。

2:自定义类加载器的父加载器应该是哪个加载器?

我们自定义类加载器的父加载器有两种选择,一个是系统类加载器(AppClassLoader),另一种是扩展类加载器(ExtClassLoader)。

首先我们要知道,扩展类加载器是系统类加载器的父加载器。

我们先考虑第一种,如果父加载器是系统类加载器(当然如果你不指定父加载器,默认就是系统类加载器),那么会出现一个现象,这个动态

加载的类不能出现在系统类加载器的classpath下。因为如果在系统类加载器的classpath下的话,当你用自定义类加载器去加载的时候,会先

使用父类加载器去加载这个类,如果父类加载器可以加载这个类就直接加载了,达不到热加载的目的。所以我们必须把要热加载的类从classpath

下删除。

在考虑第二种,如果父加载器是扩展类加载器,这时候热加载的类就可以出现在classpath下,但又会出现另一种现象,这个类中不能引用由系统

类加载器加载的类。因为这时候,自定义类加载器和系统类加载器是兄弟关系,他们两个加载器加载的类是互不可见的。这个问题貌似是致命的。

除非热加载的类中只引用了扩展类加载器加载的类(大部分javax开头的类)。

所以我们自定义的类加载器的父加载器最好是系统类加载器。

3:热加载的类要不要实现接口?

要不要实现接口,要根据由系统类加载器加载的类A中是否有热加载类B的引用。如果有B的引用,那么加载A的时候,系统类加载器就会去加载B,这时候B

不在classpath下,就会加载报错。这时候我们就需要为B定义一个接口,A类中只有接口的引用。这样我们使用系统类加载器加载接口,使用自定义

类加载器加载B类,这样我们就可以热加载B类。

如果A中没有B的引用的话,就灵活多了,可以实现接口,也可以不实现接口,都可以进行热加载。

解决了这三个问题后,实现代码就已经在我们心中了。下面直接看代码:
包结构如下:

java高*之类热加载
java高*之类热加载

源码:

package com.load.custom;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;

public class MyClassLoader extends ClassLoader {
private String classpath;

public MyClassLoader(String classpath){
super(ClassLoader.getSystemClassLoader());
this.classpath = classpath;
}

@Override
public Class<?> findClass(String name) {
System.out.println("加载类==="+name);
byte[] data = loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}

public byte[] loadClassData(String name) {
try {
name = name.replace(".", "//");
FileInputStream is = new FileInputStream(new File(classpath + name + ".class"));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while ((b = is.read()) != -1) {
baos.write(b);
}
is.close();
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
package com.load.manager;

/**
* @author chuer
* @Description: 此类的子类需要动态更新
* @date 2015年5月13日 上午11:09:24
* @version V1.0
*/

public interface BaseManager {
public void logic();
}
package com.load;

import com.load.custom.MyClassLoader;
import com.load.manager.BaseManager;

public class LoadInfo {

private MyClassLoader myLoader;
private long loadTime;
private BaseManager manager;

public LoadInfo(MyClassLoader myLoader,long loadTime){
this.myLoader = myLoader;
this.loadTime = loadTime;
}
public MyClassLoader getMyLoader() {
return myLoader;
}
public void setMyLoader(MyClassLoader myLoader) {
this.myLoader = myLoader;
}
public long getLoadTime() {
return loadTime;
}
public void setLoadTime(long loadTime) {
this.loadTime = loadTime;
}
public BaseManager getManager() {
return manager;
}
public void setManager(BaseManager manager) {
this.manager = manager;
}



}
package com.load;

public class Logic {
public void logic(){
System.out.println("logic");
}

}
package com.load;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import com.load.custom.MyClassLoader;
import com.load.manager.BaseManager;

public class ManagerFactory {

/**
*记录热加载类的加载信息
*/

private static final Map<String,LoadInfo> loadTimeMap = new HashMap<>();

public static final String CLASS_PATH = "D:/";

public static final String MY_MANAGER = "com.load.manager.MyManager";


public static BaseManager getManager(String className){
File loadFile = new File(CLASS_PATH+className.replaceAll("\\.","/")+".class");
long lastModified = loadFile.lastModified();

//查看是否被家再过 ,如果没有被家再过则加载
if(loadTimeMap.get(className) == null){
load(className,lastModified);
}else if(loadTimeMap.get(className).getLoadTime() != lastModified){//如果被加载过,查看加载时间,如果该类已经被修改,则重新加载
load(className,lastModified);
}

return loadTimeMap.get(className).getManager();
}


private static void load(String className,long lastModified){

MyClassLoader myLoader = new MyClassLoader(CLASS_PATH);
Class<?> loadClass = null;
try {
loadClass = myLoader.loadClass(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
BaseManager manager = newInstance(loadClass);
LoadInfo loadInfo2 = new LoadInfo(myLoader,lastModified);
loadInfo2.setManager(manager);
loadTimeMap.put(className, loadInfo2);
}


private static BaseManager newInstance(Class<?> cls){
try {
return (BaseManager)cls.getConstructor(new Class[]{}).newInstance(new Object[]{});
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
return null;
}
}
package com.load;

import com.load.manager.BaseManager;

public class MsgHandler implements Runnable{



@Override
public void run() {

while(true){
BaseManager manager = ManagerFactory.getManager(ManagerFactory.MY_MANAGER);
manager.logic();

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) {
new Thread(new MsgHandler()).start();

// File loadFile = new File("D:/"+ManagerFactory.MY_MANAGER.replaceAll(".","/")+".class");
// long lastModified = loadFile.lastModified();
// System.out.println("com.load.manager.MyManager".replaceAll("\\.","/"));
}

}

运行后,修改MyManager类后输出如下:

加载类===com.load.manager.MyManager
bbb
logic
bbb
logic
bbb
logic
bbb
logic
bbb
logic
加载类===com.load.manager.MyManager
aaaaaaaaaaaaaaaaaaaa
logic
aaaaaaaaaaaaaaaaaaaa
logic
aaaaaaaaaaaaaaaaaaaa
logic
aaaaaaaaaaaaaaaaaaaa
logic

可见,修改后重新加载了MyManager。到此热加载成功。

还有另一种方案可以进行类的热加载,
http://blog.csdn.net/maosijunzi/article/details/45918477