美文网首页
一份使用Electron + Spring Boot构建桌面应用

一份使用Electron + Spring Boot构建桌面应用

作者: 程序员吉森 | 来源:发表于2021-03-17 17:33 被阅读0次

    前言

    在这份指南中,我将向大家分享如何使用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桌面连接工具

    附录2 相关技术简介

    Gradle

    Electron

    Vue

    Spring Boot

    参考资料

    相关文章

      网友评论

          本文标题:一份使用Electron + Spring Boot构建桌面应用

          本文链接:https://www.haomeiwen.com/subject/eauecltx.html