Java Dev Work Flow

2026-04-03
3 min read

在典型的 Java 后端迭代里,真正吞噬效率的不止有代码编写,还有冗长的重启和低质量发布反馈。

dev work flow

这篇笔记聚焦一件事:把「本地热替换调试 + 远程可重复部署 + 实时日志观测」串成一条最短反馈链路。

  • 本地使用 JRebel 进行快速调试(减少反复重启)
  • 通过 SSH 配置连接远程机器
  • 本地执行一键部署脚本,远程拉代码、构建、重启
  • 登录服务器实时追日志验证结果

核心目标是缩短「code -> build -> boot -> observe -> verify」闭环延迟,同时保证线上变更可控、可回滚、可审计。

确实,今天可选的 CI/CD 工具已经非常多:Jenkins、GitLab CI、GitHub Actions、ArgoCD 都能很好地解决流水线编排问题。
但这个场景里我更倾向于手写一套可维护的最小 CI/CD(script-first):

  • 步骤透明:每个阶段输入/输出清晰,失败点可定位
  • 状态可见:不依赖黑盒插件,shell 级即可复盘
  • 依赖可控:最小外部耦合,迁移成本低
  • 变更可审计:脚本即规范,review 即发布策略

这不是“手工点点点部署”,而是把发布链路代码化,用工程化方式维护一个轻量、可演进的交付系统。

architecture flow

+---------------------+
|   local workspace   |
|  code + jrebel run  |
+----------+----------+
           |
           | ssh skytech-prod
           v
+---------------------+
|   remote server     |
|  git pull + mvn pkg |
+----------+----------+
           |
           | start jar
           v
+---------------------+
|   application up    |
| tail log and verify |
+---------------------+

ssh config

在 `~/.ssh/config` 中配置目标主机别名,后续脚本和命令都可以复用这个别名。

Host skytech-prod
    Hostname 10.100.10.178
    User root
    IdentityFile ~/.ssh/id_rsa

build on local

  1. use jrebel

    https://www.jrebel.com/jrebel-releases

    在项目中创建 `src/main/resources/rebel.xml`,让 JRebel 知道 class 目录位置。

    <application
        generated-by       = "maven"
        build-tool-version = "3.6.1"
        plugin-version     = "1.1.10"
        xmlns              = "http://www.zeroturnaround.com"
        xmlns:xsi          = "http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation = "http://www.zeroturnaround.com http://update.zeroturnaround.com/jrebel/rebel-2_2.xsd">
    <classpath>
        <dir name="~/workspace/skytech/skytech-app/target/classes"></dir>
    </classpath>
    </application>
    

    说明:

    • `target/classes` 是 Maven 编译产物目录
    • 修改 Java 代码后,重新编译对应模块即可热更新(无需整体重启)
    • 热替换主要覆盖方法体与实现细节;涉及类结构漂移(字段签名、继承层次、Spring 上下文装配边界)时通常仍需冷启动
  2. start on local

    "$JAVA_11_HOME"/bin/java -agentpath:/Users/van/ZY/workspace/jrebel/lib/libjrebel64.dylib \
    -Dspring.profiles.active=test -Dserver.port=8383 -jar api.jar
    

    建议补充 JVM 参数(按需):

    • `-Xms512m -Xmx1024m`:避免本地调试时频繁 GC 抖动
    • `-Dfile.encoding=UTF-8`:确保日志和接口字符集一致
    • `-XX:+HeapDumpOnOutOfMemoryError`:本地复现疑难问题时保留故障现场

build on server

首次部署先克隆仓库:

git clone https://user:password@git.example.com/group/repo.git

注意:生产环境不建议把用户名密码直接写在 clone URL 中,建议改为 SSH Key 或 token。
另外建议把部署用户降权,避免长期使用 root 直接发布。

  1. script on local

    本地触发部署(只负责连接与调用远程脚本):

    #!/bin/bash
    set -e
    
    # 记录发布日志
    TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$TIMESTAMP] 部署 skytech-app" >> deploy.log
    
    ssh skytech-prod 'cd /root/skytech/skytech-app/ && sh run-on-server-prod.sh'
    
  2. script on server

    服务器构建并重启服务:

    #!/bin/bash
    set -e
    
    git pull
    
    export JAVA_HOME=/root/jdk-11.0.11
    /root/apache-maven-3.9.14/bin/mvn clean package -U
    
    export TZ="Asia/Shanghai"
    # 仅杀掉当前应用,避免误杀机器上其它 Java 进程
    APP_NAME=skytech-app-1.0.0-SNAPSHOT.jar
    ps -ef | grep -v grep | grep "${APP_NAME}" | awk '{print $2}' | xargs -r kill -9
    echo '' > nohup.out
    nohup /root/jdk-11.0.11/bin/java -jar ./target/skytech-app-1.0.0-SNAPSHOT.jar \
    --spring.profiles.active=prod \
    --logging.level.com.vanniuner.platform.sys.config.filter=error \
    --spring.cloud.nacos.config.enabled=false \
    --spring.cloud.nacos.discovery.enabled=false &
    
    tail -f nohup.out
    

    说明:

    • `set -e`:任一步骤失败立即退出,避免半成功状态
    • `xargs -r`:无匹配进程时不执行 kill,防止空参数报错
    • `tail -f`:部署后第一时间观察启动日志和异常堆栈
    • 可以进一步把 `kill -9` 改成 `TERM -> 等待 -> KILL` 两阶段退出,降低强杀带来的副作用

log tail on server

持续查看最新业务日志:

ssh skytech-prod "ls -t ~/skytech/skytech-app/logs/*.log | head -1 | xargs tail -f -n 1000"