程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 精通Grails: 了解插件

精通Grails: 了解插件

編輯:關於JAVA

在開始階段,精通 Grails 主要著眼於核心 Grails 功能。對如何將基礎部件組合在一起了解得越多 ,將其結合起來構建一個完善的產品應用程序就會變得越容易。盡管我前面多次提到過插件,但我均有意 回避了對插件做深入的介紹。現在,該是介紹的時候了。

在接下來的幾篇系列文章中,我將與您一起探索 Grails 插件系統。最早,Grails 平台的構建對可插 入性是有所考慮的。正因為有了這個雖小卻十分重要的考慮,我們才能很方便地利用上百個預捆綁的功能 塊。

在本文寫作之時,清單 1 所示的 Groovy 腳本已經能夠返回 225 個插件。(有關此腳本如 何工作的詳細信息,請參見 “實戰 Groovy:構建和解析 XML”。)

清單 1. 計算可用 Grails 插件的簡單 Groovy 腳本

def addr = "http://plugins.grails.org/.plugin-meta/plugins-list.xml"
def  plugins = new XmlSlurper().parse(addr)
def count = 0
plugins.plugin.each{
 println it.@name
 count++
}
println "Total number of plugins:  ${count}"

要獲得一個更為友好的列表,可以在命令提示符後鍵入 grails list-plugins 或訪問 Grails Plugins 站點。

何為插件?

老練的 Java™ 開發人員都是一些精明的探尋者和收集者。他們從不夢想著去編寫自已的日志庫 ;而是簡單地把 log4j JAR 放入其類路徑。需要一個 XML 解析器嗎?那好,將 Xerces JAR 添加到您的 項目中即可。這些可插入的功能塊是面向對象編程的可重用性的一種體現。

Grails 插件可服務於同樣的目的,不過,規模更大。它們可以包括很多 JAR 及 GroovyServer Page (GSP)、控制器、TagLib、服務等。就像 SiteMesh 將兩個 GSP 合並成一個一樣,插件可以將兩個或多個 Grails 應用程序合並成一個。這樣您就可以將更多的精力用於核心業務需求,在需要的時候,從外部資 源加入所需的額外功能 — 查詢、認證、備用表示層等。

此外,插件實質上也是外部 資源。雖然 Grails 開發團隊已經編寫了一些有價值的插件,但絕大多數 插件仍來自於社區。實際上,Grails 團隊一直致力於在適當的時候將其核心功能整合進插件,這就使得 Grails 自身在每次發布的時候都更小也更為穩定。

那麼如何將其應用到 Blogito — 您在本系列中逐步構建的這個 “小型博客” 應用程序中呢?假設 您想添加的下一個功能是本地搜索功能。並且您願意采用一個現有的解決方案而不是從頭構建一個您自已 的搜索基礎架構,那麼請往下看。

安裝一個搜索插件

這個搜索插件能為您的應用程序帶來類似 Google 那樣的搜索能力。它使用 Apache Lucene 創建索引 ,用 Compass 將索引鉤掛到 Grails Object Relational Mapping (GORM)/Hibernate 生命周期。這就意 味著每當您創建、更新或刪除一個 domain 類時,Lucene 索引都會相應更新。

要想安裝此插件,請鍵入 grails install-plugin searchable。(下一章節將會深入介紹安裝插件時 的技術細節。)

接下來,將這行代碼 — static searchable = true — 添加到 grails-app/domain/Entry.groovy, 如清單 2 所示:

清單 2. 讓 Entry 類成為可查詢的

class Entry {
  static searchable = true

  static constraints = {
   title()
   summary(maxSize:1000)
   filename(blank:true, nullable:true)
   dateCreated()
   lastUpdated()
  }

  static mapping = {
   sort "lastUpdated":"desc"
  }

  static belongsTo = [author:User]

  String title
  String summary 
  String filename
  Date dateCreated
  Date lastUpdated
}

請注意:必須要顯式地讓 domain 類變成可搜索的。這意味著您可以繼續將基礎架構數據,比如登錄 和密碼,保存在隱藏的 User 類中。

