前言
在这份指南中,我将向大家分享如何使用Electron + Spring Boot这样的组合来构建桌面应用程序。除了上述两种技术外,我们还会使用到Vue和Gradle这样的技术。
1. 创建Gradle项目
可以使用Intellij IDEA等IDE进行创建,也可以在命令行中使用如下命令:
gradle init --type java-application
如果使用命令行的方式,需要先下载最新版本的Gradle (https://gradle.org/releases/)并配置好环境变量。
新创建好的项目中包含了build.gradle文件。现在让我们来修改这个文件:
plugins {
id 'java'
}
group 'cn.gsein'
version '1.0'
repositories {
mavenCentral()
}
dependencies {
}
2. 在项目中引入Spring Boot
在build.gradle的dependencies中加入Spring Boot相关依赖的坐标:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:2.4.2'
}
在IDE中刷新Gradle工程,这样就可以看到Spring Boot相关的依赖包被引入项目中。
接下来需要创建Spring Boot的启动类,在src/main/java目录下新建项目的包,如cn.gsein.demo,在新建的包中创建Application启动类:
package cn.gsein.demo;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
/**
* @author G. Seinfeld
* @since 2021/03/17
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).run(args);
}
}
从IDE中启动项目,如果没有问题的话,在浏览器中通过http://localhost:8080访问项目,会提示“Whitelabel Error Page”。
3. 在项目中引入Vue和Electron
3.1 安装Node.js和Npm
在开始之前,需要先安装最新版Node.js和Npm
3.1 安装vue-cli3
npm install @vue/cli -g
3.2 创建vue项目
在src/main目录下,执行以下命令,创建vue项目:
vue create electron-vue-demo
3.3 安装electron
进入到项目根目录,执行
vue add electron-builder
3.4 修改项目配置
在项目根目录下创建vue.config.js,粘贴以下代码:
const path = require('path');
function resolve (dir) {
return path.join(__dirname, dir);
}
module.exports = {
publicPath: './',
devServer: {
// can be overwritten by process.env.HOST
host: '0.0.0.0',
port: 8080
},
chainWebpack: config => {
config.resolve.alias
.set('@', resolve('src'))
.set('src', resolve('src'))
.set('common', resolve('src/common'))
.set('components', resolve('src/components'));
}
};
为了取消跨域限制,在background.js中创建窗口时做如下修改:
function createWindow () {
// Create the browser window.
win = new BrowserWindow({
width: 1200,
height: 620,
webPreferences: {
+ webSecurity: false,
nodeIntegration: true
}
})
更多细节可以参考https://zhuanlan.zhihu.com/p/75764907。
4. Electron和Spring Boot联通性调试
可以在js中增加Electron向Spring Boot发送Http请求的接口,然后IDE中分别启动Electron和Spring Boot进程,测试是否能够正常发送请求。如果发生跨域的问题,请参考第3部分修改配置。
5. Gradle中引入Node、Spring Boot和Application插件
plugins {
id 'java'
id 'org.springframework.boot' version "2.4.2"
id 'com.moowork.node' version "1.3.1"
id 'application'
}
- Node插件提供了nodeSetup、npmSetup、npmInstall等任务,可以用于管理Node.js项目,方便将Electron和Spring Boot进行统一管理。
- Spring Boot插件用于构造Spring Boot应用
- Application插件用于应用的构造、部署、发布等,还可以用于生成运行java应用的shell或bat启动脚本
- 当项目中同时存在Spring Boot和Application插件时,Application插件的所有任务将转变为Spring boot版本的任务,如任务startScripts(生成启动脚本)转变为bootStartScripts。
6. 技术细节:怎么实现Electron和Spring Boot进程同时启停?
6.1 启动Electron同时启动Spring Boot
可以使用Node.js的child_process来实现,如下:
let serverProcess
if (isDevelopment) {
serverProcess = true
} else {
if (platform === 'win32') {
serverProcess = require('child_process').spawn('cmd.exe', ['/c', 'redis-client.bat'], {
cwd: app.getAppPath() + '/bin'
})
} else {
const chmod = require('child_process').spawn('chmod', ['+x', app.getAppPath() + "/bin/redis-client"]);
chmod.on('close', (code => {
const chmod2 = require('child_process').spawn('chmod', ['+x', app.getAppPath() + "/runtime/bin/java"]);
chmod2.on('close', () => {
serverProcess = require('child_process').spawn(app.getAppPath() + "/bin/redis-client")
})
}))
}
}
也就是说,在Electron启动后,利用Node.js的child_process去执行在构建环节中生成的执行脚本。
6.2 保证Spring Boot进程启动后再打开窗口
我们可以利用Node.js的第三方依赖包minimal-request-promise来检查Spring Boot进程是否已经成功启动。minimal-request-promise可以用来发送http请求,它的体积很小,基本没有其他依赖。我们可以向Spring Boot端一个有效的Url发送请求,如果请求成功,证明进程已启动,可以打开窗口;否则,隔一段时间再次发送请求。
const startUp = function () {
const requestPromise = require('minimal-request-promise')
requestPromise.get(appUrl).then(function (response) {
console.log(response);
console.log('Server started!');
createWindow();
appStarted = true
}, function (response) {
console.log(response)
console.log('Waiting for the server start...');
setTimeout(function () {
startUp()
}, 500)
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
startUp()
})
6.3 保证所有窗口关闭后关闭掉Spring Boot进程
可以使用Node.js的第三方依赖包tree-kill,将Spring Boot端的进程杀掉。
// Quit when all windows are closed.
app.on('window-all-closed', (e) => {
if (serverProcess && process.platform !== 'darwin') {
e.preventDefault()
const kill = require('tree-kill')
kill(serverProcess.pid, 'SIGTERM', function () {
console.log('Server process killed')
serverProcess = null
app.quit()
})
}
})
7. 技术细节:怎么在构建的安装包中内置Jre?
为了在没有安装java(或安装版本不符合要求)的机器上运行应用,需要在应用中内置Java运行环境(Jre)。
7.1 将Jre拷贝到Electron模块的public目录下
可以使用Gradle task中的copy函数进行操作
var targetDir = project.file("src/main/electron/redis-electron/public")
var runtimeDir = File(targetDir, "runtime")
if (runtimeDir.exists()) {
runtimeDir.delete()
}
runtimeDir.mkdir()
copy {
from(File(System.getProperty("java.home"), "jre"))
into(runtimeDir)
}
7.2 将启动脚本中的JAVACMD修改为Jre中的java命令
Gradle application插件生成的启动脚本默认使用环境变量中配置的java命令,为了使用自定义的命令位置,需要修改启动脚本。
这里我们可以对Gradle application插件进行配置,使用自定义的模板来生成启动脚本。由于我们同时使用了Spring Boot插件,这里需要更改的是Spring Boot插件中的bootStartScripts任务,将任务中unix和windows脚本生成器的模板设定为自定义的模板。
自定义模板的内容可以照抄官方模板,只把涉及到JAVACMD的部分进行修改即可。这里我们以unix版的脚本为例,将其中涉及JAVACMD判断的部分修改为:
# Determine the Java command to use to start the JVM.
JAVACMD="\$APP_HOME/runtime/bin/java"
完整的模板可以参考附录中的参考项目
tasks {
bootStartScripts {
(unixStartScriptGenerator as TemplateBasedScriptGenerator).template = resources.text.fromFile("customUnixStartScript.txt")
(windowsStartScriptGenerator as TemplateBasedScriptGenerator).template = resources.text.fromFile("customWindowsStartScript.txt")
}
}
8. 优缺点
优点:熟悉的技术栈
缺点:应用启动较慢,应用的安装包较大
附录1 参考项目
基于本文技术开发的Redis桌面连接工具
- github地址:https://github.com/lhing17/gsein-redis-client.git
- gitee地址:https://gitee.com/lhing17/gsein-redis-client.git
网友评论