Jetty之servlet容器(2011.11.09)

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下測試的。
           
            版權聲明:任何獲得Matrix授權的網站,轉載時請務必保留以下作者信息和鏈接
            作者: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的網站來獲取更多的文檔和示例代碼。

            另附jetty的配置指南(Maven Jetty Plugin Configuration Guidehttp://docs.codehaus.org/display/JETTY/Maven+Jetty+Plugin

發佈了54 篇原創文章 · 獲贊 61 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章