我在 java 基础:原始数据类型,包的创建与导入 - CSDN 博客一文中记录了包的使用,此文就详细讲解一下 IDEA 中如何进行组件化开发。
介绍
–
现在的软件系统功能越来越复杂,规模也越来越大,为了应对这种挑战,人们将 “老系统” 中可以重用的代码抽取出来单独构建为 “组件(Component)”,开发新系统时,直接使用这些现成的组件而不是一切都重新再来,是 “组件化开发(CBD:Component Based Development)” 的基本思想。
Java 应用程序开发时,最普遍使用的组件就是 jar 包,JDK 的各个组件,基本上都是以 jar 包的形式提供给 Java 开发者的。
什么是 Jar 包?
▪ Jar 文件其实是一个压缩包,遵循于 Zip 数据压缩标准,可以使用 WinRAR 等解压软件打开。
▪ Jar 文件中包容一个清单(manifest)文件,包容一些重要信息。比如可以在 windows 资源管理器中 “双击” 执行的 jar 包,其清单文件必须指明包中哪个类是“主类(main class)”,从而让 JVM 知道应该从哪个类中的 main 方法开始执行。里面包容了 程序运行所需的.class文件和相关资源,通常,还附加有相应的元数 据,放到META-INF文件夹中。
JAR包与Java应用程序
JAR包有名称,彼此之间存在着依赖关系,如果需要的话,每个 JAR包中的公有类型,都能够被其他JAR包中的代码所访问
启动应用程序时,可以在类路径上列出所有要使用的JAR:
java -classpath 路径列表或jar包列表 -jar 要运行的JAR包名.jar
Java应用程序必须能找到它所依赖的所有JAR包才能运行。 运行时,JVM会到JAR包中去加载要用到的类。
如果在类路径或者JAR包中没有找到所需的类,JVM会抛出 一个运行时异常。
但我们平时可以用java –jar MyJavaApp.jar
来直接运行jar包,而不需要指定-classpath参数的场景,是因为不需要指定类路径的JAR包,是一种 “fat jar”,它将运行所需的资源全 部打包到了单独的一个JAR包中。
默认情况下,使用IntelliJ IDEA生成 JAR包,会自动选中“extract …” 这个选项,将用到的外部JAR包中 的.class文件,抽出来打包到自己的 JAR包中,这样一来,在运行时,就不再需要指定它所依赖的外部JAR了。
组件化开发过程
下面我们通过一个例子一步步地展示以重用为目的的 Java 代码演化过程:
原始程序
比如说我在一个程序内写了一个倒序输出字符串的功能
public class ReverseString1 {
// 示例程序功能:将一个字符串倒序输出
public static void main(String[] args) {
String str = "abcd";
StringBuffer buff = new StringBuffer();
for (int i = str.length() - 1; i >= 0; i--)
buff.append(str.charAt(i));
System.out.println(buff.toString());
}
}
示例代码中,将所有代码都放到了 main() 方法中,并且混杂了数据处理代码(实现字符串倒序)和输出代码(println)。这导致代码无法重用。
重构:方法抽取
此时我们可以把倒序输出字符串的方法抽取出来,写成这样
public class ReverseString2 {
public static void main(String[] args) {
String str = "abcd";
System.out.println(reverseString(str));
}
// 方法功能:将一个字符串倒序输出
private static String reverseString(String inputStr) {
StringBuffer buff = new StringBuffer();
for (int i = inputStr.length() - 1; i >= 0; i--)
buff.append(inputStr.charAt(i));
return buff.toString();
}
}
但是在同一个项目中,倒序输出的功能如果在多个地方都用到,就要把 reverseString() 方法的源代码在每个类中都 Copy & Paste 一遍。
重构:类的引入
//导入另一个模块包中的类
//Main.java文件
import cn.utils.MyStringUtil;
public class Main {
public static void main(String[] args) {
String str="abcd";
System.out.println(MyStringUtil.reverseString(str));
}
}
//MyStringUtil.java文件,放在Main.java 同目录的 cn/utils目录下
package cn.utils;
/**
* 此类封装一些字符串操作的功能
*/
public class MyStringUtil {
// 方法功能:将一个字符串倒序输出
public static String reverseString(String inputStr) {
StringBuffer buff = new StringBuffer();
for (int i = inputStr.length() - 1; i >= 0; i--)
buff.append(inputStr.charAt(i));
return buff.toString();
}
}
将需要重用的代码,放到独立的类中,同一项目中就能很容易地重用这一功能了。
只需要在项目要用到这个类时,import 这个类就能使用类中的方法。
不过问题又来了,如果我有一个新项目,如果需要调用许多不同的类
将需要跨项目重用的代码,封装为一个单独的 jar 包,然后在新项目中将其添加进来,这样一来,只要有一个 jar 包,就可以直接使用 jar 包中的代码。
tips:如果你看这个文件夹名用 (.) 连接不习惯的话,可以更改一下显示方式
跨模块
在你的项目里新建一个模块
我这边是改名为 MyJavaLibrary 就点创建了,你也能改成任意名字
创建完之后,你会发现项目中多了一个模块,它也有一个 src 文件夹,可以向其中添加相应的 Java 文件。而原来主目录下的那个模块被称为默认模块。
tips: 在 IntelliJ 中,每个 “项目(Project)” 都可以包容多个“模块(Module)”,模块是 IntelliJ 编译 Java 项目的基本单元。
你把模块里没用的那个 Main.java 删掉,并把 MyStringUtil.java 文件移到这个模块的 src 目录下。
然后你就会发现 package 后面有一个红色的报错,因为你没有给你的包放在相应的目录下,你可以手动创建 cn/utils 目录,也可以把鼠标移上去,自动创建目录.
tips: IntelliJ 提供了移动类到特定包中的 “代码重构” 功能,可以用它把 MyStringUtil 类移到指定的包中,IntelliJ 会自动创建相应的文件夹。
更改好目录后,MyStringUtil.java 文件 是不报错了,但是 Main.java 文件还没有成功导入
那是因为默认模块中的代码,如果要使用另一模块中的代码,需要在这两个模块之间建立依赖关系。
打开项目结构
打开模块 - 默认模块 - 依赖
正常情况下只有你使用的 JDK 的依赖,我这里是 JDK22
点击 “+” 号,选择模块依赖,选择你刚刚创建的模块并确定
默认模块创建好对 MyJavaLibrary 模块的依赖关系之后,就可以使用里面的 MyStringUtil 类了。
如果你的 idea 还是显示报错,可以重新打开项目看看。
使用给模块添加依赖关系这种手段,可以方便地组织代码,实现同一项目内 “源代码级别” 的“跨模块”的代码重用。
jar 包
更进一步,如果希望这些代码能 “跨项目” 重用,则可以将 MyJavaClassLibrary 模块导出为 jar 包,从而被另外的项目所重用。
在 IDEA 中生成 jar 包
还是在项目结构中,点击 工件 - JAR - 来自具有依赖项的模块
选择需要导出成 jar 包的模块,确定。
继续确定,此时就已经准备好要构建的 jar 包了
然后在构建这里选择构建工件
在这里选构建
你就能发现 jar 包已经生成了。
使用命令行生成 jar 包
我们现在已经有一个 java 文件在 “…… \src\cn\utils\MyStringUtil.java”
把命令行定位到 src 目录下,(建议先把 out 文件夹清空,这样有变化了可以看的更佳直观。或者把 \ cn\utils\MyStringUtil.java 整体移到一个空文件夹操作)
执行这行命令来编译 MyStringUtil.java ,-d out
表示编译后的 .class
文件会被放入 out
目录,保持包的目录结构。
javac -d out cn/utils/MyStringUtil.java
接下来,你需要将编译好的 .class
文件打包成 .jar
文件。仍然在 src
目录中运行以下命令
注意:out 后面要加上一个空格和一个 “.”
jar cf my-string.jar -C out .
解释:
jar cf
是创建.jar
文件的命令,其中c
表示创建,f
表示指定文件名。- my-string
.jar
是输出的.jar
文件名。 -C out .
表示切换到out
目录并将其中的文件打包进.jar
文件。
打包后,你应该在当前目录中看到生成的 my-string.jar 文件。
验证 .jar
文件
你可以验证 .jar
文件内容,使用以下命令列出它包含的内容:
jar tf my-string.jar
然后你能看到 jar 文件内部的结构
使用 jar 包
此时你可以创建一个新项目。在这个项目的主目录下创建一个叫 libs 的文件夹,把之前生成的 MyJavaLibrary.jar 文件放进这个目录。
就像这样
然后还是打开 项目结构 - 模块,添加 JAR 文件即可,和前面添加模块步骤一样,就不一一截屏了。
可执行的 jar 包
生成 jar 包时,可以指定主类(Main Class),在 Windows 资源管理器中,双击这个 jar 包,就能直接运行它。但这有个前提条件,那就是这个 jar 包必须包容所有所需的组件。
如果一个指定了主类(Main Class)的 jar 包,它依赖于另外的 jar 包,则必须将其合并起来,才能正确运行。
让 JAR 包可执行,关键在于指定主类。可以点击右侧的文件夹图标,在打开的对话窗口中选择主类。
所谓 “主类”,就是程序入口点(即 main 方法)所在的那个类。
从依赖的 jar 包中抽取用到的类型,加入到 jar 包中。这里是已经帮你自动提取完了,点击确定即可。
还是一样点击,构建工件。
完成之后在 artifacts 文件夹下可以找到生成的 jar 包,在 production 文件夹下可以看到生成的 jar 包的内部结构。
此时生成的 jar 包,不再依赖于其他的 jar 包
我们可以将这个 jar 包移动到任何地方,并使用 java 的 - jar 参数,可以直接运行一个 “可运行的”JAR 包。
java -jar jar_Test.jar
tips:如果电脑里装了多个版本的 java,要确保命令行默认的 java 版本和你编译时用的版本相同,(用 java -version 查看版本)否则可能会无法运行。如果不相同的话可以去系统环境变量里更改一下优先级,或者重新用另一个版本的 JDK 重新编译。