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

语言: CN / TW / HK

theme: qklhk-chocolate

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

前因

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

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

image.png

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

image.png

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

功能实现

基础规则模块 base

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

image.png ```java public interface BaseService {

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

} ```

具体功能实现模块 service

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

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

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

下面两个类用来模拟不同的扣减规则。 ```java public class MyService1 implements BaseService {

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

} java public class MyService2 implements BaseService {

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

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

使用案例 demo

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

同时这里也需要引用基础规则模块,不然后面加载对应逻辑Jar包的时候会出现异常,找不到对应接口。 xml <dependencies> <dependency> <groupId>org.example</groupId> <artifactId>base</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>

加载Jar包

$_{加载的方式有好几种,我这里只写了其中一种,有不足之处,还望大家在评论区指出,共同学习功通进步!}$ ```java /* * 加载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);
}

} ```

调用方法

```java /* * 运行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();

} ```

测试

java 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