有了這一行代碼,就為 Blogito 賦予了 Lucene 和 Compass 的強大功能。鍵入 grails run-app,啟 動這個應用程序,然後訪問 http://localhost:9090/blogito/searchable。鍵入一個搜索關鍵詞,比如 grails,看一下搜索結果,如圖 1 所示:

圖 1. 默認的搜索結果

雖然搜索出一些結果,但結果不容易描述。要解決這個問題,可以為 Entry.groovy 添加一個 toString() 方法,如清單 3 所示:

清單 3. 為 Entry 添加一個 toString()

class Entry {
  static searchable = true

  //snip

  String toString(){
   "${title} (${lastUpdated})"
  }
}

再次搜索 grails 。這次的結果的用戶友好性會有所提高,如圖 2 所示:

圖 2. 用 toString() 方法得到的搜索結果

這個可搜索插件的原始功能已經就緒,現在可以采取下一個步驟了:將它深入地集成到您的應用程序 內。

探索這個插件的基礎架構

縱覽 Blogito 的所有目錄,這裡似乎沒有任何新的文件。如果通過 Web 浏覽器訪問 http://localhost:9090/blogito/searchable,那裡應該會有一個 grails- app/controllers/SearchableController.groovy 文件。但奇怪的是,該文件不在那裡。在 lib 目錄中 也應該有一些 Lucene 與 Compass 的 JAR 文件,但它一如您首次鍵入 grails create-app 啟動這個項 目時一樣,是空的。實際上,對 Blogito 的惟一更改就是在 application.properties 中加入的這一行 新代碼,如清單 4 所示:

清單 4. application.properties,顯示了新安裝的 Searchable 插件

#utf-8
#Wed Jun 24 15:41:16 MDT 2009
app.version=0.4
app.servlet.version=2.4
app.grails.version=1.1.1
plugins.searchable=0.5.5
plugins.hibernate=1.1.1
app.name=blogito

通過 plug-ins.searchable 這一行代碼,可以判斷 Blogito 已經知曉 Searchable 插件的存在。那 麼所有這些功能都藏在哪了?要想查明,需返回到第一次安裝此插件時一閃而過的那個屏幕輸出。接下來 ,我將帶您探個究竟。

當鍵入 grails install-plugin searchable 後,所發生的第一件事情是向 Web 發出一個請求來拉出 插件的最新列表,如清單 5 所示:

清單 5. 下載插件的主列表

$ grails install-plugin searchable
//snip

Reading remote plugin list ...
  [get] Getting: http://svn.codehaus.org/grails/trunk/grails-plugins/
    .plugin-meta/plugins-list.xml
  [get] To: /Users/sdavis/.grails/1.1.1/plugins-list-core.xml
  [get] last modified = Mon Jun 22 04:16:31 MDT 2009

Reading remote plugin list ...
  [get] Getting: http://plugins.grails.org/.plugin-meta/plugins-list.xml
  [get] To: /Users/sdavis/.grails/1.1.1/plugins-list-default.xml
  [get] last modified = Wed Jun 24 06:51:24 MDT 2009

這兩個列表 — core 和 default — 提供了這些插件的元數據,包括作者、描述和版本號 。更重要的是,在這裡,Grails 可以發現實際包含這些插件的 ZIP 文件所對應的 URL。清單 6 顯示了 來自於 plugins-list-core.xml 文件的有關 Hibernate 插件的信息:

清單 6. Hibernate 插件 的描述
<plugins revision="9011">
 <plugin latest-release="1.1.1"  name="hibernate">
  <release tag="RELEASE_1_1" type="svn"  version="1.1">
   <title>Hibernate for Grails</title>
    <author>Graeme Rocher</author>
   <authorEmail/>
    <description>A plugin that provides integration between
    Grails and  Hibernate through GORM</description>
    <documentation>http://grails.org/doc/$version</documentation>
     <file>http://svn.codehaus.org/grails/trunk/grails-plugins/ 
     grails- hibernate/tags/RELEASE_1_1/grails-hibernate-1.1.zip 
    </file>
   </release>
  <!-- snip -->
 </plugin>
</plugins>  

目前,Hibernate 插件是核心插件文件內所列的惟一一個插件。這個列表包含了必需 插件 — Grails 運行所不能或缺的功能。默認列表包括了來自於社區的可選插件。

