Java Service Wrapper 是一个使 Java 应用程序能作为 Windows 服务或 UNIX / Linux 守护进程运行的开源软件。它提供了一个可靠的方式来启动、停止,并且监控 Java 应用程序的状态。
Java Service Wrapper(以下简称 JSW)的主要特点和功能包括:
作为服务或守护进程运行 :允许 Java 程序在没有用户登录情况下作为服务或守护进程运行。
控制和管理 :提供对 Java 应用程序的启动、停止、重启等控制管理功能。
配置灵活 :通过配置文件对 Java 虚拟机参数、环境变量、类路径等进行灵活配置。
日志记录 :支持详细的日志记录,便于问题排查和监控。
性能监控 :监控 Java 应用程序的性能,如响应时间和内存使用情况。
错误恢复 :可以配置服务在遇到错误时的自动恢复动作,如重新启动服务。
跨平台 :支持在 Windows、Linux 和其他 UNIX / Linux 系统上运行。
在工作中,我使用 JSW 将 Spring Boot 开发的 Java 应用部署在 Linux 服务器上,对于相同的平台环境可以做到一次部署,到处运行。
下载 JSW 截止到 2024 年 4 月,JSW 支持的系统和架构如下:
基本涵盖了主流的操作系统和架构,下载时选择与服务器对应的操作系统和架构。JSW 有三个版本,分别是专业版、标准版和社区版,我们一般下载社区版,社区版是免费使用的。以 Linux x86 64 位为例,下载最新版本的 .tar.gz
格式的压缩包,先解压出来看看文件目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ├── README_de.txt ├── README_en.txt ├── README_es.txt ├── README_ja.txt ├── bin │ ├── demoapp │ ├── testwrapper │ └── wrapper ├── conf │ ├── demoapp.conf │ └── wrapper.conf ├── doc │ ├── index.html │ ├── revisions.txt │ └── wrapper-community-license-1.3.txt ├── lib │ ├── libwrapper.so │ ├── wrapper.jar │ ├── wrapperdemo.jar │ └── wrappertest.jar ├── logs │ └── wrapper.log └── src ├── bin │ ├── App.sh.in │ └── App.shconf.in └── conf └── wrapper.conf.in 9 directories, 20 files
压缩包为我们提供了一个 demoapp 测试程序,你可以在终端进入到 bin
目录执行 ./testwrapper console
来测试一下 JSW 在当前系统是否正常工作。
JSW 的核心文件主要有五部分组成:
bin/wrapper
wrapper 主程序,一个可执行的二进制文件
lib/libwrapper.so
本地库文件,使用此文件与操作系统交互
lib/wrapper.jar
wrapper 使用此 jar 包实现 JVM 参数配置和日志记录等功能
src/bin/App.sh.in
wrapper 的控制脚本,包括启动、停止等操作
src/conf/wrapper.conf.in
wrapper 配置文件,包括 JVM 参数配置、启动类配置、启动参数等配置
部署 Spring Boot 项目 首先使用 maven 创建一个简单的 Spring Boot 项目jsw-demo
并添加 spring-boot-starter-web
依赖,项目文件结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 . ├── HELP.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main │ ├── java │ │ └── cc │ │ └── cui │ │ └── jswdemo │ │ └── JswDemoApplication.java │ └── resources │ ├── application.yml │ │ ├── static │ │ │ └── a.txt │ │ └── templates │ │ └── b.txt └── test └── java └── cc └── cui └── jswdemo └── JswDemoApplicationTests.java
但默认的 maven 配置打包出来的是一个 jar 包,为了保持与官网 demo 相同的目录风格,我希望最终打包好的程序目录结构是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 jsw-demo/target/jsw-demo ❯ tree . ├── bin │ ├── application.yml │ ├── cc │ │ └── cui │ │ └── jswdemo │ │ └── JswDemoApplication.class │ ├── jsw-demo │ ├── static │ │ └── a.txt │ ├── templates │ │ └── b.txt │ ├── wrapper │ └── wrapper.conf └── lib ├── jackson-annotations-2.15.4.jar ├── jackson-core-2.15.4.jar ├── jackson-databind-2.15.4.jar ├── jackson-datatype-jdk8-2.15.4.jar ├── jackson-datatype-jsr310-2.15.4.jar ├── jackson-module-parameter-names-2.15.4.jar ├── jakarta.annotation-api-2.1.1.jar ├── jul-to-slf4j-2.0.12.jar ├── libwrapper.so ├── log4j-api-2.21.1.jar ├── log4j-to-slf4j-2.21.1.jar ├── logback-classic-1.4.14.jar ├── logback-core-1.4.14.jar ├── micrometer-commons-1.12.4.jar ├── micrometer-observation-1.12.4.jar ├── slf4j-api-2.0.12.jar ├── snakeyaml-2.2.jar ├── spring-aop-6.1.5.jar ├── spring-beans-6.1.5.jar ├── spring-boot-3.2.4.jar ├── spring-boot-autoconfigure-3.2.4.jar ├── spring-boot-starter-3.2.4.jar ├── spring-boot-starter-json-3.2.4.jar ├── spring-boot-starter-logging-3.2.4.jar ├── spring-boot-starter-tomcat-3.2.4.jar ├── spring-boot-starter-web-3.2.4.jar ├── spring-context-6.1.5.jar ├── spring-core-6.1.5.jar ├── spring-expression-6.1.5.jar ├── spring-jcl-6.1.5.jar ├── spring-web-6.1.5.jar ├── spring-webmvc-6.1.5.jar ├── tomcat-embed-core-10.1.19.jar ├── tomcat-embed-el-10.1.19.jar ├── tomcat-embed-websocket-10.1.19.jar └── wrapper.jar
在上述的文件结构中,target
目录下包含了一个 jsw-demo
的项目文件夹,其 bin
目录包含了 target/classes
目录下的所有文件,并额外增加了 wrapper
和 wrapper.conf
文件,lib
目录包含了所有第三方依赖包和 libwrapper.so
本地库文件和 wrapper.jar
文件。
为了实现上面的打包效果,需要我们做一些额外的操作。首先需要将 JSW 的核心文件复制到项目中。
bin/wrapper
–> ${APP_HOME}/wrapper/bin/
src/bin/App.sh.in
–> ${APP_HOME}/wrapper/bin/jsw-demo
(与项目名称一致或自定义)
src/bin/wrapper.conf.in
–> ${APP_HOME}/wrapper/bin/wrapper.conf
lib/libwrapper.so
–> ${APP_HOME}/wrapper/lib/
lib/wrapper.jar
–> ${APP_HOME}/wrapper/lib/
此时项目文件结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 . ├── main │ ├── java │ │ └── cc │ │ └── cui │ │ └── jswdemo │ │ └── JswDemoApplication.java │ ├── resources │ │ ├── application.yml │ │ ├── static │ │ │ └── a.txt │ │ └── templates │ │ └── b.txt │ └── wrapper │ ├── bin │ │ ├── jsw-demo │ │ ├── wrapper │ │ └── wrapper.conf │ └── lib │ ├── libwrapper.so │ └── wrapper.jar └── test └── java └── cc └── cui └── jswdemo └── JswDemoApplicationTests.java
随后配置 maven 打包插件,将 src/wrapper
目录下的文件复制到对应的位置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-dependency-plugin</artifactId > <executions > <execution > <id > copy-dependencies</id > <phase > package</phase > <goals > <goal > copy-dependencies</goal > </goals > <configuration > <outputDirectory > ${project.build.directory}/jsw-demo/lib</outputDirectory > <excludeTransitive > false</excludeTransitive > <stripVersion > false</stripVersion > <includeScope > runtime</includeScope > </configuration > </execution > </executions > </plugin > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-resources-plugin</artifactId > <configuration > <encoding > UTF-8</encoding > </configuration > <executions > <execution > <id > copy-resources</id > <phase > package</phase > <goals > <goal > copy-resources</goal > </goals > <configuration > <encoding > UTF-8</encoding > <outputDirectory > ${project.build.directory}/jsw-demo/bin</outputDirectory > <resources > <resource > <directory > ${project.basedir}/src/main/resources</directory > </resource > <resource > <directory > ${project.basedir}/src/main/wrapper/bin</directory > </resource > <resource > <directory > ${project.build.directory}/classes</directory > </resource > </resources > </configuration > </execution > <execution > <id > copy-wrapper-lib</id > <phase > package</phase > <goals > <goal > copy-resources</goal > </goals > <configuration > <encoding > UTF-8</encoding > <outputDirectory > ${project.build.directory}/jsw-demo/lib</outputDirectory > <resources > <resource > <directory > ${project.basedir}/src/main/wrapper/lib</directory > </resource > </resources > </configuration > </execution > </executions > </plugin > </plugins >
在部署到服务器之前还需要更改 src/wrapper/bin
目录下的 jsw-demo
文件和 wrapper.conf
文件,来配置应用名称和启动方式等参数。
jsw-demo
实际上是 wrapper 的启动脚本,主要改动里面的几个地方:
1 2 3 4 5 6 7 APP_NAME ="jsw-demo" APP_LONG_NAME ="JSW Demo App" WRAPPER_CMD ="./wrapper" WRAPPER_CONF ="./wrapper.conf"
wrapper.conf
是 wrapper 的配置文件,配置项较多:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 wrapper.jarfile =../lib/wrapper.jarwrapper.java.command =javaset.JAVA_HOME =/java/pathwrapper.java.command =%JAVA_HOME%/bin/javawrapper.java.mainclass =org.tanukisoftware.wrapper.WrapperSimpleAppwrapper.java.classpath.1 =../lib/*.jarwrapper.java.classpath.2 =../binwrapper.java.library.path.1 =../libwrapper.java.initmemory =500 wrapper.java.maxmemory =2048 wrapper.app.parameter.1 =cc.cui.jswdemo.JswDemoApplicationwrapper.console.format =PMwrapper.console.loglevel =INFOwrapper.logfile =../logs/wrapper.logwrapper.logfile.format =LPTMwrapper.logfile.loglevel =INFOwrapper.logfile.rollmode =SIZE_OR_WRAPPERwrapper.logfile.maxsize =10 mwrapper.logfile.maxfiles =10 wrapper.syslog.loglevel =NONE
到目前为止,wrapper 就集成到了 Spring Boot 项目中,使用 maven 打包后,把 target
下面的整个项目文件夹 jsw-demo
上传到服务器尝试运行。
在运行之前需要创建 wrapper.conf
文件中配置的日志目录,因为 wrapper 并不会自动创建,当 wrapper 找不到日志目录时,会将日志写入到与启动脚本相同的目录下。
上面说了 bin/jsw-demo
文件其实是一个启动脚本,赋予可执行权限并执行 ./jsw-demo console
以控制台方式启动 wrapper,如果配置正确的话应该会看到类似下面的结果:
可以使用 ./jsw-demo
命令查看启动脚本的帮助信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root@localhost bin]#./jsw-demo Usage: ./jsw-demo [ console | start | stop | restart | condrestart | status | install | installstart | remove | dump ] Commands: console Launch in the current console. start Start in the background as a daemon process. stop Stop if running as a daemon or in another console. restart Stop if running and then start. condrestart Restart only if already running. status Query the current status. install Install to start automatically when system boots. installstart Install and start running as a daemon process. remove Uninstall. dump Request a Java thread dump if running.
console
在当前 shell 终端启动
start
以守护进程在后台启动
stop
停止守护进程或停止在另一个终端启动的进程
restart
重启
condrestart
仅在运行时重启
status
查看当前运行状态
install
安装到当前系统并随机启动
installstart
安装到当前系统并马上启动(since v3.5.28)
remove
卸载
dump
如果进程已经启动,则请求 Java 线程转储
首次部署时需要手动创建 wrapper.conf
配置中的日志目录 wrapper 启动后会在启动脚本同级目录创建 pid 等文件 wrapper 停止后会删除创建的 pid 等文件
高级用法 实现 Java 应用自动重启 有时我们希望部署的 Java 应用在某些场景下能够自动重启,比如在工作中我使用 Java 编写了一个升级系统,当升级系统自身升级了依赖包之后,我希望它能自动重启以生效。此时我们可以借助 Java Service Wrapper 的特性实现。
在 wrapper.conf
底部增加配置:
1 2 wrapper.on_exit.9 =RESTART
上面的配置告诉 JSW,当程序的退出代码为 9
时执行重启操作。
随后需要在 Java 项目中合适的位置定义退出代码:
参考资料