浅尝Java动态加载Jar包(一)

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

前因

近期项目开发已经进入后半段了,目前还有一个积分模块还没实现,具体的积分消费规则没确定下来,导致该功能模块的开发一直搁置着,眼看着时间越来越近了,这么拖着也不是个事,还是得抓紧把他解决掉才行。

基于上述的原因,哗哗哗的就先草草的画了一张流程图出来。

image.png

大概的支付流程是这样子的,其中计费模块需要通过 折扣模块积分模块 这两个前置条件处理完之后才进行费用计算。由于积分规则暂时只确定了一两种,后续还会新增多种规则,上面领导要求该模块需要实现动态拓展规则功能,不能每次更新都得停机操作。因此该模块的设计上采用了模板方法模式 来进行实现,通过传入不同的规则名称来获取到对应的实现类进行功能实现。

image.png

大概的思路确定了,接下来就是逐渐细化并实现了。

功能实现

基础规则模块 base

对于规则的实现,这里定义了一个基础模块,该模块里面定义了一个接口,限定了规则需实现的方法,同时也便于后续的逻辑调用,不会因为开发人员各自的命名问题导致需要添加多余的判断。

image.png

public interface BaseService {

    /**
     * 运行逻辑
     *
     * @param param 参数
     * @return 结果
     * @author unidentifiable
     * @date 2022/2/17 10:50
     */
    String run(String param);
}

具体功能实现模块 service

功能实现模块,用来实现具体的处理逻辑,在该模块中,我们需要引入上面提到的基础规则模块,实现接口规定的方法。

每一个模块只负责一个规则逻辑的实现,逻辑实现后将其打成Jar包即可。

image.png

<dependencies>
    <!-- 引入基础规则 -->
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>base</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

下面两个类用来模拟不同的扣减规则。

public class MyService1 implements BaseService {

    @Override
    public String run(String param) {
        return "运行扣减规则1:,参数 {" + param + "}";
    }
}
public class MyService2 implements BaseService {

    @Override
    public String run(String param) {
        return "运行扣减规则2:,参数 {" + param + "}";
    }
}

Jar包名称保持和类名一致。 image.png

使用案例 demo

规则逻辑实现的Jar包已经打好了,接下来就是看我们要怎么来使用它了,这里我们需要实现的功能有两个:

  1. 加载Jar
  2. 调用实现类的方法

同时这里也需要引用基础规则模块,不然后面加载对应逻辑Jar包的时候会出现异常,找不到对应接口。

<dependencies>
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>base</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

加载Jar包

加载的方式有好几种,我这里只写了其中一种,有不足之处,还望大家在评论区指出,共同学习功通进步! _{加载的方式有好几种,我这里只写了其中一种,有不足之处,还望大家在评论区指出,共同学习功通进步!}

/**
 * 加载Jar包
 *
 * @param jarPath jar包目录
 * @author unidentifiable
 * @date 2022/2/17 15:33
 */
public static void loadJar(String jarPath) throws Exception {
    // 指定Jar包存放的路径
    File files = new File(jarPath);
    // 找到所有以 .jar 结尾的文件
    File[] fileArray = files.listFiles(f -> f.getName().endsWith(".jar"));

    // 获得类加载器相关方法对象(URLClassLoader:支持从jar包或者文件夹等路径链接中获取class)
    Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
    URLClassLoader classLoader = null;
    try {
        // 获取方法的访问权限
        method.setAccessible(true);
        classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        for (File file : fileArray) {
            // 将当前类路径加到类加载器中
            method.invoke(classLoader, file.toURI().toURL());
        }
    } finally {
        method.setAccessible(false);
    }
}

调用方法

/**
 * 运行Jar包内部逻辑
 *
 * @param name Jar包名称
 * @param param 参数
 * @author unidentifiable
 * @date 2022/2/17 15:33
 */
public static String run(String name, String param) throws Exception {
    // 类的全路径
    String packagePath = "org.example.unidentifiable.service.impl.";
    Class<?> aClass = null;
    try {
        // 得到实现类
        aClass = Class.forName(packagePath + name);
    } catch (ClassNotFoundException e) {
        return "未找到{" + name + "}规则,请核对后重新提交!";
    }

    Object run = null;
    try {
        // 调用规则方法
        run = aClass.getDeclaredMethod("run", String.class).invoke(aClass.newInstance(), param);
    } catch (NoSuchMethodException e) {
        return "规则处理异常,请检查相关资源包:{" + name + "}";
    }
    // 返回结果
    return run.toString();
}

测试

public static void main(String[] args) throws Exception {
    loadJar("F:\");
    System.out.println(run("MyService1", "消费积分"));
    System.out.println("------------------------------------");
    System.out.println(run("MyService2", "赠送积分"));
    System.out.println("------------------------------------");
    System.out.println(run("MyService3", "未知"));
}

image.png

测试了一下,Jar包里面的方法可以正常调用,到这里,一个基础版的动态加载功能已经实现了,先丢上去和领导交差了,又可以愉快的摸鱼去了!

PS: 网上有一些文章提到了使用完 classLoader 之后需要将其关闭,经过测试,如果关闭了该加载器会导致找不到载入类的异常。

image.png

猜你喜欢

转载自juejin.im/post/7065885520521330725