概述

开发了很多 java 项目,最终启动的时候都要用命令行方式启动,项目逻辑千千万,但启动方式都大同小异,参考了一些开源项目(比如 Nacos)的启动脚本,下面整理了一些我目前正在用的 java 应用启动脚本,也支持 jar 加密成 xjar 后的启动,大家可以参考下面的脚本,改造成适合自己的方式

springboot 应用(支持 jar 和 xjar)

springboot 打包部署压缩包看 springboot 应用打包成部署压缩包(支持 xjar)

部署目录结构

app
  |-bin/
    |-app-server.sh
    |-app.jar
    |-app.xjar
    |-xjar
  |-config/
    |-application.yml
  |-logs/
  |-workspace/

使用方法:

  • 启动 jar:./bin/app-server.sh start

  • 启动 xjar:./bin/app-server.sh start xjar

  • 停止:./bin/app-server.sh stop

  • 重启 jar:./bin/app-server.sh restart

  • 重启 xjar:./bin/app-server.sh restart xjar

app-server.sh

#!/bin/bash

BASE_DIR=`cd "$(dirname "$0")"; pwd`
JAR_FILE=$BASE_DIR/app.jar
XJAR_BIN=$BASE_DIR/xjar
XJAR_FILE=$BASE_DIR/app.xjar
CONFIG_FILE=$BASE_DIR/../config/application.yml
LOG_DIR=$BASE_DIR/../logs/
WORKSPACE=$BASE_DIR/../workspace

cygwin=false
darwin=false
os400=false
case "`uname`" in
CYGWIN*) cygwin=true;;
Darwin*) darwin=true;;
OS400*) os400=true;;
esac
error_exit ()
{
    echo "错误: $1 !!"
    exit 1
}

[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/opt/taobao/java
[ ! -e "$JAVA_HOME/bin/java" ] && unset JAVA_HOME

if [ -z "$JAVA_HOME" ]; then
  if $darwin; then

    if [ -x '/usr/libexec/java_home' ] ; then
      export JAVA_HOME=`/usr/libexec/java_home`

    elif [ -d "/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home" ]; then
      export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home"
    fi
  else
    JAVA_PATH=`dirname $(readlink -f $(which javac))`
    if [ "x$JAVA_PATH" != "x" ]; then
      export JAVA_HOME=`dirname $JAVA_PATH 2>/dev/null`
    fi
  fi
  if [ -z "$JAVA_HOME" ]; then
        error_exit "本应用需要 jdk 环境,请先安装 jdk 环境并设置 JAVA_HOME 环境变量"
  fi
fi

# jdk主版本
JAVA_MAJOR_VERSION=$($JAVA -version 2>&1 | sed -E -n 's/.* version "([0-9]*).*$/\l/p"')

HELP_INFO="
用法: $0 [options]
参数:
  -h, --help              帮助信息
  start <package-type>    启动服务,type可选jar或xjar,默认是jar
  stop                    停止服务
  restart <package-type>  重启服务,type可选jar或xjar,默认是jar
"

CMD_TYPE="unknown"
START_PACKAGE_TYPE="jar"
while [[ $# -gt 0 ]]; do
  case "$1" in
    -h|--help)
      echo "$HELP_INFO"
      exit 0
      ;;
    start)
      CMD_TYPE="start"
      if [[ -n "$2" ]]; then
        START_PACKAGE_TYPE="$2"
      else
        START_PACKAGE_TYPE="jar"
      fi
      break
      ;;
    stop)
      CMD_TYPE="stop"
      break
      ;;
    restart)
      CMD_TYPE="restart"
      if [[ -n "$2" ]]; then
        START_PACKAGE_TYPE="$2"
      else
        START_PACKAGE_TYPE="jar"
      fi
      break
      ;;
    *)
      error_exit "未知参数: $1"
      echo "$HELP_INFO"
      exit 1
      ;;
  esac
done

JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m"

