1. 程式人生 > >Jetty是什麼?Jetty介紹以及配置

Jetty是什麼?Jetty介紹以及配置

Jetty 是一個開源的servlet容器,它為基於Java的web內容,例如JSP和servlet提供執行環境。Jetty是使用Java語言編寫的,它的API以一組JAR包的形式釋出。開發人員可以將Jetty容器例項化成一個物件,可以迅速為一些獨立執行(stand-alone)的Java應用提供網路和web連線。 

本文包括以下內容:
1.        嵌入式Servlet容器有什麼意義? 
2.        建立一個嵌入式的容器: 使用The Jetty API 
3.        將配置從程式碼中獨立出來: XML驅動的配置檔案 
4.        可執行的JAR包 
5.        結論 
6.        資源 


如果讓一個人說出一種開源的servlet容器,可能他們會回答Apache Tomcat。但是,Tomcat並不是孤單的,我們還有Jetty。Jetty作為可選的servlet容器只是一個額外的功能,而它真正出名是因為它是作為一個可以嵌入到其他的Java程式碼中的servlet容器而設計的。這就是說,開發小組將Jetty作為一組Jar檔案提供出來,因此你可以在你自己的程式碼中將servlet容器例項化成一個物件並且可以操縱這個容器物件。

Jetty在servlet容器中算不上一個新面孔;它從1998年就已經嶄露頭角。Jetty的釋出遵循了Apache 2.0的開源協議,你可以在免費軟體和商業軟體中使用Jetty而不用支付版稅。

在本文中,筆者將為你為何需要嵌入式servlet容器提出一點見解,解釋Jetty API的基礎,並且展示如何使用XML配置檔案來將Jetty的程式碼精簡到最少。

本文的示例程式碼是在Jetty5.1.10以及Sun JDK 1.5.0_03下測試的。

轉載時請務必保留以下作者資訊和連結

作者:Ethan McCallum;shenpipi
原文:http://www.onjava.com/pub/a/onjava/2006/06/14/what-is-jetty.html
Matrix:http://www.matrix.org.cn/resource/article/44/44588_Jetty.html
關鍵字:Jetty

嵌入式Servlet容器的意義何在?

在你採用Jetty之前,理智的做法是首先問問自己:為什麼自己的應用程式中需要嵌入一個servlet容器。 吸引我的視線的是Jetty可以為一個已經存在的應用程式提供servlet功能的能力。這種能力對於很多組織都是有用的,包括Java EE應用伺服器生產商,軟體測試人員以及定製軟體生產商。大部分的Java開發人員都可以劃分到這三種情況中。

首先,考慮要建立自己的Java EE應用伺服器這樣一種邊緣情況。根據規範,一個完整的應用伺服器必須提供servlet,EJB,以及其他一些功能。你應該採用已經存在而且測試過的元件並且使用Jetty而不是從零開始。Apache Geronimo, JBoss, 和ObjectWeb JOnAS這些專案組在建立自己Java EE應用伺服器時也是這樣做的。

當已經存在的容器不能滿足需要的時候,軟體測試人員會得益於按照需要來生成自己的servlet容器。例如,曾經有個同事想要尋找某種方式來驅動他為web service程式碼所寫的單元測試。對於他的這種情形——幾個開發人員加上幾個執行在Cruise Control中的自動單元測試——我向他示範了在他的單元測試組(unit test suites)中如何動態的(on the fly)使用Jetty來例項化一個servlet容器。沒有多餘的指令碼,沒有剩餘的檔案,只有程式碼。

對於那些開發Java EE應用作為產品的人員來說,為什麼僅僅提供一個WAR檔案?這樣你為會容器的規範而頭疼,同時也會增加你的技術支援的成本。相反的,可以提供給客戶一個自己具有啟動,停止以及管理功能的應用程式。就連硬體生產商也會從中受益:Jetty對於普通的HTTP服務(沒有servlet)只需要350k的記憶體,這使得可以將其用在智慧裝置中。你可以提供基於web的控制面板並且具有Java web應用的所有功能而不用擔心那些獨立的容器所帶來的壓力。

