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

news2024/12/25 1:18:20

应用场景

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

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

完结~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/950110.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无涯教程-Android - AutoCompleteTextView函数

AutoCompleteTextView是一个类似于EditText的视图&#xff0c;只是它在用户键入时自动显示补充数据。 AutoCompleteTextView - 属性 以下是与AutoCompleteTextView控件相关的重要属性。您可以查看Android官方文档以获取属性的完整列表以及可以在运行时更改这些属性的相关方法。…

循环冗余校验码(CRC校验)

CRC在线计算 CRC即循环冗余校验码&#xff08;Cyclic Redundancy Check&#xff09;&#xff1a;是数据通信领域中最常用的一种查错校验码&#xff0c;其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查&#xff08;CRC&#xff09;是一种数据传输检错功能&#xf…

会员管理系统实战开发教程06-会员充值

我们上篇讲解了会员开卡的操作&#xff0c;有了会员卡之后日常就是给会员进行充值&#xff0c;充值的逻辑是对余额进行累加&#xff0c;而且要记录充值的情况。 1 创建充值记录表 打开控制台&#xff0c;点击号创建数据源 输入数据源名称充值记录 点击编辑添加字段 先添加…

three.js(八):内置的三维几何体

三维几何体 BoxGeometry 立方体TetrahedronGeometry 四面体OctahedronGeometry 八面体DodecahedronGeometry 十二面体IcosahedronGeometry 二十面体PolyhedronGeometry 多面体SphereGeometry 球体ConeGeometry 圆锥CylinderGeometry 圆柱TorusGeometry 三维圆环TorusKnotGeomet…

部署你自己的导航站-dashy

现在每天要访问的网页都太多了&#xff0c;尽管chrome非常好用&#xff0c;有强大的标签系统。但是总觉的少了点什么。 今天我就来分享一个开源的导航网站系统 dashy。这是一个国外的大佬的开源项目 github地址如下&#xff1a;https://github.com/Lissy93/dashy 来简单说一下…

LabVIEW开发同步磁阻电机匝间短路故障在线诊断技术

LabVIEW开发同步磁阻电机匝间短路故障在线诊断技术 近年来&#xff0c;电动驾驶系统取得了实质性的改进&#xff0c;不仅在工业自动化和机器人技术中实施&#xff0c;而且在运输和风力发电中实施。自动化系统和其他执行器的典型要求包括&#xff1a;小尺寸、低重量、低成本、高…

【ES6】JavaScript 中的数组方法reduce

reduce() 是一个 JavaScript 中的数组方法&#xff0c;它会对数组的每个元素执行一个提供的 reducer 函数&#xff0c;将其减少到一个单一的值。 这是 reduce() 的基本用法&#xff1a; //(method) Array<number>.reduce(callbackfn: (previousValue: number, currentV…

如何在Windows / Mac / iPhone / Android / Online上将MP4转换为MP3

如果只想保留MP4视频的音频轨道&#xff0c;则可以将MP4转换为MP3格式。 MP3是几乎所有设备&#xff0c;播放器和编辑器都支持的数字音频格式。无论您将MP4视频转换为MP3音频以进行脱机播放或进一步编辑&#xff0c;都可以提取音轨并保存为MP3格式。这是在不损失质量的情况下将…

浅析Linux虚拟网络技术

文章目录 概述Tap/tun设备tun/tap的工作机制 Bridge网桥Bridge的工作机制Bridge IP 相关参考 概述 在传统的网络环境中&#xff0c;一台物理主机包含一张或多张网卡&#xff0c;要实现与其它物理主机之间的通信&#xff0c;需要将自身的网卡通过路由器或者交换机连接到外部的物…

解决gitee仓库中 .git 文件夹过大的问题

最近&#xff0c;许多项目都迁移到gitee。使用的也越来越频繁&#xff0c;但是今天突然收到一个仓库爆满的提示。让我一脸懵逼。本文将详细为你解答&#xff0c;这种情况如何处理。 1、起因 我收到的报错如下&#xff1a; remote: Powered by GITEE.COM [GNK-6.4] remote: T…

Docker - Docker安装MySql并启动

因为项目需要连接数据库&#xff0c;但是远程服务器上的mysql我不知道账户和密码&#xff0c;这个时候便是docker发挥作用的关键时刻了&#xff01; 目录 docker安装安装gcc卸载老docker&#xff08;如有&#xff09;安装软件包设置镜像仓库更新yum软件包索引安装docker启动doc…

园区能源管理系统是什么

园区能耗监测系统是可以用电力电气技术性、物联网、电子信息技术、通讯技术相当于一身&#xff0c;可以对当场里的能耗数据信息完成数据收集、存储、剖析、大屏幕呈现、分享等多种功能&#xff0c;可以为政府部门进行环保节能方式及其能源数据统计分析、预警信息、预测等&#…

04、添加 com.fasterxml.jackson.dataformat -- jackson-dataformat-xml 依赖报错

Correct the classpath of your application so that it contains a single, compatible version of com.fasterxml.jackson.dataformat.xml.XmlMapper 解决&#xff1a; 改用其他版本&#xff0c;我没写版本号&#xff0c;springboot自己默认的是 2.11.4 版本 成功启动项目…

黑马点评环境搭建导入

一开始配置maven的时候&#xff0c;发现怎么都无法查看maven的版本&#xff0c;后来才知道是JAVA_HOME的问题&#xff0c;开头多了一个空格&#xff08;因为我是直接复制过去的&#xff09;&#xff0c;然后搜网上通过命令行可以看到肉眼看不到的bug。 通过命令行的方式改正确后…

器件手册识读之 :运放

器件手册识读之 &#xff1a;运放 一、基本信息 二、引脚排列 三、最大额定参数 四、电气特性 五、应用电路 1、称重传感器放大器 2、热电偶低偏置&#xff0c;低漂移环路测量二极管冷端补偿。

网络编程 day 4

1、多进程并发服务器根据流程图重新编写 #include <myhead.h>#define ERR_MSG(msg) do{\fprintf(stderr, "__%d__:", __LINE__); \perror(msg);\ }while(0)#define PORT 8888 //端口号&#xff0c;范围1024~49151 #define IP "192.168.11…

【SpringSecurity】七、SpringSecurity集成thymeleaf

文章目录 1、thymeleaf2、依赖部分3、定义Controller4、创建静态页面5、WebSecurityConfigurerAdapter6、权限相关7、当用户没有某权限时&#xff0c;页面不展示该按钮 1、thymeleaf 查了下读音&#xff0c;leaf/li:f/&#xff0c;叶子&#xff0c;前面的单词发音和时间time一…

C++中的继承和派生

C 中的继承是类与类之间的关系&#xff0c;是一个很简单很直观的概念&#xff0c;与现实世界中的继承类似&#xff0c;例如儿子继承父亲的财产。 继承&#xff08;Inheritance&#xff09;可以理解为一个类从另一个类获取成员变量和成员函数的过程。例如类 B 继承于类 A&#x…

【SQL】关系模型与查询和更新数据

一、关系模型 1.1 主键 主键是关系表中记录的唯一标识。主键的选取非常重要&#xff1a;主键不要带有业务含义&#xff0c;而应该使用BIGINT自增或者GUID类型。主键也不应该允许NULL。 可以使用多个列作为联合主键&#xff0c;但联合主键并不常用。 1.2 外键 FOREIGN KEY …