Java工程打成tar包的博文很多, 但是很多都只是给了简单的配置文件demo, 但是作为一项工程, 简单的配置是不能成事的. 大部分开源软件都是tar.gz格式, 很明显非常的工程化, 规范化.

所以仅仅一些随笔性质的博文还是不能够让我掌握, 得以在生产环境中使用.

于是, 自己资料加线上测试探索, 基本掌握一点入门使用.

您将了解到:

打tar包的基本配置

启动/停止脚本

shell启动java程序classpath问题

shell结束java程序时, java自己在结束进程之前如何做些后续处理工作

分环境打包和assembly插件基本使用

目录结构

|- javapro
|- bin
|- conf
|- logs
|- lib
|- README.MD
...

lib: 自己的工程打成的jar包以及依赖jar包

conf:配置文件

logs: 日志

bin: 脚本, 如启动,停止脚本

打包配置

通常流行使用assembly插件打包, 包括tar包.

这一步麻烦的是配置, 所以需要整理好模板, 以备不时之需.

pom.xml

引入插件.

org.apache.maven.plugins
maven-assembly-plugin
2.2.1
assemble
single
package
false
false
profile标签配置
release
true
env
release
maven-assembly-plugin
${basedir}/src/main/assembly/release.xml
${project.artifactId}-${project.version}
${project.parent.build.directory}

上文的 profile可以配置多环境(开发,测试,生成..., 这里仅用一个环境release演示).

可以看到release环境中引入了./src/main/assembly/release.xml文件. 这个文件就是决定了打包的目录结构和文件.

release.xml
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
dist
tar.gz
false
.
/
README*
test.csv
./src/main/bin
bin
**/*
0755
./src/main/conf
/conf
**/*
0644
./src/main/meta
/meta
**/*
0644
./src/main/resources
/
**/*
target
logs
**/*
lib
junit:junit

可单独了解相关专题: [java分环境打包部署][assembly插件打包]

到此, 最简单的基本配置完成了, 可以达成tar.gz包了. 但是问题是, 部署的时候, 那什么来启动java程序. 请看下文.

启动/关闭脚本

我的shell脚本处于幼儿园水平, 以下脚本都是复制粘贴修改的.

启动脚本:

startup.sh
#!/bin/bash
case "`uname`" in
Linux)
bin_abs_path=$(readlink -f $(dirname $0))
;;
*)
bin_abs_path=`cd $(dirname $0); pwd`
;;
esac
echo "脚本位置: $bin_abs_path"
base=${bin_abs_path}/..
#base=$(dirname $(cd `dirname $0`;pwd))
echo "base path: $base"
echo "cd to $base"
cd $base
export LANG=en_US.UTF-8
export BASE=$base
echo "cd to: $base"
#can't run repeatedly
if [ -f $base/bin/addr.pid ] ; then
echo "found bin/addr.pid , Please run stop.sh first ,then startup.sh" 2>&2
exit 1
fi
## set java path
if [ -z "$JAVA" ] ; then
JAVA=$(which java)
fi
str=`file $JAVA_HOME/bin/java | grep 64-bit`
if [ -n "$str" ]; then
JAVA_OPTS="-server -Xms1024m -Xmx1536m -Xmn256m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -Xss256k -XX:-UseAdaptiveSizePolicy -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError"
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m "
fi
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
for i in $base/lib/*;
do CLASSPATH=$i:"$CLASSPATH";
done
#$JAVA $JAVA_OPTS -classpath .:$CLASSPATH com.jfai.addr.StartUp 1>>$base/bin/nohup.out 2>&1 &
$JAVA $JAVA_OPTS -classpath .:$CLASSPATH com.jfai.addr.StartUp 1>$base/bin/nohup.out 2>&1 &
echo $! > $base/bin/addr.pid
echo "Process addr is running..., pid=$!"

停止脚本:

stop.sh
#!/bin/bash
cygwin=false;
case "`uname`" in
CYGWIN*)
cygwin=true
;;
esac
get_pid() {
STR=$1
PID=$2
if $cygwin; then
JAVA_CMD="$JAVA_HOME\bin\java"
JAVA_CMD=`cygpath --path --unix $JAVA_CMD`
JAVA_PID=`ps |grep $JAVA_CMD |awk '{print $1}'`
else
if [ ! -z "$PID" ]; then
JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'`
else
JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep -v grep|awk '{print $2}'`
fi
fi
echo $JAVA_PID;
}
base=`dirname $0`/..
pidfile=$base/bin/addr.pid
if [ ! -f "$pidfile" ];then
echo "addr.pid is not found, addr is not running? exit"
exit
fi
pid=`cat $pidfile`
if [ "$pid" == "" ] ; then
pid=`get_pid "addr"`
fi
echo -e "`hostname`: stopping addr, pid=$pid ... "
kill $pid
LOOPS=0
while (true);
do
gpid=`get_pid "addr" "$pid"`
if [ "$gpid" == "" ] ; then
echo "Oook! cost:$LOOPS"
`rm $pidfile`
break;
fi
let LOOPS=LOOPS+1
sleep 1
done

附录

classpath问题

一开始, 笔者在用startup.sh启动时, 碰到:

若在~/bin/目录下执行sh startup.sh时, classpath就是: ~/bin/, 这回导致: new File("conf/xx.properties")(相对路径)找不到文件的, 因为它会将相对路径转成: ~/bin/conf/xx.properties. 这显然不是我想要的.

若在~/路径下执行: sh bin/startup.sh, 能够成功转成: ~/conf/xx.properties

('~'代表省略的父路径)

根据问题详情页的解答, 解决了这个问题.

关键是要在脚本的一开始就cd 到项目根路径

停止脚本杀死进程时, 如何让java进程在退出之前做些事情?

Runtime.getRuntime().addShutdownHook(...)可以搞定.

请看演示代码:

public static void main(String[] args) {
//Test:
System.out.println("启动 ...");
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
log.info("执行shutdown...");
//这里演示shutdown线程可以读取到主线程改变的变量
System.out.println("主线程循环了"+count+"次");
running = false;
}
});
while (running) {
System.out.println("Not shutdown, running ...");
count++;
}
//kill pid后, 主线程会停止
System.out.println("退出循环体, 程序将退出");
//end test

效果

注意: stop.sh中kill命令不能加-9参数.

加上-9之后, 是不会执行到添加到shutdownhook中的线程任务的.

错误写法