您是否 注意到 清單 5 中這些文件保存的位置?在主目錄中(在類似 UNIX® 的系統上,主目錄為 /Users/ 任何人;在 Windows® 系統上,主目錄為 C:\Documents and Settings\任何人)創建了一個 .grails 目錄。這個目錄內保存了在鍵入 grails run-app 時被編譯的那些類。當鍵入 grails clean 時 ,projects 下的 application 目錄會被刪除。但是,如您所見,.grails 也是存放下載插件的地方。用 文件編輯器打開 .grails/1.1.1/plugins-list-default.xml 並找到 Searchable 插件這一項。請見清單 7:

清單 7. Searchable 插件的描述

<plugin latest-release="0.5.5"  name="searchable">
  <release tag="RELEASE_0_5_5" type="svn" version="0.5.5">
   <title>Adds rich search functionality to Grails domain models.
    This version is recommended for JDK 1.5+</title>
   <author>Maurice Nicholson</author>
   <authorEmail>[email protected]</authorEmail>
   <description>Adds rich search functionality to Grails domain models.
    Built on Compass (http://www.compass-project.org/) and Lucene
    (http://lucene.apache.org/)
    This version is recommended for JDK 1.5+
   </description>
   <documentation>http://grails.org/Searchable+Plugin</documentation>
   <file>http://plugins.grails.org/grails-searchable/
    tags/RELEASE_0_5_5/grails-searchable-0.5.5.zip</file>
  </release>

  <!-- snip -->
</plugin>

一旦 Grails 發現了從哪裡可以下載這些插件,它(理所當然)會將這些所需要的插件下載到 .grails/1.1.1/plugins,如清單 8 所示:

清單 8. 下載插件

$ grails install-plugin searchable
  //download core and default plugin lists

  // continued...
  [get] Getting: http://plugins.grails.org/grails-searchable/
  tags/RELEASE_0_5_5/grails-searchable-0.5.5.zip
  [get] To: /Users/sdavis/.grails/1.1.1/plugins/grails-searchable-0.5.5.zip
  [get] last modified = Thu Jun 18 22:24:45 MDT 2009

最後,Grails 會從本地緩存中將這些插件復制到您的項目並進行解壓縮,如清單 9 所示:

清單 9. 向您的項目中添加插件

$ grails install-plugin searchable
  //download core and default plugin lists
  //download requested plugin

  // continued...
  [copy] Copying 1 file to /Users/sdavis/.grails/1.1.1/projects/blogito/plugins
  Installing plug-in searchable-0.5.5
  [mkdir] Created dir:
    /Users/sdavis/.grails/1.1.1/projects/blogito/plugins/searchable-0.5.5
  [unzip] Expanding:
    /Users/sdavis/.grails/1.1.1/plugins/grails-searchable-0.5.5.zip into
    /Users/sdavis/.grails/1.1.1/projects/blogito/plugins/searchable-0.5.5

>

更深一步研究

從 Grails 1.1 的發布說明中可以找到關於此插件基礎架構的更多信息。從中可以了解如何安裝全局 性的插件(以便所創建的每個新的項目都會自動包含這些特定的插件)、如何向列表中添加替代插件存儲 庫、如何限制插件只在特定環境中運行或是只針對特定的 Grails 命令行腳本運行,等等。

不過,在您進行太過深入的研究之前,請務必確保這對您來說具有實際意義。在 application.properties 內的行對應於 .grails 內的 project 目錄中的解壓縮目錄。這就意味著要想 卸載一個插件,可以鍵入 grails uninstall-plugin myplugin ,或者干脆將這一行從 application.properties 中刪除並手動地從 .grails 的 project 目錄中將這個目錄刪除。

插件以簡單的 ZIP 文件來回傳遞,知曉這一點非常重要。在下一篇文章中,我將向您展示如何創建您 自已的插件並通過一個本地 ZIP 文件(grails install-plugin myplugin /local/path/to/myplugin.zip)來安裝這個插件。您甚至可以通過一個遠程 URL — grails install- plugin myplugin http://somewhere.com/myplugin.zip 來安裝這個插件。

對 Searchable 插件的探討

知道了 Searchable 插件安裝的位置(.grails/1.1.1/projects/blogito/plugins/searchable-0.5.5 )後,我們就可以對它進行探討了。這個目錄結構(如圖 3 所示)應該看上去有點眼熟 — 插件和應用 程序共享同樣的基礎布局:

圖 3. 目錄結構

SearchableController 恰恰處於我們想要的位置:grails-app/controllers。在一個文件編輯器中打 開這個文件。清單 10 顯示了部分源代碼:

清單 10. SearchableController

import  org.compass.core.engine.SearchEngineQueryParseException
class SearchableController {
  def searchableService

  def index = {
   if (!params.q?.trim()) {
    return [:]
   }
   try {
    return [searchResult: searchableService.search(params.q, params)]
   } catch (SearchEngineQueryParseException ex) {
    return [parseException: true]
   }
  }

  //snip
}

如您所見,SearchableService 在類被聲明後被注入到此控制器。這個熟悉的 index 動作就是默認的 目標。如果沒有傳遞進 q 參數,就會將一個空的 hashmap 返回給 grails- app/views/searchable/index.gsp。基於視圖中的邏輯,它將顯示一個空白頁。

在 index.gsp 的第 100 行左右的位置,應該能夠找到一個表單,它可設置 q 參數及遞歸地將自身提 交回 index 動作。清單 11 顯示了這個表單:

清單 11. index.gsp 中的 searchable 表單

<g:form url='[controller:  "searchable", action: "index"]'
     id="searchableForm"
     name="searchableForm"
     method="get">
  <g:textField name="q" value="${params.q}" size="50"/>
  <input type="submit" value="Search" />
</g:form>

回過頭,再看看 清單 10,可以發現一旦 q 參數內有了一個搜索條件,searchableService.search() 調用的結果就會被返回給 index.gsp。在 index.gsp 中的第 150 行左右,會顯示這些結果,如清單 12 所示:

清單 12. 顯示搜索結果

<g:if test="${haveResults}">
  <div class="results">
   <g:each var="result" in="${searchResult.results}" status="index">
    <div class="result">
     <g:set var="className" value="${ClassUtils.getShortName(result.getClass())}" />
     <g:set var="link"
         value="${createLink(controller: className[0].toLowerCase() +
          className[1..-1],
         action: 'show',
         id: result.id)}" />
     <div class="name"><a href="${link}">${className} #${result.id} </a></div>
     <g:set var="desc" value="${result.toString()}" />
     <g:if test="${desc.size() > 120}">
      <g:set var="desc" value="${desc[0..120] + '...'}" />
     </g:if>
     <div class="desc">${desc.encodeAsHTML()}</div>
     <div class="displayLink">${link}</div>
    </div>
   </g:each>
  </div>

  <!-- snip -->