最後,我敢打賭嵌入式servlet容器最有趣的應用會發生在那些從來不編寫傳統的基於web應用的人身上。可以將Java EE和HTTP的組合作為一個C/S結構程式的後臺。考慮一個事件驅動的服務,例如(假想的)Message-Driven Bank(onjava上的另外一篇文章中提到),從main()方法啟動並且等待到來的請求,就像Unix中的daemon程式一樣。肯定會有一些人想要將這個程式暴露成一種基於使用者的風格,例如一個GUI桌面應用,這只是個時間問題。

要建立自己的基礎元件,協議和socket通訊程式碼是最令人生厭的,而且會使人從業務邏輯中分心,就更不用說將來可能要除錯的事情了。使用嵌入式的Jetty容器來將業務邏輯通過HTTP協議暴露是一個不錯的選擇,它不用對現有程式作過多改變。選擇採用Swing,SWT,XUI這些GUI並且將請求包裝成HTTP Post操作,REST,甚至SOAP來完成這個迴路。與定製的特定於某個領域的協議相比,這些通用的協議可能效能稍差,但是,用不了多久,你就會從這些已經存在的經過實際檢驗的協議中得到好處並且節省大量的努力。

建立一個嵌入式的容器:使用Jetty API


希望以上的想法能夠刺激你的胃口讓你嘗試一下嵌入式的servlet容器。示例程式Step1Driver 演示了一個基於Jetty的簡單服務。它建立了一個servlet容器的例項,將一個servlet class對映到一個URI,並且使用一些URL來呼叫這個servlet。為了程式碼的簡潔,我犧牲了一些程式碼的質量。

Service物件就是Jetty容器,例項化出這樣一個物件就產生了一個容器。
Server service = new Server() ;


這樣一來,Service物件就像一個沒有門的賓館:沒有人能夠進入並且使用,所以還是沒有用的。接下來的一行程式碼設定容器在localhost,埠7501監聽。
service.addListener( "localhost:7501" ) ;


為了在所有的interface上監聽,不使用主機名("addListener( ":7501" )")。就像名字暗示的那樣,你可以呼叫addListener()多次來在多個interface上監聽。

注意到示例程式碼中維護了Server物件的一個引用,這是將來要停止容器需要用到的。
將一個web應用對映到Service是很直觀的:
service.addWebApplication(
   "/someContextPath" ,
   "/path/to/some.war"
) ;


這個呼叫將處理一個web應用中的web.xml部署描述符(descriptor)來對映其中的過濾器servlet和servlet,就像其他容器所做的那樣。第一個引數是context path,這個web應用的所有servlet和JSP都會被對映成相對於這個路徑的URI。第二個引數是web應用本身。可以是一個打包的WAR檔案或者目錄格式的web應用。再次呼叫addWebApplication()可以用來新增其他的web應用。

注意到Jetty並不需要一個完整的符合規範的WAR檔案來部署servlet。如果編寫了一個搭載於HTTP協議的定製應用程式協議,你可以載入一個單一的servlet並且將其通過網路提供出去。並沒有必要使用WAR檔案僅僅為了使一個非web應用具有通過HTTP協議訪問的功能。

為了對映這種一次性的servlet,通過在Service物件上呼叫getContext()動態的建立一個context。這個示例程式碼建立了一個叫做/embed的context。
ServletHttpContext ctx = (ServletHttpContext)
   service.getContext( "/embed" ) ;


如果context不存在地話,呼叫getContext()將會建立一個新的context
接下來,呼叫addServlet()將一個servlet類對映到一個URI
ctx.addServlet(
   "Simple" , // servlet name
   "/TryThis/*" , // URI mapping pattern
   "sample.SimpleServlet" // class name
) ;


