Jetty是一个用 Java 实现、开源、基于标准的,并且具有丰富功能的 Http 服务器和 Web 容器。Jetty中应用最广泛的一项功能就是可以作为嵌入式Web容器。
本文将着重介绍如何配置使用Jetty的嵌入式Web容器功能,关于Jetty的基本配置和功能请参考http://www.ibm.com/developerworks/cn/web/wa-lo-jetty/
一、开发阶段
1、使用maven启动Jetty
我们修改了源码的时候eclipse会自动编译,Jetty Maven Plugin插件发现编译文件有变化后会自动更新到jetty容器中,非常方便我们进行开发。
首先定义Jetty的版本属性
1
2
3
|
<
properties
>
<
jetty.version
>8.1.9.v20130131
jetty.version
>
properties
>
|
然后引入Jetty依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<
dependency
>
<
groupId
>org.eclipse.jetty.aggregate
groupId
>
<
artifactId
>jetty-webapp
artifactId
>
<
version
>${jetty.version}
version
>
<
scope
>test
scope
>
dependency
>
<
dependency
>
<
groupId
>org.eclipse.jetty
groupId
>
<
artifactId
>jetty-jsp
artifactId
>
<
version
>${jetty.version}
version
>
<
scope
>test
scope
>
dependency
>
|
配置Jetty Maven Plugin插件,示例如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<
plugin
>
<
groupId
>org.mortbay.jetty
groupId
>
<
artifactId
>jetty-maven-plugin
artifactId
>
<
version
>${jetty.version}
version
>
<
configuration
>
<
systemProperties
>
<
systemProperty
>
<
name
>spring.profiles.active
name
>
<
value
>development
value
>
systemProperty
>
systemProperties
>
<
useTestClasspath
>true
useTestClasspath
>
<
webAppConfig
>
<
contextPath
>/${project.artifactId}
contextPath
>
webAppConfig
>
configuration
>
plugin
>
|
该配置运行jetty并指定spring的profile为development,同时设定web应用的上下文地址与应用本身的artifactId一致。
执行如下命令启动Jetty,即可通过http://localhost:8080/${project.artifactId}访问Web应用。
1
|
mvn jetty:run
|
Jetty Maven Plugin插件支持多个maven goals,最常用的就是run,下面的参数支持大部分的goals
(1)配置Jetty容器(支持所有goals)
比如org.mortbay.jetty.NCSARequestLog就是一个NCSA格式((美国)国家超级计算技术应用中心 (NCSA) 公用格式,是常用的标准日志格式)的实现。
(2)配置Web应用程序(不支持run-forked、stop两个goals)
run goals将会启动Jetty并运行应用程序,不需要应用程序编译成war包。另外run还支持webapp节点的其它属性:
Jetty Maven Plugin插件支持的其它goals简介如下(详见http://wiki.eclipse.org/Jetty/Feature/Jetty_Maven_Plugin):
2、在java中启动Jetty
SpringSide4中封装了Jetty的操作提供了工具类JettyFactory ,让我们可以很简单的启动Jetty容器,JettyFactory代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
/**
* Copyright (c) 2005-2012 springside.org.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
*/
package
org.springside.modules.test.jetty;
import
java.util.List;
import
org.apache.commons.lang3.StringUtils;
import
org.eclipse.jetty.server.Connector;
import
org.eclipse.jetty.server.Server;
import
org.eclipse.jetty.server.nio.SelectChannelConnector;
import
org.eclipse.jetty.webapp.WebAppClassLoader;
import
org.eclipse.jetty.webapp.WebAppContext;
import
com.google.common.collect.Lists;
/**
* 创建Jetty Server的工厂类.
*
* @author calvin
*/
public
class
JettyFactory {
private
static
final
String DEFAULT_WEBAPP_PATH =
"src/main/webapp"
;
private
static
final
String WINDOWS_WEBDEFAULT_PATH =
"jetty/webdefault-windows.xml"
;
/**
* 创建用于开发运行调试的Jetty Server, 以src/main/webapp为Web应用目录.
*/
public
static
Server createServerInSource(
int
port, String contextPath) {
Server server =
new
Server();
// 设置在JVM退出时关闭Jetty的钩子。
server.setStopAtShutdown(
true
);
SelectChannelConnector connector =
new
SelectChannelConnector();
connector.setPort(port);
// 解决Windows下重复启动Jetty居然不报告端口冲突的问题.
connector.setReuseAddress(
false
);
server.setConnectors(
new
Connector[] { connector });
WebAppContext webContext =
new
WebAppContext(DEFAULT_WEBAPP_PATH, contextPath);
// 修改webdefault.xml,解决Windows下Jetty Lock住静态文件的问题.
webContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH);
server.setHandler(webContext);
return
server;
}
/**
* 设置除jstl-*.jar外其他含tld文件的jar包的名称.
* jar名称不需要版本号,如sitemesh, shiro-web
*/
public
static
void
setTldJarNames(Server server, String... jarNames) {
WebAppContext context = (WebAppContext) server.getHandler();
List
".*/jstl-[^/]*\\.jar$"
,
".*/.*taglibs[^/]*\\.jar$"
);
for
(String jarName : jarNames) {
jarNameExprssions.add(
".*/"
+ jarName +
"-[^/]*\\.jar$"
);
}
context.setAttribute(
"org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern"
,
StringUtils.join(jarNameExprssions,
'|'
));
}
/**
* 快速重新启动application,重载target/classes与target/test-classes.
*/
public
static
void
reloadContext(Server server)
throws
Exception {
WebAppContext context = (WebAppContext) server.getHandler();
System.out.println(
"[INFO] Application reloading"
);
context.stop();
WebAppClassLoader classLoader =
new
WebAppClassLoader(context);
classLoader.addClassPath(
"target/classes"
);
classLoader.addClassPath(
"target/test-classes"
);
context.setClassLoader(classLoader);
context.start();
System.out.println(
"[INFO] Application reloaded"
);
}
}
|
JettyFactory包含三个方法
调用JettyFactory在Jetty中运行调试Maven Web应用的示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
package
org.springside.examples.quickstart;
import
org.eclipse.jetty.server.Server;
import
org.springside.modules.test.jetty.JettyFactory;
import
org.springside.modules.test.spring.Profiles;
/**
* 使用Jetty运行调试Web应用, 在Console输入回车快速重新加载应用.
*
* @author calvin
*/
public
class
QuickStartServer {
public
static
final
int
PORT =
8080
;
public
static
final
String CONTEXT =
"/quickstart"
;
public
static
final
String[] TLD_JAR_NAMES =
new
String[] {
"sitemesh"
,
"spring-webmvc"
,
"shiro-web"
,
"springside-core"
};
public
static
void
main(String[] args)
throws
Exception {
// 设定Spring的profile
Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT);
// 启动Jetty
Server server = JettyFactory.createServerInSource(PORT, CONTEXT);
JettyFactory.setTldJarNames(server, TLD_JAR_NAMES);
try
{
server.start();
System.out.println(
"[INFO] Server running at http://localhost:"
+ PORT + CONTEXT);
System.out.println(
"[HINT] Hit Enter to reload the application quickly"
);
// 等待用户输入回车重载应用.
while
(
true
) {
char
c = (
char
) System.in.read();
if
(c ==
'\n'
) {
JettyFactory.reloadContext(server);
}
}
}
catch
(Exception e) {
e.printStackTrace();
System.exit(-
1
);
}
}
}
|
上段代码还提供了通过捕获在console中输入的回车自动重新载入上下文,并重新载入Class文件,提高了响应速度。
在执行main方法过程中如果发生如下错误:
class “javax.servlet.HttpConstraintElement”‘s signer information does not match signer information of other classes in the same package
通过执行如下命令检查依赖
1
|
mvn dependency:tree -Dverbose|grep servlet
|
检查结果如图
发现是因为Jetty8版本的包的依赖包org.eclipse.jetty.orbit.javax.servlet3.0.jar提供了javax.servlet.HttpConstraintElement类,而javax.servlet.servlet-api.jar的依赖包javax.servlet.javax.servlet-api-3.0.1.jar也提供了javax.servlet.HttpConstraintElement类,两者发生了冲突。可以使用7.6.14.v20131031版本的Jetty解决此问题。
二、测试阶段
在功能测试或集成测试阶段,希望在测试开始时自动运行Jetty加载项目进行测试,测试完成时停止Jetty容器。Jetty Maven Plugin插件可以帮助我们完成这种自动化工作。配置示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<
plugin
>
<
groupId
>org.mortbay.jetty
groupId
>
<
artifactId
>jetty-maven-plugin
artifactId
>
<
configuration
>
<
scanIntervalSeconds
>10
scanIntervalSeconds
>
<
stopKey
>foo
stopKey
>
<
stopPort
>9999
stopPort
>
configuration
>
<
executions
>
<
execution
>
<
id
>start-jetty
id
>
<
phase
>pre-integration-test
phase
>
<
goals
>
<
goal
>start
goal
>
goals
>
<
configuration
>
<
scanIntervalSeconds
>0
scanIntervalSeconds
>
<
daemon
>true
daemon
>
configuration
>
execution
>
<
execution
>
<
id
>stop-jetty
id
>
<
phase
>post-integration-test
phase
>
<
goals
>
<
goal
>stop
goal
>
goals
>
execution
>
executions
>
plugin
>
|
在上述配置中,通过execution来自定义运行阶段:
使用
三、运行阶段
为了能够创建可以直接运行的war包,需要把jetty jar包解开,将其中的class直接编译到war包中,并需要在war中提供一个可以创建并运行Jetty的Main方法。本文提供两种实现方法:
方法一
SpringSide4中提供了一种实现方法,稍加修改优化后步骤如下:
1、使用maven-assembly-plugin重新打包
maven-assembly-plugin插件能将应用程序打包成指定格式的分发包,更重要的是能够自定义包含/排除指定的目录或文件。
为方便操作,单独建立一个Maven Profile用于打包,配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<
profile
>
<
id
>standalone
id
>
<
build
>
<
plugins
>
<
plugin
>
<
groupId
>org.apache.maven.plugins
groupId
>
<
artifactId
>maven-assembly-plugin
artifactId
>
<
executions
>
<
execution
>
<
phase
>package
phase
>
<
goals
>
<
goal
>single
goal
>
goals
>
<
configuration
>
<
descriptors
>
<
descriptor
>assembly-standalone.xml
descriptor
>
descriptors
>
<
archive
>
<
manifest
>
<
mainClass
>org.springside.examples.showcase.Main
mainClass
>
manifest
>
archive
>
configuration
>
execution
>
executions
>
plugin
>
plugins
>
build
>
profile
>
|
上述配置中,通过execution配置打包操作在package阶段开始,引入assembly-standalone.xml文件定义打包的规则,配置archive修改war包中的META-INF/Manifest.mf,替换main class为org.springside.examples.showcase.Main。
assembly-standalone.xml中的配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
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/xsd/assembly-1.1.2.xsd"
>
<
id
>standalone
id
>
<
formats
>
<
format
>war
format
>
formats
>
<
includeBaseDirectory
>false
includeBaseDirectory
>
<
dependencySets
>
<
dependencySet
>
<
outputDirectory
>/
outputDirectory
>
<
includes
>
<
include
>org.eclipse.jetty*:*
include
>
includes
>
<
scope
>provided
scope
>
<
unpack
>true
unpack
>
<
unpackOptions
>
<
excludes
>
<
exclude
>*
exclude
>
<
exclude
>META-INF/*
exclude
>
<
exclude
>about_files/*
exclude
>
excludes
>
unpackOptions
>
dependencySet
>
dependencySets
>
<
fileSets
>
<
fileSet
>
<
directory
>${project.basedir}/target/${project.build.finalName}
directory
>
<
outputDirectory
>/
outputDirectory
>
<
excludes
>
<
exclude
>META-INF/**/*
exclude
>
excludes
>
fileSet
>
<
fileSet
>
<
directory
>${project.basedir}/target/classes
directory
>
<
includes
>
<
include
>**/*/Main.class
include
>
includes
>
<
outputDirectory
>/
outputDirectory
>
fileSet
>
fileSets
>
assembly
>
|
assembly-standalone.xml涉及到几个关键点:
2、使用代码创建Jetty容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
package
org.springside.examples.quickstart;
import
java.io.File;
import
java.net.URL;
import
java.security.ProtectionDomain;
import
org.eclipse.jetty.server.Server;
import
org.eclipse.jetty.webapp.WebAppContext;
/**
* Main Class for standalone running.
*
* @author calvin
*/
public
class
Main {
public
static
void
main(String[] args)
throws
Exception {
|