ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

将jar包封装成Windows服务:WinSW配置详解

将jar包封装成Windows服务:WinSW配置详解 1. 为什么非要把 jar 包做成 Windows 服务1.1 窗口期部署的三大痛点先聊一个特别常见的场景你写了一个 Spring Boot 应用或者任何可执行的 Java 服务在本地java -jar app.jar跑了几天一切正常部署到 Windows 服务器上双击 cmd 窗口拉起来也能跑。然后麻烦就来了。第一个麻烦你把 cmd 窗口关掉服务就没了。哪怕只是手滑点了一下那个小黑窗的关闭按钮Java 进程直接被杀接口全部 502。Windows 环境下 Java 进程的生命周期和它的宿主终端绑定在一起终端一死进程跟着死这和 Linux 下nohup那种“终端跑路我还能活着”的行为完全不一样。第二个麻烦服务器一旦重启你得手动远程登录然后重新打开 cmd、输入java -jar断断续续聊着天就可能忘了。第三个麻烦如果你用的是 Windows Server而且系统要求你设置了登录密码策略那你还得保证用户一直处于登录状态否则哪怕你人登出了服务一样跟着挂。这些问题本质上是同一个原因Java 进程只是作为“一个前台应用”在运行而不是被 Windows 当作“一个受管理的系统服务”来对待。Windows 服务由服务控制管理器SCM统一管理进程在后台运行不依赖哪个用户开着窗口能做开机自启崩溃后还能按策略自动拉起。1.2 服务化的底层逻辑Windows 服务不是玄学它就是一个受 SCM 管理的独立进程。SCM 会在系统启动阶段根据服务的启动类型Automatic、Manual、Disabled等决定是否自动拉起进程进程运行后SCM 会持续监控状态如果进程意外退出还可以根据配置执行自动重启。它的一个重要特性是服务进程在独立的会话Session 0中运行与用户的交互式桌面隔离。这就解释了为什么靠 cmd 窗口跑 jar 包在 Windows 上不靠谱——它注册不到 SCM 里天然没有“自启”“守护”“崩溃拉起”这些能力。而把 jar 包包装成服务之后你的应用就跟 IIS、SQL Server 这些“正经系统组件”一个待遇开机自动起来和用户登录不登录都没关系。那么怎么包装自己写 C 服务包装器显然不现实在 Windows 上装一个 Linux 虚拟化方案又太重杀鸡用牛刀。最直接的做法是找一个成熟的开源服务包装器把你的 Java 命令原封不动地“翻译”成一个服务定义。WinSWWindows Service Wrapper就是被大量 Java 开发者验证过的那个工具如果你希望用更轻量的方案也可以尝试 NSSM但下面我会说明为什么我最终留着 WinSW 没换。WinSW-x64则是专门给 64 位系统准备的封装 exe核心作用就是把“怎么启动你的进程、进程挂了怎么办、日志写到哪里”这些复杂逻辑封装成一套 XML 配置注册进 SCM然后你的 jar 包就算正式“转正”了。2. 工具选型为什么是 WinSW 而不是别家方案2.1 主流服务化工具的横向对比在 Windows 上把 jar 包装成服务绕不开这几条路自己拿sc create硬写用 NSSM或者用 WinSW。先说sc create它最直接但只解决了“注册服务”这一件事。启动方式、参数、日志、失败重启策略都需要另想办法。而且sc create启动进程用的命令是cmd /c本身就很容易被 Windows 的保护机制干扰实际工作中很少直接用。NSSM 是另一个老牌工具它能捕获服务的输出并写到自己的日志体系里在可视化配置方面做得很好。但它的理念和 WinSW 不太一样——NSSM 是一门心思围着“服务”转的 GUI/BAT 工具相比之下WinSW 的配置文件干净、参数清晰、适合用文本批量创建和管理尤其是配合 CI/CD 自动化时WinSW 的“一个 XML 一个 exe”的设计要友好得多。你完全可以做到把同一个 jar 包上线到十台 Windows 服务器复制一份同样的 exe 和 XML改个服务名就完事这在自动化批量部署场景里价值很高。另外NSSM 把服务的日志捕获逻辑捆绑在自身实现中对 Java 9 模块化后的输出流处理偶尔会有一些小毛病WinSW 则把日志转储功能直接内置但也允许你设置modenone完全交给应用自己处理自由度更高。社区上两派都有用户我个人用下来的体会是NSSM 适合“手点几下右键快速把某个程序变成服务”的场景而 WinSW 更适合一群 Java 后端集中维护的规模化部署。再补一个方案Spring Boot 官方其实提供了在 Linux 下通过 systemd 集成的一整套文档但在 Windows 下并没有官方服务化工具。所以现实就是Windows Server 玩 Spring BootWinSW 基本是人气最旺的那个选项。2.2 版本选择与文件命名细节WinSW 的版本迭代比较频繁大版本主要是 2.x 和 3.x还有一堆在官方 GitHub Release 页面里的预编译二进制。下载时你通常看到WinSW-x64.exe、WinSW-x86.exe、WinSW.NET4.exe这类文件还有在 2.x 及更早版本中依赖.NET Framework的变体。WinSW-x64和其他版本之间的本质区别是宿主架构不同因此选型时先确认服务器是 64 位还是 32 位即可。绝大多数现代服务器都是 64 位直接用WinSW-x64.exe就行不用多想。这里面最容易踩坑的一个点是不要用默认文件名。官方文档一直建议把下载的WinSW-x64.exe改成自己的服务名例如my-app-service.exe。因为 WinSW 运行时配置文件是“exe 文件名 .xml”的对应关系而不是直接认固定名字的config.xml。比如你下载的是WinSW-x64.exe直接运行的话它会找同目录下的WinSW-x64.xml。可你部署十个服务时要是全都用WinSW-x64.xml命名那不就全撞了所以正确姿势是把 exe 重命名为myapp.exe配置也命名为myapp.xml这样一个目录里可以同时放多个不同的服务包装器互不干扰。你下载到的 exe 具体是 2.x 还是 3.x决定了 XML 配置的差异。2.x 版本里比较麻烦的是同时存在WinSW-x64.exe基于 .NET Framework 的经典版和WinSW-x64.exe某些旧时代残留版本中用一组参数替代 XML 的逻辑新版 3.x 把 XML 结构统一得更清晰了还把一些旧参数废弃掉。如果你是从某些老博客复制的现成 XML千万不要以为所有版本通用。我在实际项目中最常用的组合是WinSW-x64 3.x JDK 8/11/17配置字段在 3.x 下都是靠谱的但同一个 XML 放到 2.x 里面有可能会出现serviceaccount、logmode之类不兼容的情况建议开工前先看一眼官方文档里的字段表格费不了三分钟。2.3 环境依赖.NET Framework 别忽略WinSW 本质是一个可执行文件它自己也是跑在 Windows 上的。2.x 及更早版本默认需要.NET Framework 4.0以上Windows Server 2012 R2、2016、2019 都自带或者很容易装3.x 之后官方改了架构带-net46等后缀的版本是针对不同 .NET 运行时做的变体底层依赖逻辑不再是个黑盒。如果安装后运行 exe 直接报“初始化失败”或者弹一个.NET相关的错误窗口十个里有八个是没装对应运行时。关于这个依赖你并不需要在服务器上安装整个 Visual Studio 或者完整的 .NET SDK。只需要保证满足对应的运行时要求即可Windows Update 里通常会有。批量部署时建议提前在一台干净的测试机上验证一次别等到生产环境这条链路出错了才开始配环境。3. 核心配置解析XML 参数逐个拆3.1 一份最小可行的配置示例WinSW 的所有行为都定义在一个 XML 文件里。下面给出一份最常用的最小配置在这个基础上按需扩展。假设你的 jar 包叫demo-app.jar放在D:\services\demo-app\目录下。service iddemo-app/id nameDemo App Service/name descriptionDemo Spring Boot Application/description executablejava/executable arguments-Xms256m -Xmx1024m -jar D:\services\demo-app\demo-app.jar/arguments workingdirectoryD:\services\demo-app/workingdirectory log modeappend pathD:\services\demo-app\logs/path /log onfailure actionrestart delay10 sec/ resetfailure1 hour/resetfailure startmodeAutomatic/startmode /service这份配置里每行都在说一件事我要启动的程序是java启动参数是固定那几个 JVM 参数和 jar 包路径工作目录是 jar 所在目录日志写到哪失败怎么处理开机要不要自动启动。能跑起来的核心就是这些剩下的都是进阶项。3.2 关键参数背后的逻辑先说id、name、description。id是注册进 Windows 服务列表里的唯一标识负责 SCM 识别一旦安装后尽量不要改因为服务注册表里已经写了这个 id。如果你改了 id 但没重新安装服务会出现“找不到服务”的诡异错误。name和description是给人看的显示在“服务”管理工具里方便同事前排查的时候能认出这个服务是干嘛的所以别写成demo这种谁都看不明白的。executable指定启动程序的路径。最稳妥的写法是写 Java 的绝对路径例如D:\jdk17\bin\java.exe。如果只写javaWinSW 启动服务时会在服务进程的环境变量里去搜java命令。这里有个非常容易踩的坑你可能在管理员 cmd 里能跑java -version不代表 SYSTEM 账户下也能找到java因为服务进程的环境变量和当前管理员用户登录时的环境变量不是一回事。很多服务配完一启动就报“找不到命令”八成是这个原因。再看arguments。这里写的是传给启动程序的全部参数JVM 参数、jar 包路径、甚至是 Spring Boot 的--server.port8081都可以。需要注意的是如果你使用了-Dspring.profiles.activeprod这类 JVM 参数建议放在-jar之前。-D参数是 JVM 启动选项如果放在-jar后面行为在某些 JDK 版本上会变得不一致为了避免踩这个坑统一格式先内存参数再-D参数再-jar路径后面再跟应用参数。workingdirectory指的是进程的工作目录。Spring Boot 在读取application.yml时如果用的是相对路径比如file:./config/就会依赖这个目录日志框架如 Logback 配置里如果写到相对目录也一样。你不设置工作目录默认可能是 WinSW 所在目录或者是C:\Windows\System32反正大概率不是你想要的。建议永远把它指到 jar 包所在目录这是最小副作用、最大避免意外的操作。log modeappend控制服务日志输出方式。WinSW 会把启动进程的标准输出和错误输出重定向到指定目录里的日志文件。可选模式有append追加、rotate按大小旋转、roll-by-time按时间滚动、none完全不捕获日志。实测经验开发环境用append最省事能看到完整的连续日志生产环境建议用roll-by-time按天切分方便按时间检索避免单个日志文件膨胀到几个 G 后连打开都费劲。模式对应的配置还得注意别出了一个日志文件名但忘了给log节点指定path结果日志文件写到了 exe 同目录下找得你怀疑人生。3.3 失败策略与开机自启的配置语义服务能在后台跑只是一个中间目标真正目标是稳定。onfailure actionrestart delay10 sec/的意思是进程意外退出时等 10 秒然后重新拉起。这个delay非常重要——进程一崩立刻重启有时反而造成连锁故障特别是如果应用在启动时依赖数据库、注册中心刚启动时数据库还没完全就绪立刻重启只会重复崩溃。给一个 10 ~ 30 秒的延时段能显著提高恢复成功率。resetfailure1 hour/resetfailure是“失败计数器重置时间”。Windows 服务有一个机制如果服务短时间内连续崩溃多次系统会认为它处于“故障风暴”中从而不再自动拉起。resetfailure告诉系统如果服务已经稳定运行了 1 小时把之前的失败次数清零重新计数。这个参数是很多只配置了onfailure的人漏掉的。缺了它高频率崩溃后服务就可能彻底罢工必须人工远程上去启动那运维事故就大了。startmodeAutomatic/startmode对应开机自启。配好这个字段并安装成功后服务会跟随系统一起启动。还有一个相关字段delayedAutoStart如果你的应用对启动顺序敏感比如一定要等到数据库服务就绪再启动可以考虑开启延后自动启动给系统多留一点初始化时间。不过多数场景下应用本身的启动就带数据库连接重试机制不延迟也能接受。3.4 服务账号与环境变量配置默认情况下服务跑在LocalSystem账户下这个账户权限极高但访问网络资源时容易“隐身”比如你要读取局域网共享目录里的配置文件LocalSystem往往不够用。WinSW 提供了serviceaccount配置可以手动指定运行服务的账号serviceaccount domainYOURDOMAIN/domain usersvc_demo/user passwordxxx/password allowservicelogontrue/allowservicelogon /serviceaccountallowservicelogon这个字段会自动给该用户分配“作为服务登录”的权限省去管理员手动去本地安全策略里翻“用户权限分配”这一步。如果你用的是普通域账户不配这个字段服务在注册时就会因权限不足直接失败。如果你需要给服务进程注入额外的环境变量比如JAVA_HOME、NACOS_ADDR等用env标签。比如env nameSPRING_PROFILES_ACTIVE valueprod/这能避免改全局系统环境变量影响其他应用是一种非常干净的注入方式。优先用 XML 注入别去全局改环境变量——这能减少一台机器上多个服务之间的变量污染。4. 从下载到托管的完整实操流程4.1 下载、改名、摆文件步骤很简单但顺序别乱。先去 WinSW 的 GitHub Releases 页面下载对应版本的WinSW-x64.exe。下载后重命名比如要装的业务叫order-center就命名为order-center.exe。然后在一个专门的服务目录里创建order-center.xml。文件布局如下D:\services\order-center\ ├── order-center.exe ├── order-center.xml ├── order-center.jar └── logs\建议把 jar 包和 exe 放在同一个目录下。虽然 WinSW 并不强制但这样工作目录、日志相对路径的语义最直观维护时也不用来回切换目录。日志目录如果不在配置里指定绝对路径就默认落在 exe 的旁边反正你到最后能找到它就行。启动命令是order-center.exe install安装成功后服务就出现在 Windows 服务列表中了。状态默认是“已停止”需要手动再启动order-center.exe start如果你想一条命令搞定安装和启动也可以用order-center.exe install order-center.exe start实际运维中我不建议合在一起跑——安装之后先看一眼服务列表里是否真的出现了再启动也不迟。毕竟如果 XML 配置有问题安装成功了但启动会失败最后还是要回头查日志。4.2 使用管理员权限与常见命令速查WinSW 的 install、start、stop、uninstall 都需要管理员权限。在 PowerShell 里执行时如果当前没有管理员权限它会直接报错“拒绝访问”或“没有权限”。所以要么提前“以管理员身份运行” PowerShell要么在命令前加上Start-Process ... -Verb RunAs自己处理提权。我个人的习惯是钉一个管理员 PowerShell 快捷方式在服务器桌面上能用得上。运维常用命令速查表操作命令安装服务order-center.exe install启动服务order-center.exe start停止服务order-center.exe stop重启服务order-center.exe restart卸载服务order-center.exe uninstall查看服务状态sc query order-center这里说一个细节升级 jar 包时我经常看到有人直接把新的 jar 覆盖到旧文件上面然后在服务列表里点“重启”。这通常没问题但 Windows 对正在运行的文件有文件锁机制覆盖有概率失败。更安全的流程是先stop再覆盖 jar 包再start。如果你停在服务正在运行的状态下强删 jar 文件可能会触发一个“文件正被另一进程使用”的错误届时又得先暂停服务再删除折腾一遍。所以养成“先停后更再启”的肌肉记忆就没这么多坑。4.3 日志体系与文件拼图WinSW 会为服务生成两类重要日志一类是它自己的包装日志一类是应用的标准输出/标准错误重定向日志。包装日志一般叫order-center.wrapper.log这个文件记录的是 WinSW 在启动、停止、重启过程中的动作而你在log节点里指定路径的就是应用日志比如order-center.out.log。排查问题时最忌讳只看应用日志忽略 wrapper 日志。如果服务一直启动失败你去翻应用日志发现根本没有输出那作者大概率要看 wrapper 日志——WinSW 会把启动失败的原因比如“找不到 java 可执行文件”“端口占用导致启动脚本异常退出”之类记录在 wrapper 日志里。所以正确姿势是wrapper 日志配合应用日志一起看先定位是“压根没起来”还是“起来后崩溃”。如果使用roll-by-time模式WinSW 一般是按天生成类似order-center.2025-01-15.log这样的文件名。配合 Windows 自带的“计划任务”定期清理老日志或者直接用磁盘清理脚本把超过 15 天的日志删掉能有效避免日志盘被写满。别小看这个问题我见过太多服务器的 C 盘被几个月没清理的 Logback 日志撑爆的案例。5. 常见问题与排查技巧实录5.1 “错误 1053服务没有及时响应启动或控制请求”这是 WinSW 启动失败时最经典的一个报错。出现这个错误大概率不是 Java 应用真的“没响应”而是 WinSW 在启动服务后的默认超时时间内没等到进程稳定运行。Java 启动一个大型 Spring Boot 服务如果内存分配很大、注册中心连接超时、数据库初始化慢前几秒可能什么输出都没有SCM 等得不耐烦就直接判定“启动超时”。解决办法是在service节点里加一个stoptimeout和starttimeout例如starttimeout60 sec/starttimeout stoptimeout30 sec/stoptimeoutstarttimeout告诉 SCM给我 60 秒时间把进程拉起来别急着报错。stoptimeout是停止服务时等待进程优雅退出的时间。如果你在停止服务时发现老是不干净退出也可以调大它。这个参数非常常见遇到 1053 先加这个配置能省大量排查时间。不过还有一层隐患即使加了starttimeout如果 Java 进程确实没被正确拉起最后依然会报启动超时。这时候一定要打开 wrapper 日志看根因——里面会直接告诉你进程退出码和退出的上下文。如果退出码是 1那多半是 Java 命令或者参数写错了先把路径改对再说。5.2 日志文件根本没有生成合理的排查顺序是先看日志目录是否存在。WinSW 默认不会自动创建logs目录如果你在 XML 里写了一个不存在的路径它可能会在启动时报错或者静默地把日志丢弃。所以提前用资源管理器建好目录不要寄希望于 WinSW 帮你建。再一个是日志模式设置。如果把log modenone写上了从界面上怎么点它都不会输出日志文件。注意检查 XML 里是否真的配了modeappend或moderoll-by-time别把模式当摆设。还有一种罕见情况应用本身用的是 Logback/Log4j2输出到了它自己配置的日志文件里而不是标准输出。WinSW 只能接管标准输出和错误输出如果应用内部日志策略清晰可能你一直找不到的“WinSW 日志”其实是压根不存在的。这不算 bug只是两种日志体系的边界感问题。我在最开始配置时故意在应用里加了一句System.out.println(STARTED)来验证 WinSW 的日志链路确认输出捕获正常后再去调应用自己的日志框架——这比来回猜靠谱得多。5.3 服务显示 Running 但是接口完全不通“服务在运行但业务打不开”这种情况通常有几种原因。第一个是端口冲突。Windows 上很多程序会占端口比如你服务配置的是 8080但机器上有另一个老应用也在用 8080Spring Boot 启动失败后整个进程退出了但 SCM 在启动时看到的是进程曾经存在过所以状态显示错乱。这种情况在日志里有明显特征Port already in use。第二个原因是“延迟启动导致的假 Running”。服务启动成功但 Spring Boot 还没完成初始化服务列表里就已经是 Running 状态了。这是服务机制决定的——SCM 只关心“进程活了”不关心“业务完全就绪”。排查时别只看服务状态直接 curl 一下健康检查接口比如 Spring Boot Actuator 的/actuator/health返回 JSON{status:UP}才算真正好使。第三个原因是 Spring Boot 的上下文没被加载成功比如数据库连接失败、Redis 没连上、配置中心拉不下配置进程本身没退出但一直处于重试状态。这时候服务状态也是 Running接口不通很正常。所以“Running 就等于一切正常”这根弦得刻在脑子里。5.4 服务的“找不到 JDK”问题前面提过服务用 SYSTEM 账户运行环境变量和用户账户不同。很多开发机把 JDK 装在用户目录下比如C:\Users\dev\jdk17只有当前登录用户能访问SYSTEM 账户一看路径不存在或者权限不够。解决路径有两种一是 XML 里executable写全绝对路径C:\Users\dev\jdk17\bin\java.exe二是把 JDK 放到所有用户都能访问的公共目录比如C:\Program Files\Java\jdk17。第二个做法更稳因为即使未来换用户启动服务也不会受环境变量牵连。另外服务注册时的路径解析是“即时的”。也就是说你安装服务时 XML 里executable如果写的是java那么安装后 SCM 记下来的是当时的 PATH 查找结果。如果你在安装之后又装了新的 JDK改了系统 PATH正在运行的服务不会跟着变化除非你重装服务。这个坑极其隐蔽——明明手动java -version正常服务里却一直报找不到命令。基于这个经验我强烈建议 XML 里永远写绝对路径从安装那一刻就锁死杜绝这类事后幻象。5.5 一台机器的多个服务实例怎么共存同一个服务器上大概率不只跑一个 Java 应用可能同时有 order-center、user-center、gateway 三个服务。把它们各放一个目录各自配一个 exe 和 xml是基础要求。但还有一个容易遗漏的Spring Boot 默认的 PID 文件。多个服务如果都写到同一个目录下的同一个app.pid文件会发生互相覆盖排查时看错 PID 会引起很大的误导。所以每个服务最好单独配 PID 文件位置。在启动参数里传--spring.pid.fileD:/services/order-center/order-center.pid就能让每个服务都维护自己的 PID 文件后续做进程级监控也方便。同时在日志切割策略上多个服务建议不要共用同一个日志目录。WinSW 配置的是每个服务自己的日志路径建议继续保持“每个服务一个目录”的结构防止日志滚动时互踩。5.6 关于系统重启后服务的启动顺序问题还有一个常被忽略的点如果 Windows 系统同时跑着 MySQL、Redis、你的 Java 服务系统重启后服务的拉起顺序通常是不确定的。即使你把 Java 服务设为 Automatic它也可能在数据库还没就绪时就抢跑了。高可用要求高的场景下最好把delayedAutoStart设为true给系统一个缓冲窗口。更进一步的方案是在应用层做连接重试而不是把命运完全交给服务启动顺序。如果实在对启动时序有硬性要求比如必须等某些外部服务完全启动后才拉起你的应用可以考虑把服务的启动类型改为“AutomaticDelayed Start”或者借助 Windows 任务计划程序写一段延时脚本去触发start命令。但我个人实践经验是绝大多数应用做好数据库连接池重试就够了不必要把系统调度搞得那么复杂。6. 踩坑后的经验总结用 WinSW 托管 jar 包我前后部署过的环境加起来有几十台了。回头总结真正让人难受的不是配置文件写不出来而是配置完后想当然地认为没问题。每个坑都是“看一眼日志就能发现”的级别但很多人不看日志、不信日志全凭猜白白耽误很多时间。这里分享几个我个人的习惯不一定对所有人都有用但确实帮我在线上少熬夜第一在服务器上部署任何 jar 包之前先在当前目录下用完整命令手动跑一遍java -Xms256m -Xmx1024m -jar app.jar确认它能不能起来、端口是否正常、依赖的外部组件是否可达然后再把它写进 XML。这个步骤能提前过滤掉一半的配置错误。第二永远把 JDK 路径写成绝对的。不要赌“环境变量一定没问题”这是服务化场景里最不高明的赌法。第三日志路径一定用绝对路径且要保证 SYSTEM 账户有写权限。部分服务器把 D 盘格式化成 NTFS 之后权限设计奇怪导致服务能启动但日志写不进去这种情况服务看起来“正常”排查时却像瞎子摸象——因为完全没有运行证据。最后还想说一个扩展点WinSW 支持通过命令行参数覆盖 XML 里的部分字段比如-p指定端口。这对 CI/CD 场景很有用——你在构建流水线里可以用不同参数安装同一个服务的不同环境。但前提是你把 XML 里的占位符规则搞清楚不同版本的支持程度不一样这个建议当作进阶项去研究初学阶段只需要把最基础的安装、配置、日志链路跑通就已经解决了 90% 的日常诉求。按这个思路走你手上的 jar 包就不再是“开着 cmd 窗口才能活的脆弱进程”了它会变成 Windows 上一个正经的服务开机自启、崩溃自愈、日志可查、更新可控。下一步你可以把部署过程做成一条脚本把 jar 拷到目录、覆盖 exe 和 xml、执行 install 和 start全自动完成发布。到那天Windows 服务器的运维体验也差不多能摸到 Linux 的脚后跟了。
返回列表