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。
打包之后和前面的工程介绍里面就接上了,这里不再演示。
本文介绍如何使用Maven Assembly插件为非Web控制台应用程序定制打包流程,包括配置pom.xml、assembly.xml,创建启动脚本及利用Assembly插件进行打包。
274

被折叠的 条评论
为什么被折叠?



