一、思路
利用自己的类加载器去重新加载class文件
二、说明
不多说废话,直接上代码,有不懂的地方可以留言也可以私信我
三、代码
(1)被监听(需要热部署)的类
public interface Message {
void send();
}
public class MessageImpl implements Message {
@Override
public void send() {
System.out.println("发送消息AAAA");
}
}
(2)自定义类加载器
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
//类加载器名称
private String name;
//类加载路径
private String path;
public MyClassLoader(ClassLoader parent, String name, String path) {
super(parent); //父类加载器
this.name = name;
this.path = path;
}
/**
* 重写父类的loadClass方法
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> clazz = null;
//判断是不是本类
if(this.name.equals(name)) {
//查找是否已经被加载过了
clazz = this.findLoadedClass(name);
if(clazz == null) {
//如果没找到则继续去查找
clazz = this.findClass(name);
}
}
return super.loadClass(name);
}
/**
* 重写findClass方法,自定义规则
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//转成二进制字节流,因为JVM只认识二进制不认识字符串
byte[] b = readFileToByteArray(name);
return this.defineClass(this.name, b, 0, b.length);
}
/**
* 将包名转换成全路径名,比如
*
* temp.a.com.dn.Demo -> D:/temp/a/com/dn/Demo.class
*
* @param name
* @return
*/
private byte[] readFileToByteArray(String name) {
InputStream is = null;
byte[] rtnData = null;
//转换
name = name.replaceAll("\\.", "/");
//拼接
String filePath = this.path + name + ".class";
File file = new File(filePath);
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
is = new FileInputStream(file);
int tmp = 0;
while((tmp = is.read()) != -1) {
os.write(tmp);
}
rtnData = os.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(null != os) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return rtnData;
}
public Class<?> loadClass() throws ClassNotFoundException {
return loadClass(this.name);
}
}
(3)main方法
public class TestClassLoader {
//需要热部署的那个类的全路径名称,不一定必须在项目中,可以在任意位置
private static final String name = "jvm.dongnao.demo2.MessageImpl";
//需要热部署的类的classes目录
private static final String path = "C:\\Users\\Chen\\workspace\\effect-java\\target\\classes\\";
public static void main(String[] args) throws Exception {
Message message = null;
while(true) {
//1、实例化自己的类加载器,并将当前线程的类加载器作为父类加载器,并将name和path传进去
MyClassLoader loader = new MyClassLoader(Thread.currentThread().getContextClassLoader(), name, path);
//2、调用本来的loadClass方法
Class<?> clazz = loader.loadClass();
//实例化上面path+name+".class"
message = (Message)clazz.newInstance();
//调用方法
message.send();
//每隔3s执行一次,检查class是否有变化
Thread.sleep(3000);
}
}
}
三、结果
直接运行main方法,然后将MessageImpl的send方法输出改成BBB查看效果
发送消息AAAA
发送消息AAAA
发送消息BBBB
发送消息BBBB
四、强调
本章内容好多代码都可以优化,比如转成byte可以用nio,关闭流可以用Apache工具集优雅的关闭等等。
不难发现我写了个死循环,并不管类有没有变化都去重新加载它,这样做法是不好的,可以用Apache vfs去监听他。
我写这篇文章的目的是为了说明原理,而不是当工具来用,写的太复杂大家理解起来也不容易。若有任何疑问都可以留言也可以私信我。
若有兴趣,欢迎来加入群,【Java初学者学习交流群】:458430385,此群有Java开发人员、UI设计人员和前端工程师。有问必答,共同探讨学习,一起进步!
欢迎关注我的微信公众号【Java码农社区】,会定时推送各种干货:
qrcode_for_gh_577b64e73701_258.jpg
网友评论