第一個引數是該servlet的一個描述性的名字。第二個引數是要對映的路徑,等同於web.xml servlet對映中的<url-pattern>。這個對映路徑是相對於context path的,這裡是/embed。”/*”表示這個servlet接收/embed/TryThis這樣一個URI,同時它也會接收所有以此開頭的URI,例如/embed/TryThis/123。在使用一個單一的servlet來作為一個大系統的入口的時候,這種對映方式非常有用。Struts和Axis就是實際應用中使用這樣的對映方式的例子。
有時候你可能想讓你的context成為root context,或者說“/”,這樣更像一個普通的HTTP服務。Jetty通過Service.setRootWebapp()來支援此功能。
service.setRootWebapp(
   "/path/to/another.war"
) ;


唯一的一個引數是一個web應用的路徑。
容器在此時還是不活動的。而且它也沒有試圖去繫結要監聽的socket,啟動容器需要呼叫:
service.start() ;


這個方法會立即返回,因為Jetty將服務在一個獨立的執行緒中執行。因此,當容器執行的時候,main()可以來做其他任何事情。
其餘的程式碼是使用一組URL來呼叫這個嵌入式容器。這些呼叫確保容器已經在執行並且servlet按照期望的方式工作。
關閉容器就像啟動它一樣直觀
service.stop() ;


注意最外層try/catch塊中的catch語句。
{

   service.start() ;
   // ... URL calls to mapped servlet ...
   service.stop() ;

}catch( Throwable t ){

   System.exit( 1 ) ;

}


顯示的呼叫System.exit()確保容器在發生異常的時候被關閉。否則,容器會持續執行因此整個應用程式也不會退出。
必須記住Jetty web應用並不限於使用程式碼來訪問。如果我將service.stop()從剛才的程式碼中去掉,那麼容器將一直執行並且我可以在瀏覽器中呼叫servlet,例如
http://localhost:7501/embed/TryThis/SomeExtraInfo

你並不一定要完全按照我說的去做。這個示例程式碼可以作為一個Eclipse專案執行。而且你也可以寫一段shell指令碼使其執行在Unix/Linux命令列中。在上面兩種情況下,確信Jetty在你的classpath中。

將配置從程式碼中獨立出來: XML驅動的配置檔案

儘管Jetty的API非常直觀簡練,但是直接的呼叫Jetty API會將大量的配置資訊——埠號,context path,servlet類名——埋藏在程式碼之中。Jetty提供了一種基於XML的配置方式來替代直接呼叫API,這樣你就可以將這些配置資訊都放在程式碼外面而使你的程式碼保持清潔。

Jetty的XML配置檔案是基於Java反射的。java.lang.reflect中的類代表了Java中的方法和類,這樣你可以例項化一個物件並且使用方法的名字和引數型別來呼叫它的方法。這種情況下,Jetty的XML配置檔案解析器會將XML的element和屬性翻譯成反射方法呼叫。

這段節選自Step2Driver示例類中的程式碼是Step1Driver的一個改良版本。要是使用到了配置檔案,就必須有一定的Jetty程式碼來載入它。
URL serviceConfig = /* load XML file */ ;
   // can use an InputStream or URL

XmlConfiguration serverFactory =
   new XmlConfiguration( serviceConfig ) ;

                        
Server service =
   (Server) serverFactory.newInstance() ;


