应用场景
- 对于支付:经常有些需求,需要我们扩展新的支付方式,但又不希望重启服务
- 对于IOT:经常需要新增一些解析数据的Handler
那我们如何能做到不重启服务,就能扩展新功能呢?本文给一个简单的demo示例,以支付扩展为例
代码演示
支付的统一接口
package org.example;
public interface Pay {
public void pay();
}
现有功能已有的微信支付实现:
package org.example;
public class WeiXinPay implements Pay {
@Override
public void pay() {
System.out.println("微信支付成功");
}
}
其他演示代码:
main方法,启动一个线程执行
package org.example;
public class Main {
public static void main(String[] args) {
new Thread(new MsgHandle()).start();
}
}
MsgHandle实现:
package org.example;
public class MsgHandle implements Runnable {
@Override
public void run() {
while (true) {
Pay weixin = PayFactory.getPay("org.example.WeiXinPay");
if (weixin == null) {
System.out.println("微信 支付失败");
} else {
weixin.pay();
}
Pay alipay = PayFactory.getPay("org.example.AliPay");
if (alipay == null) {
System.out.println("支付宝 支付失败");
} else {
alipay.pay();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
重点看PayFactory获取对应支付方式的getPay
package org.example;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class PayFactory {
/** 记录热加载类的加载信息 */
private static final Map<String, LoadInfo> loadTimeMap = new HashMap<>();
/** 要加载的类的 classpath */
public static final String CLASS_PATH = "/Users/zhengchao/code/HotLoad/target/classes/org/example";
/** 实现热加载的类的全名称(包名+类名 ) */
public static Pay getPay(String className) {
File loadFile = new File(CLASS_PATH + className.replaceAll("\\.", "/") + ".class");
// 获取最后一次修改时间
long lastModified = loadFile.lastModified();
//System.out.println("当前的类时间:" + lastModified);
// loadTimeMap 不包含 ClassName 为 key 的信息,证明这个类没有被加载,要加载到 JVM
if (loadTimeMap.get(className) == null) {
load(className, lastModified);
} // 加载类的时间戳变化了,我们同样要重新加载这个类到 JVM。
else if (loadTimeMap.get(className).getLoadTime() != lastModified) {
load(className, lastModified);
}
LoadInfo loadInfo = loadTimeMap.get(className);
if(loadInfo == null){
return null;
}
return loadInfo.getManager();
}
/**
* 加载 class ,缓存到 loadTimeMap
*
* @param className
* @param lastModified
*/
private static void load(String className, long lastModified) {
MyClasslLoader myClasslLoader = new MyClasslLoader(className);
Class loadClass = null;
// 加载
try {
loadClass = myClasslLoader.loadClass(className);
} catch (Exception e) {
System.out.println("暂不支持 " + className + " 支付方式");
return;
}
Pay manager = newInstance(loadClass);
LoadInfo loadInfo = new LoadInfo(myClasslLoader, lastModified);
loadInfo.setManager(manager);
loadTimeMap.put(className, loadInfo);
}
/**
* 以反射的方式创建 BaseManager 的子类对象
*
* @param loadClass
* @return
*/
private static Pay newInstance(Class loadClass) {
try {
return (Pay)loadClass.getConstructor(new Class[] {}).newInstance(new Object[] {});
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
}
这个类是关键,实现不重启动态加载的核心
MyClasslLoader是自定义的类加载,用于根据类的全路径加载已有的类、或新增的类
package org.example;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
public class MyClasslLoader extends ClassLoader {
/** 要加载的 Java 类的 classpath 路径 */
private String classpath;
public MyClasslLoader(String classpath) {
// 指定父加载器
super(ClassLoader.getSystemClassLoader());
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = this.loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
/**
* 加载 class 文件中的内容
*
* @param name
* @return
*/
private byte[] loadClassData(String name) {
try {
// 传进来是带包名的
name = name.replace(".", "//");
FileInputStream inputStream = new FileInputStream(new File(classpath + name + ".class"));
// 定义字节数组输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while ((b = inputStream.read()) != -1) {
baos.write(b);
}
inputStream.close();
return baos.toByteArray();
} catch (Exception e) {
System.out.println("暂不支持 " + name + " 支付方式");
}
return null;
}
}
LoadInfo实现:
package org.example;
public class LoadInfo {
/** 自定义的类加载器 */
private MyClasslLoader myClasslLoader;
/** 记录要加载的类的时间戳-->加载的时间 */
private long loadTime;
/** 需要被热加载的类 */
private Pay manager;
public LoadInfo(MyClasslLoader myClasslLoader, long loadTime) {
this.myClasslLoader = myClasslLoader;
this.loadTime = loadTime;
}
public MyClasslLoader getMyClasslLoader() {
return myClasslLoader;
}
public void setMyClasslLoader(MyClasslLoader myClasslLoader) {
this.myClasslLoader = myClasslLoader;
}
public long getLoadTime() {
return loadTime;
}
public void setLoadTime(long loadTime) {
this.loadTime = loadTime;
}
public Pay getManager() {
return manager;
}
public void setManager(Pay manager) {
this.manager = manager;
}
}
运行即可看到微信支付已经成功,但支付宝支付没有成功,如果想要不重启服务扩展支付宝支付,本地新增一个支付宝支付实现:
package org.example;
public class AliPay implements Pay {
@Override
public void pay() {
System.out.println("支付宝支付成功");
}
}
将AliPay编译成AliPay.class文件,放入到CLASS_PATH目录下
String CLASS_PATH = "/Users/zhengchao/code/HotLoad/target/classes/org/example";
上面一直执行的线程会加载支付宝支付,输出支付宝支付成功
完结~