Java不重启服务 扩展新支付功能demo

应用场景

  • 对于支付:经常有些需求,需要我们扩展新的支付方式,但又不希望重启服务
  • 对于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 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";

Java不重启服务 扩展新支付功能demo_第1张图片

上面一直执行的线程会加载支付宝支付,输出支付宝支付成功

完结~

你可能感兴趣的:(java,开发语言)