Jetty是一個用 Java 實現、開源、基於標準的,並且具有豐富功能的 Http 伺服器和 Web 容器。Jetty中應用最廣泛的一項功能就是可以作為嵌入式Web容器。 在開發階段,可以使用Jetty在Eclipse里直接啟動應用,而不是像Tomcat那樣繁瑣,先把幾十兆應用打包,然後再複製到某個
Jetty是一個用 Java 實現、開源、基於標準的,並且具有豐富功能的 Http 伺服器和 Web 容器。Jetty中應用最廣泛的一項功能就是可以作為嵌入式Web容器。
- 在開發階段,可以使用Jetty在Eclipse里直接啟動應用,而不是像Tomcat那樣繁瑣,先把幾十兆應用打包,然後再複製到某個目錄後再啟動。
- 在測試階段,可以直接在測試用例中啟動Jetty,而不是先將應用打包部署到容器。
- 在運行階段,可以將war包配置成直接能夠運行的應用
本文將著重介紹如何配置使用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)
- <connectors>:可選參數,配置org.eclipse.jetty.server.Connector(Jetty埠監聽器)列表,如果不指定該參數,將會連接預設的8080埠。
- <jettyXml>:可選參數,指定Jetty配置文件的路徑
- <scanIntervalSeconds>:可選參數,配置Web應用掃描的暫停時間,Web應用掃描如果發現修改了程式,會自動重新部署。該參數預設值為0,即不啟動熱部署
- <systemProperties>:可選參數,設置插件執行時的系統參數,比如上面的配置示例中指定了spring的profile為development,如果不設置該參數,就需要配置maven與spring的profile一致,同時在mvn命令中增加-Pdevelopment選項,或者直接在spring配置文件中設置spring.profiles.active為development
- <systemPropertiesFile>:可選參數,設置系統參數配置文件的位置,將會批量執行其中的系統參數配置
- <loginServices>:可選參數,配置org.eclipse.jetty.security.LoginService實現類的列表。
- <requestLog>:可選參數,配置請求日誌介面:org.eclipse.jetty.server.RequestLog的實現類,配置請求日誌的處理格式,
比如org.mortbay.jetty.NCSARequestLog就是一個NCSA格式((美國)國家超級計算技術應用中心 (NCSA) 公用格式,是常用的標準日誌格式)的實現。
(2)配置Web應用程式(不支持run-forked、stop兩個goals)
- <webApp>:從jetty6.1.6rc0起使用webAppConfig,web應用程式配置根節點
- <contextPath>:設置web應用程式的context路徑,預設情況下,它被設置成該項目的pom.xml的<artifactId>
- <descriptor>:設置web應用程式主配置文件web.xml的路徑,預設該配置文件位於WEB-INF目錄下
- <defaultsDescriptor>:設置先於web.xml執行的webdefault.xml配置文件的路徑
- <overrideDescriptor>:設置在web.xml讀取之後執行的配置文件,使用該參數可以覆蓋或增加web.xml中的配置
- <tempDirectory>:Web應用的臨時目錄,Jetty可以在此目錄編譯jsp文件或者複製jar包,預設路徑為${project.build.outputDirectory}/tmp
- <baseResource>:指定Web應用靜態資源所在的路徑,預設路徑為src/main/webapp
- <resourceBases>:指定多個Web應用靜態資源所在的路徑,使用逗號分隔
- <baseAppFirst>:可選參數,預設值為true,控制是否可以在Web應用的原始資源之前或之後疊加多個war包
- <jettyEnvXml>:可選參數,指定jetty-env.xml配置文件的路徑
- <containerIncludeJarPattern>:jetty-8.1.x之後的版本可以使用,可選參數,配置載入到Jetty容器 Classloader中的Jar包的路徑或匹配模式,符合條件的jar包將會被檢測META-INF、資源、tld和類的繼承關係
- <webInfIncludeJarPattern>:jetty-8.1.x之後的版本可以使用,可選參數,配置載入到Web應用程式的Classloader(WEB-INF classpath)中的Jar包的路徑或匹配模式,符合條件的jar包將會被檢測META-INF、資源、tld和類的繼承關係
- <contextXml>:可選參數,指定context xml配置文件的路徑
run goals將會啟動Jetty並運行應用程式,不需要應用程式編譯成war包。另外run還支持webapp節點的其它屬性:
- <classesDirectory>:Web應用程式類編譯後的路徑
- <testClassesDirectory>:Web應用程式單元測試類編譯後的路徑,預設值為${project.build.testOutputDirectory}.
- <useTestScope>:Jetty-7之前的版本參數名稱為useTestClasspath,如果設置為true,開啟測試模式,<testClassesDirectory>中指定的類及其依賴將首先被載入到classpath中,預設值為false
- <useProvidedScope>:如果設置為true,依賴範圍標示為“provided”的依賴將被載入到容器的classpath中,該參數很少使用。
- <webAppSourceDirectory>:Web應用程式靜態資源路徑,預設值為${basedir}/src/main/webapp
- <scanTargets>:配置除了插件自動掃描的位置外,其它需要掃描的目錄或文件列表
- <scanTargetPatterns>:配置除了插件自動掃描的位置外,其它需要掃描的目錄或文件的匹配模式
- <skip>:預設值為false,如果設置為true,將會停止執行插件
Jetty Maven Plugin插件支持的其它goals簡介如下(詳見http://wiki.eclipse.org/Jetty/Feature/Jetty_Maven_Plugin):
- run-war:將Web應用程式打包成war包並部署到Jetty中。
- run-exploded:使用war exploded模式(文件夾模式)將Web應用程式打包並部署到Jetty中
- deploy-war:功能與run-war類似,區別就是maven生命周期中不包含package階段
- run-forked:jetty-7.5.2之後的版本可用,強迫Jetty使用一個新的JVM啟動應用程式
- start:jetty-7.6.0之後的版本可用,一般在配合插件中的execution節點使用,test-compile階段之後才執行構建,確保必要的類及文件都生成好了。一般用於集成測試時啟動Jetty,本文第二部分測試階段會有詳細介紹
- stop:關閉運行中的Jetty容器
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<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包含三個方法
- createServerInSource:以src/main/webapp為Web應用目錄創建Jetty WebServer,確保jvm退出時關閉Jetty,在同一個埠啟動多個jetty時報告埠衝突,並解決了javascript、css等靜態文件被jetty鎖定而不能修改的問題。
- setTldJarNames:Jetty,tomcat等web容器通常都會對classloader做擴展,Jetty中的org.mortbay.jetty.webapp.WebAppClassLoader負責載入一個Web應用context中的應用類。
- Jetty的jsp處理引擎來自於Glassfish,要求JSF標簽必須位於Jetty容器的classpath中,不能位於Web應用的classpath中,而Jetty的WebAppClassLoader優先使用父classloader載入類,導致tld文件都被載入到父classloader中,在Jetty的classpath中根本掃描不到,所以會出現找不到tld文件的情況。setTldJarNames方法可以設置將包含tld的jar包載入到Jetty的classpath中。
- reloadContext:重新載入Jetty的context
調用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來自定義運行階段:
- 在pre-integration-test階段運行start goals啟動Jetty容器
- 在post-integration-test階段運行stop goals停止Jetty容器。
使用<daemon>true</daemon>配置選項來預防Jetty無限期運行,迫使它只在執行Maven時才運行。
三、運行階段
為了能夠創建可以直接運行的war包,需要把jetty jar包解開,將其中的class直接編譯到war包中,並需要在war中提供一個可以創建並運行Jetty的Main方法。本文提供兩種實現方法:
方法一
SpringSide4中提供了一種實現方法,稍加修改優化後步驟如下:
1、使用maven-assembly-plugin重新打包
maven-assembly-plugin插件能將應用程式打包成指定格式的分發包,更重要的是能夠自定義包含/排除指定的目錄或文件。
為方便操作,單獨建立一個Maven Profile用於打包,配置如下: