浅尝Java动态加载Jar包(一)
theme: qklhk-chocolate
我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!
前因
近期项目开发已经进入后半段了,目前还有一个积分模块还没实现,具体的积分消费规则没确定下来,导致该功能模块的开发一直搁置着,眼看着时间越来越近了,这么拖着也不是个事,还是得抓紧把他解决掉才行。
基于上述的原因,哗哗哗的就先草草的画了一张流程图出来。
大概的支付流程是这样子的,其中计费模块需要通过 折扣模块 和 积分模块 这两个前置条件处理完之后才进行费用计算。由于积分规则暂时只确定了一两种,后续还会新增多种规则,上面领导要求该模块需要实现动态拓展规则功能,不能每次更新都得停机操作。因此该模块的设计上采用了模板方法模式 来进行实现,通过传入不同的规则名称来获取到对应的实现类进行功能实现。
大概的思路确定了,接下来就是逐渐细化并实现了。
功能实现
基础规则模块 base
对于规则的实现,这里定义了一个基础模块,该模块里面定义了一个接口,限定了规则需实现的方法,同时也便于后续的逻辑调用,不会因为开发人员各自的命名问题导致需要添加多余的判断。
```java public interface BaseService {
/**
* 运行逻辑
*
* @param param 参数
* @return 结果
* @author unidentifiable
* @date 2022/2/17 10:50
*/
String run(String param);
} ```
具体功能实现模块 service
功能实现模块,用来实现具体的处理逻辑,在该模块中,我们需要引入上面提到的基础规则模块,实现接口规定的方法。
每一个模块只负责一个规则逻辑的实现,逻辑实现后将其打成Jar
包即可。
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包名称保持和类名一致。
使用案例 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", "未知"));
}
测试了一下,Jar
包里面的方法可以正常调用,到这里,一个基础版的动态加载功能已经实现了,先丢上去和领导交差了,又可以愉快的摸鱼去了!
PS: 网上有一些文章提到了使用完
classLoader
之后需要将其关闭,经过测试,如果关闭了该加载器会导致找不到载入类的异常。