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
引入插件.
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>assemble</id>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<attach>false</attach>
</configuration>
</plugin>
</plugins>
profile标签配置
<profiles>
<profile>
<id>release</id>
<activation>
<activeByDefault>true</activeByDefault>
<property>
<name>env</name>
<value>release</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<!-- 发布模式使用的maven assembly插件描述文件 -->
<descriptors>
<descriptor>${basedir}/src/main/assembly/release.xml</descriptor>
</descriptors>
<!-- 如果一个应用的包含多个deploy模块,如果使用同样的包名, 如果把它们复制的一个目录中可能会失败,所以包名加了 artifactId以示区分 -->
<finalName>${project.artifactId}-${project.version}</finalName>
<!-- scm 要求 release 模式打出的包放到顶级目录下的target子目录中 -->
<outputDirectory>${project.parent.build.directory}</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
上文的 profile
可以配置多环境(开发,测试,生成..., 这里仅用一个环境release
演示).
可以看到release环境中引入了./src/main/assembly/release.xml
文件. 这个文件就是决定了打包的目录结构和文件.
release.xml
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>dist</id>
<formats>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>.</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>README*</include>
<include>test.csv</include>
</includes>
</fileSet>
<fileSet>
<directory>./src/main/bin</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>**/*</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
<fileSet>
<directory>./src/main/conf</directory>
<outputDirectory>/conf</outputDirectory>
<includes>
<include>**/*</include>
</includes>
<fileMode>0644</fileMode>
</fileSet>
<fileSet>
<directory>./src/main/meta</directory>
<outputDirectory>/meta</outputDirectory>
<includes>
<include>**/*</include>
</includes>
<fileMode>0644</fileMode>
</fileSet>
<fileSet>
<directory>./src/main/resources</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
<fileSet>
<directory>target</directory>
<outputDirectory>logs</outputDirectory>
<excludes>
<exclude>**/*</exclude>
</excludes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<excludes>
<exclude>junit:junit</exclude>
</excludes>
</dependencySet>
</dependencySets>
</assembly>
可单独了解相关专题: [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中的线程任务的.

网友评论