源码解析
#!/bin/bash
#
# . ____ _ __ _ _
# /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
# ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
# \\/ ___)| |_)| | | | | || (_| | ) ) ) )
# ' |____| .__|_| |_|_| |_\__, | / / / /
# =========|_|==============|___/=/_/_/_/
# :: Spring Boot Startup Script ::
#
### BEGIN INIT INFO
# Provides: {{initInfoProvides:spring-boot-application}}
# Required-Start: {{initInfoRequiredStart:$remote_fs $syslog $network}}
# Required-Stop: {{initInfoRequiredStop:$remote_fs $syslog $network}}
# Default-Start: {{initInfoDefaultStart:2 3 4 5}}
# Default-Stop: {{initInfoDefaultStop:0 1 6}}
# Short-Description: {{initInfoShortDescription:Spring Boot Application}}
# Description: {{initInfoDescription:Spring Boot Application}}
# chkconfig: {{initInfoChkconfig:2345 99 01}}
### END INIT INFO
# [ -z STRING ] “STRING” 的长度为零则为真。
# [ -n STRING ] “STRING” 的长度为非零 non-zero则为真。
# 判断是否开启 debug 模式。如果开启了 debug 模式,则通过 set -x 设置显示执行的命令
[[ -n "$DEBUG" ]] && set -x
# Initialize variables that cannot be provided by a .conf file
# 获取工作空间路径
WORKING_DIR="$(pwd)"
# shellcheck disable=SC2153
# 获取 jar 文件路径,并赋值
[[ -n "$JARFILE" ]] && jarfile="$JARFILE"
# 获取 APP 名字
[[ -n "$APP_NAME" ]] && identity="$APP_NAME"
# Follow symlinks to find the real jar and detect init.d script
cd "$(dirname "$0")" || exit 1
# 如果不存在 jarfile,则将 当路径/当前执行文件名赋值给 jarfile
[[ -z "$jarfile" ]] && jarfile=$(pwd)/$(basename "$0")
# 判断 jarfile 是否符号链接。-L 判断文件是否为符号链接
while [[ -L "$jarfile" ]]; do
# 如果 jarfile 包含 “init.d” ,则将当前执行文件名赋值给 init_script
[[ "$jarfile" =~ init\.d ]] && init_script=$(basename "$jarfile")
# 获取符号链接所指向的位置
jarfile=$(readlink "$jarfile")
# 进入文件的真实目录
cd "$(dirname "$jarfile")" || exit 1
# 获取 jarfile 的绝对路径
jarfile=$(pwd)/$(basename "$jarfile")
done
# 进入 jarfile 文件所在的目录,并获取文件绝对目录赋值给 jarfolder 。pwd -P 显示当前目录的绝对路径
jarfolder="$( (cd "$(dirname "$jarfile")" && pwd -P) )"
# 回到最初的工作空间
cd "$WORKING_DIR" || exit 1
# Source any config file
# 获取配置文件名称,从这里可以看出来,配置文件名和可执行文件名称除了后缀,其他的应该相同
configfile="$(basename "${jarfile%.*}.conf")"
# Initialize CONF_FOLDER location defaulting to jarfolder
# 如果 CONF_FOLDER 值为空,则通过 jarfolder 定义 CONF_FOLDER 的值
[[ -z "$CONF_FOLDER" ]] && CONF_FOLDER="{{confFolder:${jarfolder}}}"
# shellcheck source=/dev/null
# 如果配置文件存在,source 配置文件
[[ -r "${CONF_FOLDER}/${configfile}" ]] && source "${CONF_FOLDER}/${configfile}"
# Initialize PID/LOG locations if they weren't provided by the config file
#如果配置文件中未定义日志输出目录,pid 文件目录等,则初始化为默认值
[[ -z "$PID_FOLDER" ]] && PID_FOLDER="{{pidFolder:/var/run}}"
[[ -z "$LOG_FOLDER" ]] && LOG_FOLDER="{{logFolder:/var/log}}"
! [[ "$PID_FOLDER" == /* ]] && PID_FOLDER="$(dirname "$jarfile")"/"$PID_FOLDER"
! [[ "$LOG_FOLDER" == /* ]] && LOG_FOLDER="$(dirname "$jarfile")"/"$LOG_FOLDER"
# 是否可执行
! [[ -x "$PID_FOLDER" ]] && PID_FOLDER="/tmp"
! [[ -x "$LOG_FOLDER" ]] && LOG_FOLDER="/tmp"
# Set up defaults
[[ -z "$MODE" ]] && MODE="{{mode:auto}}" # modes are "auto", "service" or "run"
[[ -z "$USE_START_STOP_DAEMON" ]] && USE_START_STOP_DAEMON="{{useStartStopDaemon:true}}"
# Create an identity for log/pid files
# 给日志文件,进程ID文件创建一个唯一的标识
if [[ -z "$identity" ]]; then
if [[ -n "$init_script" ]]; then
identity="${init_script}"
else
identity=$(basename "${jarfile%.*}")_${jarfolder//\//}
fi
fi
# Initialize log file name if not provided by the config file
#日志文件名录
[[ -z "$LOG_FILENAME" ]] && LOG_FILENAME="{{logFilename:${identity}.log}}"
# Initialize stop wait time if not provided by the config file
[[ -z "$STOP_WAIT_TIME" ]] && STOP_WAIT_TIME="{{stopWaitTime:60}}"
# ANSI Colors
echoRed() { echo $'\e[0;31m'"$1"$'\e[0m'; }
echoGreen() { echo $'\e[0;32m'"$1"$'\e[0m'; }
echoYellow() { echo $'\e[0;33m'"$1"$'\e[0m'; }
# Utility functions
# &> /dev/null :把标准错误重新定向到文件 /dev/null
checkPermissions() {
touch "$pid_file" &> /dev/null || { echoRed "Operation not permitted (cannot access pid file)"; return 4; }
touch "$log_file" &> /dev/null || { echoRed "Operation not permitted (cannot access log file)"; return 4; }
}
isRunning() {
ps -p "$1" &> /dev/null
}
await_file() {
end=$(date +%s)
let "end+=10"
while [[ ! -s "$1" ]]
do
now=$(date +%s)
if [[ $now -ge $end ]]; then
break
fi
sleep 1
done
}
# Determine the script mode
action="run"
if [[ "$MODE" == "auto" && -n "$init_script" ]] || [[ "$MODE" == "service" ]]; then
action="$1"
shift
fi
# Build the pid and log filenames
PID_FOLDER="$PID_FOLDER/${identity}"
pid_file="$PID_FOLDER/{{pidFilename:${identity}.pid}}"
log_file="$LOG_FOLDER/$LOG_FILENAME"
# Determine the user to run as if we are root
# shellcheck disable=SC2012
# id -u 获取当前用户 uid 的值 ,$(ls -ld "$jarfile" | awk '{print $3}')获取用户名
[[ $(id -u) == "0" ]] && run_user=$(ls -ld "$jarfile" | awk '{print $3}')
# Find Java
# 查找 Java
# type命令用来显示指定命令的类型,判断给出的指令是内部指令还是外部指令。
# -t:输出“file”、“alias”或者“builtin”,分别表示给定的指令为“外部指令”、“命令别名”或者“内部指令”;
# -p:如果给出的指令为外部指令,则显示其绝对路径;
# -a:在环境变量“PATH”指定的路径中,显示给定指令的信息,包括命令别名。
# "Unable to find Java" 问题的根本所在
if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
javaexe="$JAVA_HOME/bin/java"
elif type -p java > /dev/null 2>&1; then
javaexe=$(type -p java)
elif [[ -x "/usr/bin/java" ]]; then
javaexe="/usr/bin/java"
else
echo "Unable to find Java"
exit 1
fi
# Java 虚拟机参数配置 ,数组
arguments=(-Dsun.misc.URLClassPath.disableJarChecking=true $JAVA_OPTS -jar "$jarfile" $RUN_ARGS "$@")
# Action functions
start() {
if [[ -f "$pid_file" ]]; then
pid=$(cat "$pid_file")
isRunning "$pid" && { echoYellow "Already running [$pid]"; return 0; }
fi
do_start "$@"
}
# ${arguments[@]} : 数组使用@ 或 * 可以获取数组中的所有元素
do_start() {
working_dir=$(dirname "$jarfile")
pushd "$working_dir" > /dev/null
mkdir -p "$PID_FOLDER" &> /dev/null
if [[ -n "$run_user" ]]; then
checkPermissions || return $?
chown "$run_user" "$PID_FOLDER"
chown "$run_user" "$pid_file"
chown "$run_user" "$log_file"
if [ $USE_START_STOP_DAEMON = true ] && type start-stop-daemon > /dev/null 2>&1; then
start-stop-daemon --start --quiet \
--chuid "$run_user" \
--name "$identity" \
--make-pidfile --pidfile "$pid_file" \
--background --no-close \
--startas "$javaexe" \
--chdir "$working_dir" \
-- "${arguments[@]}" \
>> "$log_file" 2>&1
await_file "$pid_file"
else
su -s /bin/sh -c "$javaexe $(printf "\"%s\" " "${arguments[@]}") >> \"$log_file\" 2>&1 & echo \$!" "$run_user" > "$pid_file"
fi
pid=$(cat "$pid_file")
else
checkPermissions || return $?
"$javaexe" "${arguments[@]}" >> "$log_file" 2>&1 &
pid=$!
disown $pid
echo "$pid" > "$pid_file"
fi
[[ -z $pid ]] && { echoRed "Failed to start"; return 1; }
echoGreen "Started [$pid]"
}
stop() {
working_dir=$(dirname "$jarfile")
pushd "$working_dir" > /dev/null
[[ -f $pid_file ]] || { echoYellow "Not running (pidfile not found)"; return 0; }
pid=$(cat "$pid_file")
isRunning "$pid" || { echoYellow "Not running (process ${pid}). Removing stale pid file."; rm -f "$pid_file"; return 0; }
do_stop "$pid" "$pid_file"
}
do_stop() {
kill "$1" &> /dev/null || { echoRed "Unable to kill process $1"; return 1; }
for i in $(seq 1 $STOP_WAIT_TIME); do
isRunning "$1" || { echoGreen "Stopped [$1]"; rm -f "$2"; return 0; }
[[ $i -eq STOP_WAIT_TIME/2 ]] && kill "$1" &> /dev/null
sleep 1
done
echoRed "Unable to kill process $1";
return 1;
}
force_stop() {
[[ -f $pid_file ]] || { echoYellow "Not running (pidfile not found)"; return 0; }
pid=$(cat "$pid_file")
isRunning "$pid" || { echoYellow "Not running (process ${pid}). Removing stale pid file."; rm -f "$pid_file"; return 0; }
do_force_stop "$pid" "$pid_file"
}
do_force_stop() {
kill -9 "$1" &> /dev/null || { echoRed "Unable to kill process $1"; return 1; }
for i in $(seq 1 $STOP_WAIT_TIME); do
isRunning "$1" || { echoGreen "Stopped [$1]"; rm -f "$2"; return 0; }
[[ $i -eq STOP_WAIT_TIME/2 ]] && kill -9 "$1" &> /dev/null
sleep 1
done
echoRed "Unable to kill process $1";
return 1;
}
restart() {
stop && start
}
force_reload() {
working_dir=$(dirname "$jarfile")
pushd "$working_dir" > /dev/null
[[ -f $pid_file ]] || { echoRed "Not running (pidfile not found)"; return 7; }
pid=$(cat "$pid_file")
rm -f "$pid_file"
isRunning "$pid" || { echoRed "Not running (process ${pid} not found)"; return 7; }
do_stop "$pid" "$pid_file"
do_start
}
status() {
working_dir=$(dirname "$jarfile")
pushd "$working_dir" > /dev/null
[[ -f "$pid_file" ]] || { echoRed "Not running"; return 3; }
pid=$(cat "$pid_file")
isRunning "$pid" || { echoRed "Not running (process ${pid} not found)"; return 1; }
echoGreen "Running [$pid]"
return 0
}
run() {
pushd "$(dirname "$jarfile")" > /dev/null
"$javaexe" "${arguments[@]}"
result=$?
popd > /dev/null
return "$result"
}
# Call the appropriate action function
case "$action" in
start)
start "$@"; exit $?;;
stop)
stop "$@"; exit $?;;
force-stop)
force_stop "$@"; exit $?;;
restart)
restart "$@"; exit $?;;
force-reload)
force_reload "$@"; exit $?;;
status)
status "$@"; exit $?;;
run)
run "$@"; exit $?;;
*)
echo "Usage: $0 {start|stop|force-stop|restart|force-reload|status|run}"; exit 1;
esac
exit 0
References:
spring-boot