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
|
<!-- jetty -->
< 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..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<String> jarNameExprssions = Lists.newArrayList( ".*/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来自定义运行阶段:
使用<daemon>true</daemon>配置选项来预防Jetty无限期运行,迫使它只在执行Maven时才运行。
三、运行阶段
为了能够创建可以直接运行的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 {
String contextPath = "/" ;
int port = Integer.getInteger( "port" , 8080 );
Server server = createServer(contextPath, port);
try {
server.start();
server.join();
} catch (Exception e) {
e.printStackTrace();
System.exit( 100 );
}
}
private static Server createServer(String contextPath, int port) {
// use Eclipse JDT compiler
System.setProperty( "org.apache.jasper.compiler.disablejsr199" , "true" );
Server server = new Server(port);
server.setStopAtShutdown( true );
ProtectionDomain protectionDomain = Main. class .getProtectionDomain();
URL location = protectionDomain.getCodeSource().getLocation();
String warFile = location.toExternalForm();
WebAppContext context = new WebAppContext(warFile, contextPath);
context.setServer(server);
// 设置work dir,war包将解压到该目录,jsp编译后的文件也将放入其中。
String currentDir = new File(location.getPath()).getParent();
File workDir = new File(currentDir, "work" );
context.setTempDirectory(workDir);
server.setHandler(context);
return server;
}
}
|
createServer方法负责创建Jetty服务,获取war包路径,创建context及工作目录
main方法负责调用createServer方法创建Jetty服务,设置上下文路径及启动端口,并启动Jetty服务,另外如果war包所在的路径包含中文,则获取路径的代码应修改为:
1
2
3
|
ProtectionDomain protectionDomain = Main. class .getProtectionDomain();
URL location = protectionDomain.getCodeSource().getLocation();
location = java.net.URLDecoder.decode(location , "utf-8" );
|
3、注意事项
通过以上配置,已经可以在Web应用程序内嵌入Jetty容器了,但还需要注意以下几点
Maven Pom中的Jetty依赖注意scope修改为provided,防止Jetty的Jar包被打到WEB-INF/lib中。
如果需要解析jsp页面,需要在依赖中加入jsp-2.1-glassfish包的引用,注意其scope不能设置为provided
1
2
3
4
5
|
< dependency >
< groupId >org.mortbay.jetty</ groupId >
< artifactId >jsp-2.1-glassfish</ artifactId >
< version >2.1.v20100127</ version >
</ dependency >
|
由于Jetty只会在容器的classpath中寻找jstl tags,所以需要注意将jstl包拆包到Jetty容器的classpath中,但是jetty-7 (7.6.9)、jetty-8 (8.1.9)、jetty-9 (9.0.0.M4)之后的版本内嵌了jstl包,不需要添加jstl包。
4、运行
执行如下命令将Web应用打包成war包,在${project.basedir}/target目录下将会生成嵌入Jetty容器的war包。
1
|
mvn package -Pstandalone
|
通过如下命令运行war包。
1
|
Java -Xms2048m -Xmx2048m -XX:MaxPermSize=128m -jar xxx.war
|
方法二
方法一中主要是使用了maven-assembly-plugin进行自定义打包,除此之外还可以使用maven-war-plugin、maven-antrun-plugin、maven-dependency-plugin、maven-compiler-plugin共同实现创建可执行的war包
Maven POM配置示例如下:
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
|
< profile >
< id >standalone</ id >
< build >
< finalName >quickstart</ finalName >
< plugins >
< plugin >
< groupId >org.apache.maven.plugins</ groupId >
< artifactId >maven-war-plugin</ artifactId >
< version >2.3</ version >
< configuration >
< archive >
< manifest >
< mainClass >org.springside.examples.quickstart.Main</ mainClass >
</ manifest >
</ archive >
< warName >${project.artifactId}-standalone</ warName >
</ configuration >
</ plugin >
< plugin >
< groupId >org.apache.maven.plugins</ groupId >
< artifactId >maven-antrun-plugin</ artifactId >
< version >1.7</ version >
< executions >
< execution >
< id >main-class-placement</ id >
< phase >prepare-package</ phase >
< configuration >
< target >
< move todir = "${project.build.directory}/${project.artifactId}/" >
< fileset dir = "${project.build.directory}/classes/" >
< include name = "**/*/Main.class" />
</ fileset >
</ move >
</ target >
</ configuration >
< goals >
< goal >run</ goal >
</ goals >
</ execution >
</ executions >
</ plugin >
< plugin >
< groupId >org.apache.maven.plugins</ groupId >
< artifactId >maven-dependency-plugin</ artifactId >
< version >2.5.1</ version >
< executions >
< execution >
< id >jetty-classpath</ id >
< phase >prepare-package</ phase >
< goals >
< goal >unpack-dependencies</ goal >
</ goals >
< configuration >
< includeGroupIds >org.eclipse.jetty, org.eclipse.jetty.orbit,
javax.servlet</ includeGroupIds >
< includeScope >provided</ includeScope >
<!-- remove some files in order to decrease size -->
< excludes >*, about_files/*, META-INF/*</ excludes >
<!-- <excludeArtifactIds>jsp-api,jstl</excludeArtifactIds> -->
< outputDirectory >
${project.build.directory}/${project.artifactId}
</ outputDirectory >
</ configuration >
</ execution >
</ executions >
</ plugin >
<!-- to support compilation in linux -->
< plugin >
< groupId >org.apache.maven.plugins</ groupId >
< artifactId >maven-compiler-plugin</ artifactId >
< version >2.5.1</ version >
< configuration >
< target >1.6</ target >
< source >1.6</ source >
< encoding >UTF-8</ encoding >
</ configuration >
</ plugin >
</ plugins >
</ build >
</ profile >
|
注意事项、org.springside.examples.showcase.Main类实现及运行方法同方法一。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。