GeoServer源碼解析和擴展 (三)結構篇

上一章我們通過實現一個服務對如何擴展GeoServer有了一定的瞭解,但是,對於爲何要這樣做並沒有說明,本章我們重點來說說GeoServer的結構,下圖來自GeoServer官網(希望沒有侵權),它很好的揭示了GeoServer處理請求的全過程。

http://www.cnblogs.com/_images/rest-dispatch.png

我 們說GeoServer使用Spring框架來構建,這裏就可以看到Spring的使用,虛線框中的Restlet就是用Spring引入系統的,每個服 務包的“applicationContext.xml”文件裏都包含了描述Route映射的信息,例如WMS就有如下片段:

    <bean id="wmsURLMapping"          class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">         <property name="alwaysUseFullPath" value="true"/>         <property name="mappings">             <props>                 <prop key="/wms">dispatcher</prop>                 <!-- prop key="/wms/putstyles">putStylesWrapper</prop-->                 <prop key="/wms/*">dispatcher</prop>             </props>         </property>     </bean>

它將形如“/wms/”的請求映射到“dispatcher”對象,而這個對象就是上一章調試的重點“org.geoserver.ows.Dispatcher”。所以,如果我們希望我們的服務也和WMS有相似的處理方式,我們就需要在自己的配置文件里加上類似的一段。

    本章我準備先兩個方面入手講解GeoServer是如何處理OWS請求的。 首先介紹GeoServer的運行時環境,包括對象是如何創建並且引用的,以及SpringFramework的配置體系。然後以Dispatcher類 的處理算法入手,重點介紹擴展點,熟悉了擴展點,我們就可以對GeoServer的OWS處理進行擴展,開發符合我們要求的應用。

    搞清楚處理機制後,我會談談GeoServer的資源API。這個部分主要羅列了GeoServer的一些接口,以及它們代表的概念。

一 OWS請求處理

    我們知道applicationContext.xml文件是SpringFramework的配置文件,許多對象都在這個文件中定義,但是爲什麼是這個文件呢,它們之間有沒有關係呢。

    讓我們先來看看“web-app”的“web.xml”文件,中間有這樣一段

    <context-param>         <param-name>contextConfigLocation</param-name>         <param-value>classpath*:/applicationContext.xml classpath*:/applicationSecurityContext.xml</param-value>     </context-param>

可以看到,這裏定義了classpath下面的applicationContext.xml文件和 applicationSecurityContext.xml作爲配置文件,系統運行起來後SpringFramework會掃描classpath下 面所有的applicationContext.xml文件和applicationSecurityContext.xml文件,創建裏面定義的對象。 所以只要我們提供的包裏面包含這兩個文件中的任意一個,就可以把自定義的對象裝載到系統運行時中,同時還可以引用已經創建的對象。

    main包中的applicationContext.xml文件定義了許多基礎對象,很重要的有:catalog,geoServer和dispatcher,這幾個對象將是我們後面講解的重點。

image

上面這幅圖描述了GeoServer處理OWS請求的步驟,右邊的紅色方框是每個步驟的擴展點,每個擴展點都對應一 個抽象類或者接口。可以通過實現這些類和接口來擴展GeoServer的功能。下面我們來介紹Dispatcher類處理OWS請求的步驟,每一步都有具 體的函數相對應。

第一步 解析HTTP請求參數(見org.geoserver.ows.Dispatcher.init(Request))

    程序並不會直接採用HTTP的參數,它們是字符串鍵值對(KVP)或者是XML字符串,程序會先把它們轉換成相應的對象。類型 org.geoserver.ows.Request是代表請求參數的類型,它包含轉換後的KVP Map就已經將原來的字符串變成了對象。

    轉換參數的工作由Parser來完成。舉例說明:參數“BBOX=-180,-90,180,90”的值是字符串“-180,-90,180,90”,經 過org.geoserver.wfs.kvp.BBoxKvpParser的處理,就變成了 org.geotools.geometry.jts.ReferencedEnvelope的對象,顯然它更容易使用。

    設想,如果我們有一個參數需要傳遞數據表,我們可以這樣構造參數格 式:TABLE=cell11,cell12,cell13|cell21,cell22,cell23|cell31,cell32,cell33。當 程序遇到這個參數的時候我們就可以使用我們開發的Parser對象來將它轉換成一個表對象,供後續代碼使用。我們只需要將這個Parser對象放到 applicationContext.xml文件立即可,就像下面這樣

     <bean id="tableKvpParser" class="examples.TableKvpParser"/>

所有Parser都必須從org.geoserver.ows.KvpParser繼承。

第二步 匹配服務對象(見org.geoserver.ows.Dispatcher.service(Request))

    程序根據參數SERVICE和VERSION的值來選擇合適的服務,例如:http://www.dummy.com/geoserver/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetCapabilities,這個URL是在請求1.1.1版本的WMS服務。所以註冊服務時需要指明服務ID和版本號,下面是wms服務的註冊代碼

     <bean id="wmsService2" class="org.geoserver.wms.DefaultWebMapService">        <constructor-arg ref="wms"/>      </bean>      <alias name="wmsService2" alias="webMapService"/>            <bean id="wmsServiceDescriptor" class="org.geoserver.platform.Service">         <constructor-arg index="0" value="wms"/>         <constructor-arg index="1" ref="wmsService2"/>         <constructor-arg index="2" value="1.1.1"/>         <constructor-arg index="3">           <list>             <value>Capabilities</value>             <value>GetCapabilities</value>             <value>DescribeLayer</value>             <value>GetFeatureInfo</value>             <value>GetLegendGraphic</value>             <value>GetMap</value>             <value>Map</value>             <value>reflect</value>             <value>kml</value>             <value>GetStyles</value>           </list>         </constructor-arg>      </bean>

來看Service類的構造函數

public Service(String id, Object service, Version version, List<String> operations)

不難看出上面的配置信息其實定義的是構造函數的參數,其中id,version,operations的含義都不難 猜出,然我們來看看service這個參數。它是一個Object,也就說對service這個對象並沒有強類型要求。但是並非任何對象都可以作爲 service參數傳進來,關於這個問題,我們會在後面加以說明,在這個例子裏,service是類 org.geoserver.wms.DefaultWebMapService的對象。

    上面的配置信息將一個ID是“wms”版本是“1.1.1”的OWS服務註冊到系統運行時中,當一個OWS請求到來時,系統就會遍歷所有註冊的服務,尋找符合要求的服務。

第三步 執行操作(見org.geoserver.ows.Dispatcher.dispatch(Request, Service))

    這一步主要的操作就是創建執行對象org.geoserver.platform.Operation,這個對象採用了Java的反射原理來實現函數調 用,所以需要創建函數參數數組。前面提到過OWS參數的格式有KVP和XML兩種,因此對參數的處理也分爲兩種,具體到類就是 org.geoserver.ows.KvpRequestReader和org.geoserver.ows.XmlRequestReader。這兩 個類將參數字符串轉換成相應的對象,在這點上與前面的Parser類似,不同的是這裏的轉換對應的是一個服務調用,而不是具體一個參數。例如 org.geoserver.wms.kvp.GetMapKvpRequestReader就負責將GetMap的參數轉換成 org.vfny.geoserver.wms.requests.GetMapRequest對象。

    這一步的擴展點就是以上兩個Reader,來看WMS中GetMap Reader的定義

    <bean id="getMapKvpReader"         class="org.geoserver.wms.kvp.GetMapKvpRequestReader">         <constructor-arg ref="wms"/>     </bean> 
    <bean id="getMapXmlReader"         class="org.geoserver.wms.xml.WMSXmlRequestReaderAdapter">        <constructor-arg index="0" value="http://www.opengis.net/ows"/>          <constructor-arg index="1" value="GetMap"/>         <constructor-arg index="2" ref="wms"/>        <constructor-arg index="3" value="org.vfny.geoserver.wms.requests.GetMapXmlReader"/>    </bean> 

與前面的擴展一樣,我們只需要把我們設計的相關Reader註冊到系統中,程序就會自己找到它。下面說說匹配Reader的算法。

    第二步提到了一個service對象,除了知道它是一個Object之外,我們並沒有過多說明。實際上,Operation類會持有這個對象,並且從裏面 查找與註冊的操作同名的公共成員函數,這個函數將通過反射來調用。顯然,我們提供給這個函數的參數必須符合它聲明的參數類型。所以,匹配KVP Reader的算法就是匹配參數類型的過程。DefaultWebMapService的getMap函數的簽名如下:

public GetMapResponse getMap(GetMapRequest request)

程序會遍歷所有註冊的GetMapKvpRequestReader,將註冊函數的參數類型與org.geoserver.ows.KvpRequestReader.getRequestBean()的返回值比較,如果兩者可以交換(Assignable)則匹配成功。

    XML Reader的匹配與KVP Reader完全不同,這一點很奇怪,它是根據註冊的操作名稱,服務ID和服務版本來匹配的。

    至於執行操作,實在沒什麼需要特殊說明的,就是調用方法而已,唯一值得注意的是它的返回值,因爲我們要把它寫到返回流(Response Stream)中。而這是下一步的事情了。

第四步 返回結果(見org.geoserver.ows.Dispatcher.response(Object, Request, Operation))

    現在需要把結果返回給客戶端了,這個步驟叫做Response。這一步的擴展點是一個叫org.geoserver.ows.Response的類,程序 會遍歷所有註冊的Response類(與前面的那些匹配完全一樣),比較返回值的類型與 org.geoserver.ows.Response.getBinding()的值,如果兩者可以交換(Assignable)則匹配成功。匹配成功 後就調用org.geoserver.ows.Response.write(Object, OutputStream, Operation)函數回寫結果。下面是WMS GetMap的Response配置信息

    <bean id="getMapResponse"         class="org.geoserver.ows.adapters.ResponseAdapter">         <constructor-arg value="org.vfny.geoserver.wms.responses.GetMapResponse"/>         <constructor-arg ref="geoServer"/>     </bean>

這裏用到了一個叫ResponseAdapter的類主要是爲了適配接口。

    到此OWS的處理就介紹完了,下面來看看GeoServer的資源API。

二 資源對象模型

    這是我起的名稱,實際上是一套接口。在包“main”的命名空間org.geoserver.catalog下面有許多接口描述了GeoServer中許多基本概念,搞清楚這些是學習GeoServer的關鍵之一。

    特別需要說明一下,GeoServer所有的資源都派生自接口org.geoserver.catalog.Info,它唯一的方法是 org.geoserver.catalog.Info.getId()。這說明,GeoServer裏面所有的資源都有一個全局唯一的ID。我們會在後 面的文章中詳細介紹,包括它的產生和保存。

1 Catalog

    這裏有一段摘錄自main配置文件的腳本

    <bean id="rawCatalog" class="org.geoserver.catalog.impl.CatalogImpl">          <property name="resourceLoader" ref="resourceLoader"/>       </bean>     <bean id="secureCatalog" class="org.geoserver.security.SecureCatalogImpl">         <constructor-arg ref="rawCatalog" />     </bean>     <!-- Switch this when you want to enable the secure catalog by default -->     <alias name="secureCatalog" alias="catalog"/> 

裏面定義了一個叫“catalog”的變量,下面是一個引用它的例子

    <bean id="geoServer" class="org.geoserver.config.impl.GeoServerImpl">       <property name="catalog" ref="catalog"/>     </bean>

這就是我們要說的Catalog。查看Catalog接口的代碼,會看到它定義了許多函數(代碼太長就不貼出了),這些函數基本涵蓋了“增刪改查”所有的方法,而每一套“增刪改查”都對應了我們將要介紹一個概念,例如這一段:

    void add(LayerInfo layer);     void remove(LayerInfo layer);     void save(LayerInfo layer);     LayerInfo getLayer(String id);

後面還有很多getLayer方法就不贅述了。

    我們可以這樣定義:Catalog是一個抽象概念,它提供了一套訪問GeoServer資源的方法,通過這些方法程序可以對GeoServer的資源進行“增刪改查”的操作,而無需知道資源的具體保存形式。當然,目前唯一的實現就是CatalogImpl,但是我們完全可以用我們自己的Catalog來替換它,只需要修改一下上面的配置信息就可以了。

    需要說明的是,很多時候我們都是通過GeoServer這個對象來獲得Catalog的,下面我們就來說說GeoServer。

2 GeoServer

    它的完整名稱是org.geoserver.config.GeoServer,根據註釋的解釋,它是用來訪問GeoServer服務器的配置信息的接 口,而它的名稱也反映出這個特點。我們可以通過它來獲得與具體服務無關的數據,例如服務的字符集,服務的聯繫人,發佈了哪些OWS服務等。當然還有服務的 資源接口Catalog。在自定義的配置文件裏,可以用“geoServer”來引用它。

3 Layer

   Layer是空間數據源與表現樣式的組合,WMS GetMap中我們指定的參數LAYERS指的就是它。org.geoserver.catalog.LayerInfo是它的代碼形式。通過這個接口, 我們可以訪問與Layer相關聯的資源,主要有空間數據源(Resource),樣式(Style),圖例(Legend)等。Layer可以相互嵌套形 成LayerGroup,LayerGroup在行爲上與Layer完全一樣,這是一個組合模式的應用。

4 Resource

    這裏的Resource並非泛指的資源,而是與空間相關聯的資源,所以org.geoserver.catalog.ResourceInfo中有訪問空 間參考(SRS或CRS)的方法。另外,通過這個接口我們可以訪問資源的Store(又是一個概念,我們姑且就使用原文),也就是資源的存儲器。 GeoServer中有兩個概念是從Resource派生來的,Coverage和FeatureType,並且它們有各自的Store。

5 Store

    Store表示Resource的存儲。org.geoserver.catalog.StoreInfo是它的代碼形式,最重要的函數是 org.geoserver.catalog.StoreInfo.getConnectionParameters(),返回連接參數,參數的具體含義 由具體的存儲介質來決定。Coverage和FeatureType都有各自的 Store,org.geoserver.catalog.CoverageStoreInfo和 org.geoserver.catalog.DataStoreInfo。

6 Coverage

    Coverage一直是讓我迷惑的概念,我把wiki的解釋原文抄錄在這裏:In geographic information systems, a coverage is a mapping of one aspect of data in space.大意是:在GIS領域,一個Coverage就是一附地圖,它反應了空間數據的一個方面。(很籠統是吧,如果你有準確的解釋,希望你能發給我,我將不勝感激)。org.geoserver.catalog.CoverageInfo是這個概念的代碼形式。

7 FeatureType

    要解釋FeatureType就必須先解釋Feature。Feature,即要素,是一個具有空間意義的實體,並且擁有附加的屬性。例如:某個城市,它 的位置是東經116.46度 北緯 39.92 度,它的名字是“北京”,它的常住人口是1972萬。我們就可以用一個要素來表示它,這個要素是一個點,點的座標是[116.46 39.92],它的屬性表示爲

屬性名稱

屬性值

Name 北京
Popu 1972

如果我們有許多城市數據,都具有相似的特徵,我們就可以定義一個叫“CITIES”的FeatureType,它的幾何類型是Point(點),它的屬性Scehma是

屬性名稱

值類型

Name 字符串
Popu 數值

    可以把FeatureType與Feature的關係想象爲類與類實例的關係。也可以把它們的關係想象爲數據表與數據記錄的關係。後者其實更實用,因爲許多時候Feature數據就是以表的形式組織和訪問的。

    org.geoserver.catalog.FeatureTypeInfo是它的代碼形式。它最重要的方法就是 org.geoserver.catalog.FeatureTypeInfo.getFeatureSource(ProgressListener, Hints),這個方法會返回數據源,我們可以用這個數據源來查詢Feature。

8 Style

    Style,可以翻譯成渲染樣式,提供了一套方法用來描述如何渲染Feature。WMS GetMap中我們指定的參數STYLES指的就是它。OGC的標準SLD提供了一種語言用來描述Style。GeoServer採用了這個語言,其他很多GIS平臺也支持這個語言。推薦我的一篇文章《OGC之路(2) 之 Style之謎》說得比較詳細,還附有代碼。

    org.geoserver.catalog.StyleInfo是它的代碼形式。它最重要的方法就是 org.geoserver.catalog.StyleInfo.getStyle(),返回樣式對象 org.geotools.styling.Style。我們可以通過它來獲得SLD裏面定義的元素。

    至此,我把GeoServer的結構做了一個簡單的介紹,從OWS處理請求的方式到GeoServer資源訪問的API。其實GeoServer的結構遠 不止這麼簡單,它還提供了一套完整的管理服務以及相關GUI,這個部分不是我們介紹的內容,所以在此略過,但是作爲用戶接觸最頻繁的部分,它的結構還是很 值得研究的。此外,GeoServer保存資源的方式也很重要,GeoServer採用文件目錄結構來分類保存各種資源,並且設置了訪問權限,我們將在下 一章對此做詳細說明。

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