Assembly简介:
maven打包工具之一,可以对非WEB项目如控制台程序进行自定义打包,通常打包要么连同依赖jar包一起打进jar包,
或着配置文件打入jar包,造成虽然可配,但是仍然无法自动配置,如果不把依赖包打入jar,那么问题又来了,依赖包需要
手动添加到classpath下,这依然是个麻烦的工作,比较理想的打包就是利用assembly这个插件按照自定义来打包,zip,gz等
格式均可,依赖包连同程序包xxx-0.0.1-SNAPSHOT.jar打入lib目录,把配置文件输出到conf目录,同时生成一个bin目录,
bin目录里面放置一个启动应用程序的脚本,这样,打完包之后,直接可以将包拷贝到服务器,解压,然后进入bin目录,
执行脚本启动程序就可以了,避免每次都需要手动拷贝依赖jar,造成部署困难。
工程介绍:
为了测试Assembly插件打包,我假设有这样的一个场景,利用spring构建一个非web控制台程序,主函数通过App.java
来启动,通过jetty框架构建一个Server,监听8080端口,请求地址为http://host:8080/business?name=xxx,处理这个请求的
Servlet会向内存的一个静态list中写入参数name的值,另外启动一个线程,来不断向日志中输出list中保存的name的大小,以
检验前面的请求是否正确处理了。
工程结构如下:
在本地运行时,没有请求时,打印的日志:list size : 0
两次请求发送之后,打印的结果如下,list size 2。和预期的一致。
到此项目运行正常,后续可以打包了。我们只需验证打包部署之后,运行结果和在eclipse中运行结果一样即可。
打包之后的生成的jar:
拷贝到服务器上,解压之后文件结构:
启动脚本,执行App.java中的main函数。
同样请求两次之后的结果:
这样,通过打包运行之后和在Eclipse里面运行结果一致,证明打包没有什么问题。到此整个过程基本结束,下面
介绍如何一步一步通过maven+Assembly打包控制台应用程序。
一、新建maven工程
普通的工程,不需要web工程,如图:
二、配置pom.xml
<properties> <mainClass>com.lenovo.webapp.App</mainClass> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring-version>4.2.0.RELEASE</spring-version> </properties> <dependencies> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>test-jetty-servlet</artifactId> <version>8.1.0.RC5</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.5</version> <configuration> <excludes> <exclude>/*.xml</exclude> <exclude>/*.properties</exclude> </excludes> <archive> <manifest> <addClasspath>false</addClasspath> <mainClass>${mainClass}</mainClass> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>com.google.code.maven-replacer-plugin</groupId> <artifactId>replacer</artifactId> <version>1.5.3</version> <executions> <execution> <phase>prepare-package</phase> <goals> <goal>replace</goal> </goals> </execution> </executions> <configuration> <file>assemblise/bin/app.sh</file> <replacements> <replacement> <token>#MAIN_CLASS#</token> <value>${mainClass}</value> </replacement> </replacements> <outputFile>target/bin/app.sh</outputFile> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.4</version> <configuration> <descriptors> <descriptor>assembly.xml</descriptor> </descriptors> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
两点说明:
1、主要的配置在后面的build节点中的maven-assembly-plugin,这里需要指定一个assembly.xml的配置文件,用来自定义打包
目录结构和相关文件如何归档。
2、这里面还配置了一个replacer,这里用来替换掉打包assemblise/bin/app.sh中启动的java主类,这样脚本不用每次改动,只需指
定启动的主类,也是相对来说比较理想化的一个配置。
三、配置assembly.xml
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 "> <id>bin</id> <formats> <format>zip</format> </formats> <fileSets> <fileSet> <directory>target/${artifactId}</directory> <outputDirectory>/</outputDirectory> </fileSet> <fileSet> <directory>assemblise</directory> <outputDirectory>/</outputDirectory> <excludes><exclude>bin/app.sh</exclude></excludes> </fileSet> <fileSet> <directory>target/bin</directory> <outputDirectory>/bin</outputDirectory> </fileSet> <fileSet> <directory>src/main/resources</directory> <outputDirectory>/conf</outputDirectory> </fileSet> </fileSets> <dependencySets> <dependencySet> <useProjectArtifact>true</useProjectArtifact> <outputDirectory>lib</outputDirectory> <scope>runtime</scope> </dependencySet> </dependencySets> </assembly>
这里配置比较繁琐,大致意思是配置文件打包到哪(/conf),和依赖包运行时环境(/lib),启动脚本替换之后,放入什么目录(/bin)。
然后就是指定打包格式,这里指定的是zip格式。
四、Java代码
App.java(程序入口)
package com.lenovo.webapp; import org.apache.log4j.Logger; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.lenovo.servlet.BusinessServlet; import com.lenovo.utils.WebContext; public class App { private static final Logger log = Logger.getLogger(App.class); private static ApplicationContext context; private static final int PORT = 8080; public static void main( String[] args ) { try { context = new ClassPathXmlApplicationContext(new String[]{"classpath:applicationContext.xml"}); WebContext.setContext(context); Server server = new Server(PORT); ServletContextHandler handler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS); handler.setContextPath("/"); handler.addServlet(new ServletHolder(new BusinessServlet()), "/business"); server.setHandler(handler); server.start(); server.join(); log.info("app start successfully!"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
BusinessServlet.java(处理请求http://hostname:port/business?name=xxx)
package com.lenovo.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.lenovo.service.BusinessService; import com.lenovo.utils.WebContext; public class BusinessServlet extends HttpServlet { private static BusinessService businessService; static{ businessService = (BusinessService) WebContext.getBean("businessService"); } /** * */ private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String name = req.getParameter("name"); req.setCharacterEncoding("UTF-8"); res.setContentType("text/html"); res.setCharacterEncoding("UTF-8"); res.setStatus(HttpServletResponse.SC_OK); res.getWriter().println(businessService.getName(name)); } }
BusinessService.java(保存name到list)
package com.lenovo.service; import org.springframework.stereotype.Service; import com.lenovo.utils.Constants; @Service("businessService") public class BusinessService { public String getName(String name){ Constants.list.add(name); return "hello,"+name; } }
OutputThreadService.java(向日志中输出list size)
package com.lenovo.service; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Service; import com.lenovo.utils.Constants; @Service("outputService") public class OutputThreadService implements InitializingBean { private static final Logger log = Logger.getLogger(OutputThreadService.class); public void afterPropertiesSet() throws Exception { handler(); } private void handler() { new Thread(new Runnable() { public void run() { //output while(true){ try{ log.info("list size: "+Constants.list.size()); TimeUnit.SECONDS.sleep(10L); }catch (Exception e) { log.error("run service with error:"+e.getMessage()); } } } }).start(); } }
WebContext.java
package com.lenovo.utils; import org.apache.log4j.Logger; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class WebContext implements ApplicationContextAware { private static final Logger log = Logger.getLogger(WebContext.class); private static ApplicationContext context; public void setApplicationContext(ApplicationContext context) throws BeansException { setContext(context); log.info("webcontext init ok!"); } public static void setContext(ApplicationContext context) { WebContext.context = context; } public static Object getBean(String name){ if(context==null){ log.error("context init error!"); return null; } return context.getBean(name); } }
Constants.java
package com.lenovo.utils; import java.util.ArrayList; import java.util.List; public class Constants { public static List<String> list = new ArrayList<String>(); }applicationContext.xml(spring配置文件,启用注解,设置扫描包)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd"> <context:annotation-config/> <context:component-scan base-package="com.lenovo.service"/> <bean id="webContext" class="com.lenovo.utils.WebContext"></bean> </beans>
五、准备启动脚本
打包之后程序如何组织?classpath如何设置?配置文件怎么指定?重复启动脚本问题如何解决?设置运行内存,运行日志
打印到哪里等问题都集中在app.sh这个脚本上,应该说是一个程序组织者,把各个单一的个体组织成一个运行程序,方便
启动应用程序。
#!/bin/sh cd `dirname $0` cd .. APP_HOME=`pwd` #echo usage usage(){ echo "Usage: ${0##*/} {start|restart|stop}" exit 1 } #if app is running stop it first stop(){ PID=`ps -ef |grep java|grep "$APP_HOME" |awk '{print $2}' ` echo "PID: $PID" if [ -n "$PID" ] then echo "App is running,PID:$PID" kill -9 "$PID" 2>/dev/null sleep 1 fi } [ $# -gt 0 ] || usage stop LOG_DIR=$APP_HOME/logs CONF_DIR=$APP_HOME/conf LIB_DIR=$APP_HOME/lib CLASSPATH=$CONF_DIR for i in "$LIB_DIR"/*.jar; do CLASSPATH="$CLASSPATH":"$i" done echo $CLASSPATH if [ -z "$APP_PID" ] then APP_PID="$APP_HOME/app.pid" fi echo "starting...\c" JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPV4Stack=true -Dlog.dir=$LOG_DIR " JAVA_MEM_OPTS=" -server -Xms1g -Xmx1g -XX:+PrintGCDetails -XX:+PrintGCTimeStamps " nohup java $JAVA_OPTS $JAVA_MEM_OPTS -classpath $CLASSPATH #MAIN_CLASS# > startapp.log & sleep 1 APP_PID=`ps -ef |grep java |grep "$APP_HOME" |awk '{print $2}'` echo $APP_PID echo "ok"
六、利用assembly插件打包
在Eclipse中选中pom.xml直接Run As->Maven install。最简单的方式,当然也可以命令行执行一条命令(maven clean
package),前提是本机已经安装了maven。
打包之后和前面的工程介绍里面就接上了,这里不再演示。