angular-electron调用java
Angular-Electron中调用Java程序
文章目录
- Angular-Electron中调用Java程序
- 1. 集成Java环境
- 2. 准备 Java 文件 和 Jar 包
- 3. 编译 Java 文件
- 4. 执行 class 文件
- 5. 问题和答案
- 6. 完整代码展示
Angular-Electron
项目中牵涉到的一些配置,在其他项目比如Electron-Vue
项目中可能不太一样,但是思路一致,只需要找到自己的项目的配置方法即可。
1. 集成Java环境
集成环境比较简单,直接把Java
环境整个放到项目里即可。但是有两点需要注意下:
-
以
Java8
和Java17
为例,二者的文件构成不太一样:- 如果是
Java 8
,那么安装之后会有一个jre
目录,这是Java
的运行时环境,集成时只需要把整个jre
目录拷贝到项目中即可。 - 如果是
Java17
,因为Oracle
从Java 9
开始就简化了安装,选择不再单独安装JRE
。Java 17
只提供了JDK
,运行时环境(JRE
)被集成在JDK
中,因此只会看到一些可执行文件,而没有单独的JRE
目录。集成时我们也只需要把包含有这几个可执行文件(java.exe, javac.exe....
)的目录直接拷贝到项目中即可。
- 如果是
-
Java
环境文件位置因为后续需要执行
Java
命令,这要求命令执行时环境要处于Java
环境下,所以Java
环境文件的位置我们要在项目中能定位到。以
Angular-Electron
项目为例-
我习惯在项目根目录下创建一个和
src
平级的plugins
目录,把Java 17
环境目录放到了plugins
目录下,这样我在执行命令行需要定位目录时,只需要写:plugins/jre
即可,如果是Java 8
的话可能需要写plugins/jre/bin
。 -
在
electron-build.json
中配置一下额外资源打包:{"extraResources": [{"from": "plugins","to": "plugins","filter": ["**/*"]}], }
这样就可以在打包时把
plugins
时原封不动的打包进去,应用程序安装之后,就可以在resources
目录下看到。题外话:在生产环境下的目录拼接,只需要在
Electron
主进程下获取下process.cwd()
,然后渲染进程拿到这个项目运行绝对路径,然后再拼接上resouces/plugins/xxx
就能在生产环境下定位到 java 环境位置。
-
2. 准备 Java 文件 和 Jar 包
还是同样的,我把需要的 Java
文件以及 Jar 包也放到了 plugins
目录下,也是为了在生产环境下方便找到。
所以,目前我的 plugins
目录的构成是这样:
----- plugins------- jar
--------- pinyin4j
----------- pinyin4j-2.5.0.jar------- java
--------- PinyinConverter.java------- jre
--------- java.exe
--------- javac.exe
--------- ...
可以看到,我使用到了一个汉字转拼音的 jar
包 pinyin4j
,然后写了一个 java
类 PinyinConverter.java
测试。
PinyinConverter.java
import net.sourceforge.pinyin4j.PinyinHelper;public class PinyinConverter {public static void main(String[] args) {if (args.length == 0) {return;}String chineseInput = args[0];StringBuilder pinyinOutput = new StringBuilder();for (char c : chineseInput.toCharArray()) {String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c);if (pinyinArray != null && pinyinArray.length > 0) {pinyinOutput.append(pinyinArray[0]).append(" ");} else {pinyinOutput.append(c).append(" "); // 如果没有拼音,原字符输出}}System.out.println(pinyinOutput.toString().trim());}
}
文件准备不是重点,这里主要是为防止后面出现jar
包和java
路径时造成阅读困难。
接下来正片开始:
3. 编译 Java 文件
说一些编译前的准备:
-
我写了一个从主进程中获取程序运行绝对路径的方法
getProgramRunPath
,所以后面不要被IPC.getProgramRunPath()
方法影响。 -
因为目录有点多,所以我抽离了一个路径拼接方法
joinPluginsFolderPath()
joinPluginsFolderPath(p: string): string {return APP_CONFIG.production ? path.join('resources/plugins', p) : path.join('plugins', p); }
传进来一个路径,自动根据当前的环境来判断是否要拼接前置路径。
OK,开始编译。
编译 Java
文件主主要用到的命令是:
javac -cp <classpath> <source-files># <classpath> 是用分号(在 Windows 上)或冒号(在 Unix/Linux 上)分隔的目录或 JAR 文件路径。# <source-files> 是要编译的 Java 源文件。
那对应到 PinyinConverter.java
实际使用上就是这样:
// 注意:rp 是我通过`IPC.getProgramRunPath()`拿到的项目运行绝对路径,这个方法后续会出现。// 其实就相当于:const javaFilePath = 'D:/xxx/xx/xxx/plugins/java/PinyinConverter.java'; 当然了生产环境下前面plugins前面还有个resources
// java 类
const javaFilePath = path.join(rp, this.joinPluginsFolderPath('java/PinyinConverter.java'));// jar包
const pinyin4jJar = path.join(rp, this.joinPluginsFolderPath('jar/pinyin4j/pinyin4j-2.5.0.jar'));// 编译命令
const compileCommand = `javac -cp ".;${pinyin4jJar}" "${javaFilePath}"`;
接下来,执行编译命令,命令执行需要在java
环境下:
// 注意子进程 exec 方法的 cwd 配置,path.join(rp, this.joinPluginsFolderPath('jre') 相当于直接拼接了一个项目jre目录的绝对路径。这保证了命令的执行在java环境下。exec(compileCommand, { cwd: path.join(rp, this.joinPluginsFolderPath('jre')) }, (err, stdout, stderr) => {if(err) {console.error(`编译错误: ${stderr}`);reutrn;}
});
如果命令执行成功,那么你就会在 plugins/java
目录下看到一个和 PinyinConverter.java
同级的 PinyinConverter.class
文件。
接下来就进入第四步,执行class
文件:
4. 执行 class 文件
执行 class
文件需要用到 java -cp
命令,具体是这样:
java -cp <classpath> <main-class># <classpath> 是用分号(在 Windows 上)或冒号(在 Unix/Linux 上)分隔的目录或 JAR 文件路径。 # <main-class> 是包含 main 方法的主类。
const runCommand = `java -cp ".;${pinyin4jJar};D:\\xxxx\\plugins\\java" PinyinConverter "你在我的剧本里杀青了"`;// 解释下这个命令:
// 命令中`D:\\xxxx\\plugins\\java` 指定的是编译后的class文件所在目录的绝对路劲,PinyinConverter 是类名。
// "你在我的剧本里杀青了" 这是传递给 PinyinConverter类方法的参数,这个转换的中文我写死了,也可以弄个变量 "${this.yourChinese}". 只要前面的拼接正确就行。
然后执行这个命令:
exec(runCommand, { cwd: path.join(rp, this.joinPluginsFolderPath('jre')) }, (err, stdout, stderr) => {if (err) {console.error(`执行错误: ${stderr}`);return;}console.log(`输出结果: ${stdout}`);
});
如果没有出错,那么就能在控制台看到输出结果。
5. 问题和答案
Q1:必须要执行编译吗?
A1: 不需要,可以私下里先编译好
java
程序,直接把java
类放到目录下直接执行java
类也是一样的。
Q2:生产环境下目录问题编译不出来对应的
java
类?A2:这里提供一个解决问题的思路,具体问题具体分析,目录问题我指的是因为程序安装之后的目录没有权限读写导致
java
程序无法正常编译生成java
类。
可以私下里先编译好运行时直接执行
java
类,这样避免去创建java
类的操作。指定
java
类的生成地址,用-d
参数指定:const compileCommand = `javac -cp ".;${pinyin4jJar}" -d output "${javaFilePath}"`; // -d output 指定了生成的 .class 文件存放在 output 目录。
- 换个
java
文件存放目录,比如可以通过electron
的getPath
方法获取下用户的userData
目录。
此时就不要把.java
文件放到plugins
目录下了,我们需要构建一个java程序代码字符串
,然后获取到userData
目录后,通过node的write方法
创建一个.java
文件并把java程序代码字符串
写进去,相当于在userData
目录下手动构建了一个PinyinConverter.java文件
这样后续执行编译时也不会出现权限问题。
Q3:
Java
程序没用到jar
包?Q3:那只需要改下命令,以编译命令为例:(执行命令同理)
// 编译命令 用到jar包 const compileCommand = `javac -cp ".;${pinyin4jJar}" "${javaFilePath}"`;// 编译命令 没用到jar包 const compileCommand = `javac -cp "${javaFilePath}"`;
6. 完整代码展示
@Component({selector: 'app-tplpage',templateUrl: './tplpage.component.html',styleUrl: './tplpage.component.scss'
})
export class TplpageComponent {public chinese = '你在我的剧本里杀青了';public pinyinConverter() {// 获取程序运行绝对路径IPC.getProgramRunPath().subscribe(rp => {// java 类const javaFilePath = path.join(rp, this.joinPluginsFolderPath('java/PinyinConverter.java'));// jar包const pinyin4jJar = path.join(rp, this.joinPluginsFolderPath('jar/pinyin4j/pinyin4j-2.5.0.jar'));// 编译命令const compileCommand = `javac -cp ".;${pinyin4jJar}" "${javaFilePath}"`;exec(compileCommand, { cwd: path.join(rp, this.joinPluginsFolderPath('jre')) }, (err, stdout, stderr) => {if (err) {console.error(`编译错误: ${stderr}`);return;}console.log(`编译成功`);// 执行编译命令(ae_archadm是项目文件夹名称)const runCommand = `java -cp ".;${pinyin4jJar};D:\\ae_archadm\\plugins\\java" PinyinConverter "${this.chinese}"`;exec(runCommand, { cwd: path.join(rp, this.joinPluginsFolderPath('jre')) }, (err, stdout, stderr) => {if (err) {console.error(`执行错误: ${stderr}`);return;}console.log(`输出结果: ${stdout}`);});});});}// 拼接地址public joinPluginsFolderPath(p: string): string {return APP_CONFIG.production ? path.join('resources/plugins', p) : path.join('plugins', p);}
}