</g:if>

我鼓勵您更深入地去探索 Searchable 插件的奧秘。請見 grails- app/services/SearchableService.groovy。注意到 lib 目錄中已經包含了 Lucene 和 Compass 的 JAR 文件。到 src/java 和 src/groovy 目錄去看看所有支持的類。再回顧一下 tests 目錄中的 GroovyTestCase。一個典型 Grails 應用程序的所有部分都在這個插件裡。

每當安裝一個新插件,都要留意一下它的實現。這將有助於您識別所有可移動部分、了解它們是如何 組合起來發揮作用的,並且 — 最重要的是 — 給您啟示,教您如何能更好地將它們融入到您的應用程序 中。接下來的一節,您將看到如何將搜索功能從默認實現轉到您自已的定制組件中。

將搜索更深入地並入到 Blogito

下面教您如何添加對 Entries 的搜索。首先,在一個文本編輯器內打開 grails- app/controllers/EntryController.groovy。添加一個簡單的 search 動作,如清單 13 所示。(別忘了 要允許未經身份驗證的用戶通過向 beforeInterceptor 添加 search 動作來進行博客條目的搜索。)

清單 13. 添加 search 動作

class EntryController {

  def beforeInterceptor = 
    [action:this.&auth, except:["index", "list", "show", "atom", "search"]]

  def search = {
   render Entry.search(params.q, params)
  }

  //snip
}