不可否認,這不比Step1Driver示例節省多少程式碼,但是,即使你要新增新的servlet或者web應用,Step2Driver的程式碼不會因此而增加。而直接呼叫Service和context物件的方法在配置逐漸增加的情況下會越來越差。
列表1是Step2Driver載入的XML檔案。頂層的<Configure> element 的屬性指明瞭要例項化那個類。這裡是Jetty Server物件。
<!-- 1 -->
<Configure class="org.mortbay.jetty.Server">

  <!-- 2 -->
  <Call name="addListener">
    <Arg>
      <!-- 3 -->
      <New
         class="org.mortbay.http.SocketListener">

        <!-- 4 -->
        <Set name="Host">

          <!-- 5 -->
          <SystemProperty
             name="service.listen.host"
             default="localhost"
          />

        </Set>

        <Set name="Port">
          <SystemProperty
             name="service.listen.port"
             default="7501"
          />
        </Set>

      </New>
    </Arg>
  </Call>


  <Call name="getContext">

    <Arg>/embed</Arg>


    <!--
    call methods on the return value of
    Server.getContext()
    -->

    <!-- 6 -->
    <Call name="addServlet">

      <!-- servlet name -->
      <Arg>"Simple"</Arg>

      <!-- URL pattern -->
      <Arg>/TryThis/*</Arg>

      <!-- servlet class -->
      <Arg>sample.SimpleServlet</Arg>

    </Call>

  </Call>

</Configure>


<Call> element代表要在Server物件上呼叫的方法。這裡要呼叫addListener(),如標記(2)處,它自己又有一個子element叫做<Arg>,這指明瞭方法的引數。這裡我只能傳遞一個字串值作為監聽的地址,而addListener()卻需要接受一個SocketListener物件作為引數。因此,我要使用<New>在標記(3)處例項化一個新的SocketListener物件。標記2和3處的程式碼等同於以下程式碼:
server.addListener(
   new SocketListener( ... )
) ;


為了配置SocketListener自己,必須使用一個<Call>來呼叫它的setHost()方法,既然這個方法遵循了JavaBean的命名規則,示例程式碼因此使用了<Set> element(4)作為一種快捷方式。在後臺,Jetty給set中name屬性所指定的屬性賦值,並且決定呼叫什麼方法,這裡是setHost()

setHost()的引數這裡沒有顯示給出,而是使用了<SystemProperty>來從系統屬性中來獲取引數的值,這裡從系統引數service.listen.host 和 service.listen.port。如果系統屬性沒有定義,你可以使用default來指定一個預設值。這裡,4和5等同於以下呼叫:
   socketListener.setHost(
      System.getProperty(
         "service.listen.host" ,
         "localhost"
      )
  ) ;


最後注意標記6處的<Call> element位於呼叫getContext方法的<Call>中。內部的<Call>是作用在外部的<Call>的返回的物件上的,這裡,呼叫的是getServlet()返回的context上的addServlet()方法:
server.getContext().addServlet( ... ) ;


Jetty 小組的英明在於這個XML配置檔案的進一步深入處理:我們可以注意到列表1中所有的Jetty特定的呼叫都是element和屬性的值,而不是名字,這就意味著XML配置檔案可以被用在任何類上,而不僅僅是Jetty的類中。根據你的應用程式的編寫方式,你可以全部使用Jetty的XML配置檔案來配置。

可執行JAR包

如果你使用Jetty的XML來配置你的應用,你需要使用大量的重複的程式碼來載入你的config檔案並且執行你的應用。不過你可以使用Jetty的可執行的start.jar來為你載入檔案,這會讓你節省更多的程式碼。

例如,你可以使用以下的命令列來載入Step2Driver中的Jetty服務。
CLASSPATH= ...various Jetty JARs...
java /
   -Djetty.class.path=${CLASSPATH} /
   -jar <jetty install path>/start.jar /
   standalone.xml


注意到這個命令僅僅載入xml檔案來建立容器和監聽器,因此,它並不會呼叫示例程式碼中用來測試URL的程式碼。

結論
一個嵌入式的Jetty servlet容器可以讓你的web使用Java應用而不用打包成正式的web應用的形式。這提供了多種可能性,讓Jetty成為你的工具箱中的一個多才多藝的幫手。
當然,我這裡所寫的東西並不能包含Jetty的所有內容。我建議你去訪問Jetty的網站來獲取更多的文件和示例程式碼。

資源
---本文的示例程式碼
--The Jetty網站 上有文件,示例以及下載的連結。同時,它還包含了一個使用Jetty的專案的列表頁。同樣值得注意的是JettyPlus子專案,它提供了JNDI,資料來源和其他servlet規範中的功能(特性)。 
--Sun's Java EE 網站, 其中包含了servlet規範的連結 
--Java Reflection in Action 詳細討論了Java的反射和自省API。 
--為了比較, OnJava同樣釋出一篇文章關於 Tomcat's embedded side. 
--本文中提到使用 Swing, XUI, and SWT 作為基於Java的GUI前端(frontend)應用程式 
--Message-Driven Bank 來自於另一篇文章, "J2EE Without the Application Server." 
Ethan McCallum 致力於 Unix/Linux, C++, 以及 Java的研究.