1、背景
任何从main方法启动的工程,比如:spring-java,spring-boot,sparing-web-jetty这种直接从main方法启动的,都可以使用如下方式打包
这种工程完全可以通过
maven-compiler-plugin ==> 指定jdk版本
maven-jar-plugin ==> 自定义自己工程,剔除一些配置信息
maven-assembly-plugin ==> 将自己工程的jar和其他依赖的jar组装起来,打包成bin、 config、 lib、 version的标准格式
2、原理
只要是可以从main方法启动,就可以使用如下方式来启动
java $JAVA-OPTS -classpath $CLASS_PATH $MAIN_CLASS 参数
其中
JAVA-OPTS就是一些jvm启动的一些参数,比如:堆、JMX、GC等
CLASS_PATH 就是将lib(包含本工程的jar)下的jar包通过 “:” 拼接起来
MAIN_CLASS 是需要运行的主类(main方法)
这里的要点是:assmebly插件能够把依赖的jar包自动的放到了lib中,需要在start.sh脚本中把它们添加到-classpath 就解决了jar依赖问题。
3、打包主要文件
-
1、pom.xml中通用的build
<build> <!-- 本工程的jar包名字,默认就是下面这个 但是assembly插件提取依赖时,即使下面设置为aaa,抽取dependencySets时一定是${project.artifactId}-${project.version} --> <finalName>${project.artifactId}-${project.version}</finalName> <plugins> <!-- java编译插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <!--设置jar插件,将需要修改的配置文件从jar中删除--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <!-- 对于文件,直接用文件名即可 对于文件夹: 如果要删除文件夹下的某个文件/mybatis/XXXMapper.xml 如果要删除整个文件夹db/ 注意:/不能少,否则会被认为是文件,而不是文件夹 注意:不要写成db/* 这样还是会有一个db空文件夹 --> <excludes> <exclude>db/db.properties</exclude> <exclude>quartz/job.properties</exclude> <exclude>caiwutong.properties</exclude> <exclude>logback.xml</exclude> <exclude>notice.txt</exclude> </excludes> <!--指定主类,一般都不需要指定,只需要在Java命令启动的时候指定即可--> <!--除非是在window下双击执行--> <!--<archive>--> <!--<manifest>--> <!--<addDefaultImplementationEntries>true</addDefaultImplementationEntries>--> <!--<addClasspath>true</addClasspath>--> <!--<mainClass>com.surfilter.pscms.timer.Timer</mainClass>--> <!--</manifest>--> <!--<manifestEntries>--> <!--<Class-Path>.</Class-Path>--> <!--</manifestEntries>--> <!--</archive>--> </configuration> </plugin> <!-- 配置assembly插件,默认的版本是pom4.0里面的2.2-beta-5 但是这个版本对dependencyManagement的传递依赖在dependencySets抽取依赖时有bug 总是使用dependencyManagement中的依赖,而不是额外指定的版本 这个bug在2.3以上的版本修复了,一般使用2.6,如下 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.6</version> <configuration> <!-- 这个是最终打包出来的压缩文件名字,${maven.build.timestamp}是加上一个时间戳 --> <finalName>${project.finalName}</finalName> <descriptor>assembly/assembly.xml</descriptor> <!--最终zip包名是否拼接assemblyID名称,默认是加上的--> <!--加上后就变成: spring-mvcweb-jetty-1.0-assembly.zip--> <appendAssemblyId>false</appendAssemblyId> </configuration> <executions> <execution> <phase>package</phase> <!-- 绑定到package生命周期阶段上 --> <goals> <goal>single</goal> <!-- 只运行一次 --> </goals> </execution> </executions> </plugin> </plugins> </build>
-
2、assembly.xml文件
<!-- - 1、任何从main方法启动的工程,都可以使用assembly进行打包,形成: bin config lib version 的标准格式 在java -classpath 中拼接上config,这样就可以直接使用 classpath:/db/db.properties xxx.class.getResourceAsStream("/db/db.properties") - 2、路径是相对于工程根目录的,直接写即可。比如:bin 和 src/main/resources - 3、依赖打包,默认使用pom4.0的2.2-beta-5版本assembly插件 但是此版本对传递依赖有bug,比如如果dependencyManagement里面包含了某依赖,总是会使用dependencyManagement中的依赖,没法额外指定 同时对于下面的文件权限无效,即使设置了0755,还是会设置为777 这些问题在2.3及以后版本修复了,一般打包都是用2.6版本 --> <assembly> <id>assembly</id> <formats> <format>zip</format> </formats> <!--如果为false,那么解压后就是bin 、 config、 lib、 version。外面没有工程名称--> <includeBaseDirectory>true</includeBaseDirectory> <fileSets> <!--bin配置--> <fileSet> <directory>bin</directory> <outputDirectory>bin</outputDirectory> <fileMode>0755</fileMode> </fileSet> <!--sbin配置--> <fileSet> <directory>sbin</directory> <outputDirectory>sbin</outputDirectory> <fileMode>0755</fileMode> </fileSet> <!--config设置--> <fileSet> <directory>src/main/resources</directory> <outputDirectory>config</outputDirectory> <!-- 对于文件,直接用文件名即可 对于文件夹: 如果要删除文件夹下的某个文件mybatis/XXXMapper.xml 如果要删除整个文件夹mybatis/ 注意:/不能少,否则会被认为是文件,而不是文件夹 注意:不要写成mybatis/* 这样还是会有一个mybatis空文件夹 --> <excludes> <exclude>db/*.xml</exclude> <exclude>db/*.sql</exclude> <exclude>mybatis/</exclude> <exclude>quartz/*.xml</exclude> <exclude>spring/</exclude> <exclude>notice.txt</exclude> </excludes> </fileSet> <!--version设置--> <fileSet> <directory>src/main/resources</directory> <outputDirectory>version</outputDirectory> <includes> <include>notice.txt</include> </includes> </fileSet> <!-- 抽取webapp,包括html和WEN-INF以及下面的web.xml (这些是jetty容器需要设置的)--> <fileSet> <directory>src/main/webapp</directory> <outputDirectory>webapp</outputDirectory> </fileSet> </fileSets> <!--lib配置,把工程放在根目录下,其他依赖包放在lib下--> <dependencySets> <dependencySet> <outputDirectory>.</outputDirectory> <includes> <include>:*${project.artifactId}*:</include> </includes> </dependencySet> <dependencySet> <outputDirectory>lib</outputDirectory> <excludes> <exclude>:*${project.artifactId}*:</exclude> </excludes> </dependencySet> </dependencySets> </assembly>
-
3、启动脚本(通过DEPLOY_DIR)
#!/bin/bash # 获取关键路径:BIN_DIR 和 DEPLOY_DIR cd `dirname $0` BIN_DIR=`pwd` cd .. DEPLOY_DIR=`pwd` # 创建logs文件夹 mkdir -p $DEPLOY_DIR/logs STDOUT_FILE=$DEPLOY_DIR/logs/stdout.log # main类,程序入口 并且 从conf.properties中获取程序名和端口 MAIN_CLASS=com.yuanmei.caiwutong.JettyServiceStarter APPLICATION_NAME="JettyServiceStarter" APPLICATION_PORT=`cat config/caiwutong.properties | grep 'application.port=' | cut -d '=' -f2- | tr -d '\r'` # 先判断此应用程序是否已经启动了以及端口是否被占用 PIDS=`jps | grep $APPLICATION_NAME | awk '{print $1}'` if [ -n "$PIDS" ]; then echo "ERROR: The $APPLICATION_NAME already started!" echo "PID: $PIDS" exit 1 fi if [ -n "$APPLICATION_PORT" ]; then SERVER_PORT_COUNT=`netstat -tln | grep $APPLICATION_PORT | wc -l` if [ $SERVER_PORT_COUNT -gt 0 ]; then echo "ERROR: The $APPLICATION_NAME port $APPLICATION_PORT already used!" exit 1 fi fi # 将config文件夹和lib下jar拼接到--classpath中。 # config 放在前面,是因为如下方式顺序查找--classpath,找到就返回 # classpath:/db/db.properties # xxx.class.getResourceAsStream("/db/db.properties") # 将config拼接到--classpath中,那么logger使用class.getResource("logback.xml")能够找到 # tr 表示将前面的那个值替换为后面的那个值 CLASS_PATH=$DEPLOY_DIR/config CLASS_PATH=$CLASS_PATH:`ls $DEPLOY_DIR/lib/*.jar | tr "\n" ":"` CLASS_PATH=$CLASS_PATH:`ls $DEPLOY_DIR/*.jar` # jvm参数 JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true " # 开启debug JAVA_DEBUG_OPTS="" if [ "$1" = "debug" ]; then JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n " fi # 开启远程jmx,也就是可以使用jconsole进行连接 JAVA_JMX_OPTS="" if [ "$1" = "jmx" ]; then JAVA_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false " fi #内存以及gc的配置 JAVA_MEM_OPTS=" -server -Xmx2g -Xms2g -Xmn256m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 " # 启动应用程序 echo -e "Starting the $APPLICATION_NAME ...\c" nohup java $JAVA_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS $JAVA_MEM_OPTS -classpath $CLASS_PATH $MAIN_CLASS $APPLICATION_PORT $1> $STDOUT_FILE 2>&1 & # 循环判断是否有对应的pid(也就是是否启动成功) while true; do sleep 1 echo -e ".\c" PIDS=`jps | grep $APPLICATION_NAME | awk '{print $1}'` if [ -n PIDS ]; then break fi done echo "OK!" echo "PID: $PIDS" echo "STDOUT: $STDOUT_FILE" # 每次调用启动脚本都把之前的监控脚本干掉,然后再启动 monitorPidArr=($(ps aux | grep $BIN_DIR/monitorRestart.sh | awk '{print $2}')) if [ ${#monitorPidArr[*]} -gt 1 ]; then ps -f | grep monitorRestart.sh | grep -v "grep" | awk '{print $2}' | xargs kill -9 fi nohup sh $BIN_DIR/monitorRestart.sh $1> /dev/null 2>&1 &
-
4、停止脚本(通过DEPLOY_DIR)
#!/bin/bash # 先获取关键位置BIN_DIR和DEPLOY_DIR cd `dirname $0` BIN_DIR=`pwd` cd .. DEPLOY_DIR=`pwd` # 给定应用名字(和start.sh配置一样) APPLICATION_NAME="JettyServiceStarter" # 通过应用名字来获取pid,最后kill(下面是通用的,不用改了,只要配置上面的APPLICATION_NAME即可) PIDS=`jps | grep $APPLICATION_NAME | awk '{print $1}'` if [ -z "$PIDS" ]; then echo "ERROR: The $APPLICATION_NAME does not started!" exit 1 fi echo -e "Stopping the $APPLICATION_NAME ...\c" while true; do echo -e ".\c" sleep 1 PIDS=`jps | grep $APPLICATION_NAME | awk '{print $1}'` if [ -n "$PIDS" ]; then for PID in $PIDS ; do kill -9 $PID > /dev/null 2>&1 done else break; fi done echo "OK!" echo "PID: $PIDS"
4、总结
-
1、只要能够从java main方法启动的工程,比如:spring-java,spring-boot,sparing-web-jetty这种直接从main方法启动的,都可以使用上面的打包方式
-
2、如果是war这种基于Tomcat的运行方式,那么只需要利用Tomcat插件进行打包即可。