Jetty嵌入式Web容器攻略

更新时间:2016-10-26 10:54:36 点击次数:2142次

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类实现及运行方法同方法一。

本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

回到顶部
嘿,我来帮您!