# (非通用)设置自定义的应用数据保存目录、日志保存目录
# 前提是应用内的logback使用了common.log.path变量,以及应用内使用了workspacePath变量指定的目录作为数据存储目录
# 用 -D 传的参数,application.yml 和 logback.xml内部都能使用
# 比如 ${common.log.path:-./logs/},则尝试从启动名中取值,取不到则设置为 ./logs/
JAVA_OPT="${JAVA_OPT} -Dcommon.log.path=$LOG_DIR -DworkspacePath=$WORKSPACE"

# 先加载jar里面的bootstrap.yml,再加载外部的application.yml
JAVA_ARGS="--spring.config.location=classpath:/bootstrap.yml,file:$CONFIG_FILE"
# 定义logback日志保存位置
JAVA_ARGS="${JAVA_ARGS} --logging.file.path=$LOG_DIR"

if [ "$JAVA_MAJOR_VERSION" -ge "9" ]; then
  # jdk9以上,使用-Xlog:gc*设置GC输出配置
  JAVA_OPT="${JAVA_OPT} -XLog:gc*:file=${LOG_DIR}/app_gc.log:time,tags:filecount=10:filesize=51200"
else
  JAVA_OPT="${JAVA_OPT} -Xloggc:${LOG_DIR}/app_gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=50M"
fi

start ()
{
  if [[ "$START_PACKAGE_TYPE" == "jar" ]]; then
    if [ ! -f "$JAR_FILE" ]; then
      error_exit "未找到jar: $JAR_FILE"
      exit 1
    else
      echo "启动jar: $JAR_FILE"
      nohup $JAVA_HOME/bin/java $JAVA_OPTS -jar $JAR_FILE $JAVA_ARGS > /dev/null 2>&1 &
      echo $! > $BASE_DIR/app.pid
    fi
  elif [[ "$START_PACKAGE_TYPE" == "xjar" ]]; then
    if [ ! -f "$XJAR_BIN" ]; then
      error_exit "未找到xjar执行程序: $XJAR_BIN"
    elif [ ! -f "$XJAR_FILE" ]; then
      error_exit "未找到xjar: $XJAR_FILE"
    else
      echo "启动xjar: $XJAR_FILE"
      nohup $XJAR_BIN $JAVA_HOME/bin/java $JAVA_OPTS -jar $XJAR_FILE $JAVA_ARGS > /dev/null 2>&1 &
      echo $! > $BASE_DIR/app.pid
    fi
  else
    error_exit "未知的启动类型: $START_PACKAGE_TYPE"
  fi
}

kill_process ()
{
  local PID=$1
  # 找子进程
  CHILD_PIDS=$(pgrep -P $PID)
  if [[ -n "$CHILD_PIDS" ]]; then
    echo "子进程: $CHILD_PIDS"
    for CHILD_PID in $CHILD_PIDS; do
      kill_process $CHILD_PID
    done
  fi
  if ps -p $PID > /dev/null; then
    echo "停止进程: $PID"
    kill $PID
    # 循环检测进程是否已停止
    WAITING_TIME=0
    while ps -p $PID > /dev/null; do
      echo "等待进程停止..."
      sleep 1
      WAITING_TIME=$((WAITING_TIME + 1))
      if [[ WAITING_TIME -ge 10 ]]; then
        echo "进程停止超时,强制终止进程: $PID"
        kill -9 $PID
        break
      fi
    done
    echo "进程已停止"
  else
    echo "进程已停止或不存在: $PID"
  fi
}

stop ()
{
  if [[ -f "$BASE_DIR/app.pid" ]]; then
      PID=$(cat $BASE_DIR/app.pid)
      if ps -p $PID > /dev/null; then
        echo "停止进程: $PID"
        kill_process $PID
        echo "进程已停止"
      else
        echo "进程已停止或不存在: $PID"
      fi
      rm -f $BASE_DIR/app.pid
    else
      echo "未找到进程ID文件: $BASE_DIR/app.pid"
    fi
}

restart ()
{
  stop
  start
}

if [[ "$CMD_TYPE" == "start" ]]; then
  start
elif [[ "$CMD_TYPE" == "stop" ]]; then
  stop
elif [[ "$CMD_TYPE" == "restart" ]]; then
  restart
else
  echo "$HELP_INFO"
  exit 1
fi