正如在前一章節所展示的那樣,SearchableService 非常適合用來進行跨所有域類的站點級別的搜索 。但 Searchable 插件也可以在您個人的域類上做一些元編程。正像 Grails 可以動態地添加 list()、 get() 和 findBy() 方法一樣,Searchable 插件可以添加一個 search() 方法。

通過在 Web 浏覽器中鍵入 http://localhost:9090/blogito/entry/search?q=groovy 來測試新的 search 動作。應該會看到一個搜索結果的 hashmap 圖,類似於圖 4:

圖 4. 顯示原始的搜索結果

知道了 search() 方法的工作原理後,下一步是要讓用戶界面更為友好一點。在 grails- app/views/layouts 中創建一個名為 _search.gsp 的局部模板。加入清單 14 中的代碼:

清單 14. 局部模板

<div id="search">
  <g:form url='[controller: "entry", action: "search"]'
      id="searchableForm"
      name="searchableForm"
      method="get">
   <g:textField name="q" value="${params.q}" />
   <input type="submit" value="Search" />
  </g:form>
</div>

請注意,在上述代碼中,控制器被設為 entry,動作被設為 search。

接下來,該顯示這個局部模板了。在一個文本編輯器內打開 grails-app/views/layouts/_header.gsp 並添加一個 render 標簽,如清單 15 所示:

清單 15. 為 header 添加這個搜索模板

<g:render  template="/layouts/search" />

<div id="header">
  <p><g:link class="header-main"  controller="entry">Blogito</g:link></p>
  <p class="header-sub">
   <g:link controller="entry" action="atom">
   <img src="${createLinkTo(dir:'images',file:'feed-icon-28x28.png')}"
     alt="Subscribe" title="Subscribe"/>
   </g:link>
   A tiny little blog 
  </p>

  <div id="loginHeader">
   <g:loginControl />
  </div>
</div>

給 web-app/css/main.css 添加一些 Cascading Style Sheets (CSS) 以確保 search <div> 可以浮在屏幕的右上角,如清單 16 所示:

清單 16. 添加 CSS 來調整搜索表單的位置

#search {
  float: right;
  margin: 2em 1em;
}

所有視圖變化均完成後,請刷新浏覽器。屏幕看上去應該如圖 5 所示:

圖 5. 給 header 添加搜索表單

需要做的最後一件事情就是以 HTML 格式提交 search 結果,而不是簡單的調試輸出。調整 EntryController 內的 search 動作,如清單 17 所示:

清單 17. 一個更健壯的搜索動作

def search = {
  //render Entry.search(params.q, params)
  def searchResults = Entry.search(params.q, params)
  flash.message = "${searchResults.total} results found for search: ${params.q}"
  flash.q = params.q
  return [searchResults:searchResults.results, resultCount:searchResults.total]
}

由於該動作被命名為 search,因此需要在 grails-app/views/entry 中創建對應的 search.gsp 文件 ,如清單 18 所示:

清單 18. Search.gsp

<html>
  <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
   <meta name="layout" content="main" />
   <title>Blogito</title>
  </head>
  <body>
   <g:if test="${flash.message}">
    <div class="message">${flash.message}</div>
   </g:if>

   <div class="body">
    <div class="list">
     <g:each in="${searchResults}" status="i" var="entry">

     <div class="entry">
      <h2>
       <g:link action="show"
           id="${entry.id}">${entry.title}</g:link>
      </h2>
      <p>${entry.summary}</p>
     </div>

     </g:each>
    </div>
   </div>
   <div class="paginateButtons">
    <g:paginate total="${resultCount}" params="${flash}"/>
   </div>
  </body>
</html>

在 Web 浏覽器中最後做一次 grails 搜索。搜索結果應該如圖 6 所示:

圖 6. HTML 格式的搜索結果

結束語

插件是 Grails 體系中最令人興奮、最為活躍的一部分。它們可以讓您坐享各式各樣的現成功能。一 旦您掌握了自己的代碼庫(application.properties 和 .grails 目錄)的觸點所在,您就可以研究源代 碼以更好地理解插件作者是如何實現魔法的,同時還能為如何與您自已的代碼進行深入集成找到靈感。

下一次,我將向您展示如何創建一個自已的插件。到那時,請享受精通 Grails 的樂趣吧!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved