Java 动态加载 Jar 包的味道 (a)
我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!
前因
近期项目开发已经进入后半段了,目前还有一个积分模块还没实现,具体的积分消费规则没确定下来,导致该功能模块的开发一直搁置着,眼看着时间越来越近了,这么拖着也不是个事,还是得抓紧把他解决掉才行。
基于上述的原因,哗哗哗的就先草草的画了一张流程图出来。
大概的支付流程是这样子的,其中计费模块需要通过 折扣模块 和 积分模块 这两个前置条件处理完之后才进行费用计算。由于积分规则暂时只确定了一两种,后续还会新增多种规则,上面领导要求该模块需要实现动态拓展规则功能,不能每次更新都得停机操作。因此该模块的设计上采用了模板方法模式 来进行实现,通过传入不同的规则名称来获取到对应的实现类进行功能实现。
大概的思路确定了,接下来就是逐渐细化并实现了。
功能实现
基础规则模块 base
对于规则的实现,这里定义了一个基础模块,该模块里面定义了一个接口,限定了规则需实现的方法,同时也便于后续的逻辑调用,不会因为开发人员各自的命名问题导致需要添加多余的判断。
public interface BaseService {
/**
* 运行逻辑
*
* @param param 参数
* @return 结果
* @author unidentifiable
* @date 2022/2/17 10:50
*/
String run(String param);
}
具体功能实现模块 service
功能实现模块,用来实现具体的处理逻辑,在该模块中,我们需要引入上面提到的基础规则模块,实现接口规定的方法。
每一个模块只负责一个规则逻辑的实现,逻辑实现后将其打成Jar
包即可。
<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包名称保持和类名一致。
使用案例 demo
规则逻辑实现的Jar
包已经打好了,接下来就是看我们要怎么来使用它了,这里我们需要实现的功能有两个:
- 加载
Jar
包 - 调用实现类的方法
同时这里也需要引用基础规则模块,不然后面加载对应逻辑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", "未知"));
}
测试了一下,Jar
包里面的方法可以正常调用,到这里,一个基础版的动态加载功能已经实现了,先丢上去和领导交差了,又可以愉快的摸鱼去了!
PS: 网上有一些文章提到了使用完
classLoader
之后需要将其关闭,经过测试,如果关闭了该加载器会导致找不到载入类的异常。
推荐阅读
-
基于 Java 的打包 jar、war、ear 包的作用和区别详解
-
Java 类加载器的作用 - 简介:类加载器是 Java™ 中一个非常重要的概念。类加载器负责将 Java 类的字节码加载到 Java 虚拟机中。本文首先详细介绍了 Java 类加载器的基本概念,包括代理模型、加载类的具体过程和线程上下文类加载器等。然后介绍了如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi™ 中的应用。 类加载器是 Java 语言的一项创新,也是 Java 语言广受欢迎的重要原因之一。它允许将 Java 类动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 开始出现,最初是为了满足 Java Applets 的需求而开发的,Java Applets 需要从远程位置下载 Java 类文件并在浏览器中执行。现在,类加载器已广泛应用于网络容器和 OSGi。一般来说,Java 应用程序的开发人员不需要直接与类加载器交互;Java 虚拟机的默认行为足以应对大多数情况。但是,如果遇到需要与类加载器交互的情况,而您又不太了解类加载器的机制,就很容易花费大量时间调试异常,如 ClassNotFoundException 和 NoClassDefFoundError。本文将详细介绍 Java 的类加载器,帮助读者深入理解 Java 语言中的这一重要概念。下面先介绍一些基本概念。 类加载器的基本概念 顾名思义,类加载器用于将 Java 类加载到 Java 虚拟机中。一般来说,Java 虚拟机以如下方式使用 Java 类:Java 源程序(.java 文件)经 Java 编译器编译后转换为 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码并将其转换为 java.lang 实例。每个实例都用来表示一个 Java 类。通过该实例的 newInstance 方法创建该类的对象。实际情况可能更加复杂,例如,Java 字节代码可能是由工具动态生成或通过网络下载的。 基本上,所有类加载器都是 java.lang.ClassLoader 类的实例。下面将详细介绍这个 Java 类。 java.lang.ClassLoader 类简介 java.lang.ClassLoader 类的基本职责是根据给定类的名称为其查找或生成相应的字节码,然后根据这些字节码定义一个 Java 类,即 java.lang.Class 类的实例。除此之外,ClassLoader 还负责加载 Java 应用程序所需的资源,如图像文件和配置文件。不过,本文只讨论它加载类的功能。为了履行加载类的职责,ClassLoader 提供了许多方法,其中比较重要的方法如表 1 所示。下文将详细介绍这些方法。 表 1.与加载类相关的 ClassLoader 方法
-
Java 插件开发 动态加载 Jar
-
记录 - JAVA 动态加载外部 JAR 并调用方法和卸载以关闭打开的外部 JAR
-
类加载器动态替换 java 类_类加载器和类的热替换(Hotswap)
-
JAVA 如何通过类加载器加载类和 jar 包
-
java(类的常用方法 获取类对象的六种方法 动态加载和静态加载 类的加载过程)
-
大数据平台底层技术 - JAVA - 如何动态加载不同版本的 HIVE JDBC 驱动程序 - 一文读懂 JAVA 类加载机制 2
-
Java 动态加载 Jar 包的味道 (a)
-
java-jar 带有环境变量(参数)的 jar 包启动