前言
用过dubbo的同学应该知道,dubbo既可以通过war包发布到 tomcat中提供dubbo服务,也可以通过dubbo容器机制直接运行,提供服务提供者。关于dubbo容器机制的探讨,可以参考之前写的一篇文章 《dubbo入门-容器(可执行性jar启动项目)》 。如果使用war包部署到tomcat这种方式,部署dubbo提供者服务 ,其实是相当耗资源的,因为dubbo本来就通过spring容器使用了单应用的功能,所以对于服务提供者,还是推荐使用内置container的方式部署服务。然而,在使用可执行jar包这个方案中,发现修改jar包里面配置文件特别麻烦,除此之外,对于服务消费方的工程,仍要复制war包到tomcat工作目录,再到tomcat执行文件夹中启动tomcat。
最近看了开涛的 《是时候闭环Java应用了》 文章,然后动手给基于dubbo的web服务和service服务做了一个闭环的demo。在这里分享以下。
该demo实现了以下功能:
- 通过maven assembly插件按约定结构打包工程
- 非web的dubbo服务提供者项目使用dubbo内置的spring容器启动
- web的dubbo消费者项目使用内嵌tomcat方式启动
- 支持脚本方式或IDE直接运行的方式启动
实现
DEMO的模块依赖关系如下,其中assembly-service是dubbo的提供者,assembly-web是dubbo的消费者,assembly-common为服务提供者和消费者提供共同依赖。
非web工程
打包后的结构如下:
实现以上打包结构需要借助maven的profiles特性,maven的resources插件选择性拷贝资源文件,assembly插件打包输出。
** 清单:pom **
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>assembly</artifactId>
<groupId>com.github.thinwonton</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>assembly-service</artifactId>
<name>assembly-service</name>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<profiles>
<profile>
<!-- 本地开发环境 -->
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<profiles.active>dev</profiles.active>
</properties>
</profile>
<profile>
<!-- 测试环境 -->
<id>test</id>
<properties>
<profiles.active>test</profiles.active>
</properties>
</profile>
<profile>
<!-- 生产环境 -->
<id>prod</id>
<properties>
<profiles.active>prod</profiles.active>
</properties>
</profile>
</profiles>
<dependencies>
<!-- 忽略依赖 -->
</dependencies>
<build>
<finalName>assembly-service</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<!-- 资源根目录排除各环境的配置,使用单独的资源目录来指定 -->
<excludes>
<exclude>test/**</exclude>
<exclude>prod/**</exclude>
<exclude>dev/**</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources/${profiles.active}</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptor>src/assembly/assembly.xml</descriptor>
<finalName>${project.build.finalName}</finalName>
<!--是否在生成的打包文件的文件名中包含assembly id-->
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>directory</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
** 清单:assembly.xml **
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3
http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>package</id>
<!--打包格式,此处使用的是dir,还可以是zip、rar等-->
<formats>
<format>dir</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<!-- 可执行文件 -->
<fileSet>
<directory>src/bin</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>*.bat</include>
</includes>
<lineEnding>dos</lineEnding>
</fileSet>
<fileSet>
<directory>src/bin</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>*.sh</include>
</includes>
<lineEnding>unix</lineEnding>
<!--文件权限-->
<fileMode>0755</fileMode>
</fileSet>
<!-- classes -->
<fileSet>
<directory>${project.build.directory}/classes</directory>
<outputDirectory>classes</outputDirectory>
</fileSet>
</fileSets>
<!-- 依赖jar包,拷贝到lib目录 -->
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<excludes>
<!--排除当前jar包-->
<exclude>${project.groupId}:${project.artifactId}</exclude>
</excludes>
</dependencySet>
</dependencySets>
</assembly>
** 启动类 **
package com.github.thinwonton.assembly.startup;
public class Bootstrap {
public static void main(String[] args) {
for (String arg : args) {
System.out.println("-----------" + arg);
}
com.alibaba.dubbo.container.Main.main(args);
}
}
启动服务,有两种方式可以运行:(1)可以直接在IDE中右键运行启动服务,需要加入启动参数 -Ddubbo.spring.config=classpath:/spring/applicationContext-config.xml,告知dubbo在哪里找到spring配置文件;(2)使用脚本运行服务,使用java命令,需要指定classpath路径和Main方法类。
** 清单:start.bat **
@echo off
echo ---------------------------
echo starting server...
echo ---------------------------
rem 保存当前路径
set "BIN_DIR_PATH=%cd%"
rem 获取上一级路径
cd..
set "CODE_HOME=%cd%"
rem 恢复路径
cd %BIN_DIR_PATH%
echo %CODE_HOME%
rem 类路径
set CLASSPATH="%CODE_HOME%/classes;%CODE_HOME%/lib/*"
rem 主运行程序类路径
set MAIN_CLASS="com.github.thinwonton.assembly.startup.Bootstrap"
rem dubbo加载spring容器的路径
set DUBBO_SPRING_CONFIG="-Ddubbo.spring.config=classpath:/spring/applicationContext-config.xml"
rem jvm参数
set JAVA_OPTS=-server -Xms128m -Xmx256m -Xss256k -XX:MaxDirectMemorySize=128m
rem java可执行文件位置
set _EXECJAVA="%JAVA_HOME%/bin/java"
%_EXECJAVA% -Dfile.encoding=utf8 %DUBBO_SPRING_CONFIG% %JAVA_OPTS% -classpath %CLASSPATH% %MAIN_CLASS%
** 清单:start.sh**
#!/bin/sh
echo -------------------------------------------
echo start server
echo -------------------------------------------
# bin路径
cd `dirname $0`
BIN_DIR=`pwd`
# 设置项目代码路径
cd ..
export CODE_HOME=`pwd`
#日志路径
export LOG_PATH="$CODE_HOME/logs"
mkdir -p $LOG_PATH
# 设置依赖路径
export CLASSPATH="$CODE_HOME/WEB-INF/classes:$CODE_HOME/WEB-INF/lib/*"
# java可执行文件位置
export _EXECJAVA="$JAVA_HOME/bin/java"
# JVM启动参数
export JAVA_OPTS="-server -Xms128m -Xmx256m -Xss256k-XX:MaxDirectMemorySize=128m"
# 服务端端口、上下文、项目根配置
export SERVER_INFO="-Dserver.port=8090 -Dserver.contextPath= -Dserver.docRelativePath=../"
# 启动类
export MAIN_CLASS="com.github.thinwonton.assembly.startup.Bootstrap"
$_EXECJAVA $JAVA_OPTS -classpath $CLASSPATH $SERVER_INFO $MAIN_CLASS &
tail -f $LOG_PATH/stdout.log
** 清单:stop.sh**
#!/bin/sh
# 设置项目代码路径
cd ..
export CODE_HOME=`pwd`
#日志路径
export LOG_PATH="$CODE_HOME/logs"
mkdir -p $LOG_PATH
# 启动类
export MAIN_CLASS="com.github.thinwonton.assembly.startup.Bootstrap"
echo -------------------------------------------
echo stop server
#所有相关进程
PIDs=`jps -l | grep $MAIN_CLASS | awk '{print $1}'`
#停止进程
if [ -n "$PIDs" ]; then
for PID in $PIDs; do
kill $PID
echo "kill $PID"
done
fi
#等待50秒
for i in 1 10; do
PIDs=`jps -l | grep $MAIN_CLASS | awk '{print $1}'`
if [ ! -n "$PIDs" ]; then
echo "stop server success"
echo -------------------------------------------
break
fi
echo "sleep 5s"
sleep 5
done
#如果等待50秒还没有停止完,直接杀掉
PIDs=`jps -l | grep $MAIN_CLASS | awk '{print $1}'`
if [ -n "$PIDs" ]; then
for PID in $PIDs; do
kill -9 $PID
echo "kill -9 $PID"
done
fi
tail -fn200 $LOG_PATH/stdout.log
web工程
web工程使用嵌入tomcat的方式启动,所以需要引入tomcat的依赖以及在启动类中自行启动tomcat
打包后的结构如下:
清单pom
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>assembly</artifactId>
<groupId>com.github.thinwonton</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<name>assembly-web</name>
<artifactId>assembly-web</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<profiles>
<profile>
<!-- 本地开发环境 -->
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<profiles.active>dev</profiles.active>
</properties>
</profile>
<profile>
<!-- 测试环境 -->
<id>test</id>
<properties>
<profiles.active>test</profiles.active>
</properties>
</profile>
<profile>
<!-- 生产环境 -->
<id>prod</id>
<properties>
<profiles.active>prod</profiles.active>
</properties>
</profile>
</profiles>
<dependencies>
<!-- 忽略依赖 -->
</dependencies>
<build>
<finalName>assembly-web</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<!-- 资源根目录排除各环境的配置,使用单独的资源目录来指定 -->
<excludes>
<exclude>test/**</exclude>
<exclude>prod/**</exclude>
<exclude>dev/**</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources/${profiles.active}</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptor>src/assembly/assembly.xml</descriptor>
<finalName>${project.build.finalName}</finalName>
<!--是否在生成的打包文件的文件名中包含assembly id-->
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>directory</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
清单:assembly.xml
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3
http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>package</id>
<!--打包格式,此处使用的是dir,还可以是zip、rar等-->
<formats>
<format>dir</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<!-- 可执行文件 -->
<fileSet>
<directory>src/bin</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>*.bat</include>
</includes>
<lineEnding>dos</lineEnding>
</fileSet>
<fileSet>
<directory>src/bin</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>*.sh</include>
</includes>
<lineEnding>unix</lineEnding>
<!--文件权限-->
<fileMode>0755</fileMode>
</fileSet>
</fileSets>
</assembly>
** 启动类 TomcatBootstrap.java **。启动参数server.docPath指定web资源的目录,如果没有启动参数server.docPath,会查找工程目录中的src/main/webapp目录,为了实现在IDE中直接run,如果有启动参数,会把该路径传给TOMCAT,让它加载web.xml和网页资源。
建议查看tomcat启动类源码org.apache.catalina.startup.Bootstrap,仿照该启动类写。
package com.github.thinwonton.assembly.startup;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.AprLifecycleListener;
import org.apache.catalina.core.JreMemoryLeakPreventionListener;
import org.apache.catalina.core.StandardServer;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.http11.Http11NioProtocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
/**
* 启动参数说明:<br/>
* server.port:tomcat端口 <br/>
* server.docPath:WEB-INF所在的文件夹路径 <br/>
*
*/
public class TomcatBootstrap {
private static final Logger LOGGER = LoggerFactory.getLogger(TomcatBootstrap.class);
public static void main(String[] args) throws Exception {
//提升性能(https://wiki.apache.org/tomcat/HowTo/FasterStartUp)
System.setProperty("tomcat.util.scan.StandardJarScanFilter.jarsToSkip", "*.jar");
//System.setProperty("securerandom.source","file:/dev/./urandom");
int port = Integer.parseInt(System.getProperty("server.port", "8080")); //端口
String contextPath = System.getProperty("server.contextPath", ""); //上下文路径
String docPath = System.getProperty("server.docPath", getDefaultDocPath()); //
LOGGER.info("server port : {}, context path : {},doc path : {}", port, contextPath, docPath);
Tomcat tomcat = createTomcat(port, contextPath, docPath);
tomcat.start();
LOGGER.info("tomcat started");
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
tomcat.stop();
} catch (LifecycleException e) {
LOGGER.error("stoptomcat error.", e);
}
}
});
tomcat.getServer().await();
}
private static String getDefaultDocPath() {
File projectDir = getProjectDir();
String path = new StringBuffer().append("src").append(File.separator).append("main").append(File
.separator).append("webapp").toString(); // "src/main/webapp"
return new File(projectDir, path).getPath();
}
private static File getProjectDir() {
File classpathDir = getClasspathDir();
return classpathDir.getParentFile().getParentFile();
}
private static File getClasspathDir() {
return new File(Thread.currentThread().getContextClassLoader().getResource(".").getFile());
}
private static Tomcat createTomcat(int port, String contextPath, String docPath) throws Exception {
Tomcat tomcat = new Tomcat();
String tmpdir = System.getProperty("java.io.tmpdir");
tomcat.setBaseDir(tmpdir);
tomcat.getHost().setAppBase(tmpdir);
tomcat.getHost().setAutoDeploy(false);
tomcat.getHost().setDeployOnStartup(false);
tomcat.getEngine().setBackgroundProcessorDelay(-1);
tomcat.setConnector(newNioConnector());
tomcat.getConnector().setPort(port);
tomcat.getService().addConnector(tomcat.getConnector());
Context context = tomcat.addWebapp(contextPath, docPath);
StandardServer server = (StandardServer) tomcat.getServer();
//APR library loader. Documentation at /docs/apr.html
server.addLifecycleListener(new AprLifecycleListener());
//Prevent memory leaks due to use of particularjava/javax APIs
server.addLifecycleListener(new JreMemoryLeakPreventionListener());
return tomcat;
}
//在这里调整参数优化
private static Connector newNioConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
return connector;
}
}
清单:start.bat
@echo off
rem 保存当前路径
set "BIN_DIR_PATH=%cd%"
rem 获取上一级路径
cd..
set "CODE_HOME=%cd%"
rem 恢复路径
cd %BIN_DIR_PATH%
rem 日志路径
set LOG_PATH="%CODE_HOME%/logs"
echo %CODE_HOME%
rem 设置依赖路径
set CLASSPATH="%CODE_HOME%/WEB-INF/classes;%CODE_HOME%/WEB-INF/lib/*"
rem java可执行文件位置
set _EXECJAVA="%JAVA_HOME%/bin/java"
rem JVM启动参数
set JAVA_OPTS=-server -Xms128m -Xmx256m -Xss256k -XX:MaxDirectMemorySize=128m
rem 服务端端口、上下文、项目根配置
set SERVER_INFO=-Dserver.port=8090 -Dserver.contextPath= -Dserver.docPath=%CODE_HOME%
rem 启动类
set MAIN_CLASS="com.github.thinwonton.assembly.startup.TomcatBootstrap"
%_EXECJAVA% -Dfile.encoding=utf8 %JAVA_OPTS% -classpath %CLASSPATH% %SERVER_INFO% %MAIN_CLASS%
清单:start.sh
#!/bin/sh
echo -------------------------------------------
echo start server
echo -------------------------------------------
# bin路径
cd `dirname $0`
BIN_DIR=`pwd`
# 设置项目代码路径
cd ..
export CODE_HOME=`pwd`
#日志路径
export LOG_PATH="$CODE_HOME/logs"
mkdir -p $LOG_PATH
# 设置依赖路径
export CLASSPATH="$CODE_HOME/WEB-INF/classes:$CODE_HOME/WEB-INF/lib/*"
# java可执行文件位置
export _EXECJAVA="$JAVA_HOME/bin/java"
# JVM启动参数
export JAVA_OPTS="-server -Xms128m -Xmx256m -Xss256k-XX:MaxDirectMemorySize=128m"
# 服务端端口、上下文、项目根配置
export SERVER_INFO="-Dserver.port=8090 -Dserver.contextPath= -Dserver.docRelativePath=../"
# 启动类
export MAIN_CLASS="com.github.thinwonton.assembly.startup.TomcatBootstrap"
$_EXECJAVA $JAVA_OPTS -classpath $CLASSPATH $SERVER_INFO $MAIN_CLASS &
tail -f $LOG_PATH/stdout.log
清单:stop.sh
#!/bin/sh
# 设置项目代码路径
cd ..
export CODE_HOME=`pwd`
#日志路径
export LOG_PATH="$CODE_HOME/logs"
mkdir -p $LOG_PATH
# 启动类
export MAIN_CLASS=com.github.thinwonton.assembly.startup.TomcatBootstrap
echo -------------------------------------------
echo stop server
echo -------------------------------------------
#所有相关进程
PIDs=`jps -l | grep $MAIN_CLASS | awk '{print $1}'`
#停止进程
if [ -n "$PIDs" ]; then
for PID in $PIDs; do
kill $PID
echo "kill $PID"
done
fi
#等待50秒
for i in 1 10; do
PIDs=`jps -l | grep $MAIN_CLASS | awk '{print $1}'`
if [ ! -n "$PIDs" ]; then
echo "stop server success"
echo -------------------------------------------
break
fi
echo "sleep 5s"
sleep 5
done
#如果等待50秒还没有停止完,直接杀掉
PIDs=`jps -l | grep $MAIN_CLASS | awk '{print $1}'`
if [ -n "$PIDs" ]; then
for PID in $PIDs; do
kill -9 $PID
echo "kill -9 $PID"
done
fi
tail -fn200 $LOG_PATH/stdout.log
相关代码
http://git.oschina.net/thinwonton/dubbo-assembly
参考资料
http://jinnianshilongnian.iteye.com/blog/2317830