程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Groovy使Spring更出色,第2部分: 在運行時改變應用程序的行為

Groovy使Spring更出色,第2部分: 在運行時改變應用程序的行為

編輯:關於JAVA

用 Groovy 為 Spring 應用程序添加可動態刷新的 bean

簡介:Spring Framework 為 Web 和企業應用程序提供堅實的基礎。通過支持 Groovy 等動態語言, Spring 添加了一些功能,從而使應用程序架構更加靈活、更具動態性。在這個 通過 Groovy 使 Spring 更出色 系列的第 2 期也是最後一期中,您將學習如何使用可動態刷新的 bean 在運行時改變 Spring 應用程序的行為。

在這個包含 2 部分的系列的 第 1 部分,您看到了如何使用 Groovy bean 使 Spring 應用程序更加 靈活。Spring 的 Groovy 支持使您可以使用編譯後的或腳本化的 Groovy 語言 bean,並通過不同的方 式配置它們,包括使用 lang XML 模式和 Grails Bean Builder。當把 Groovy 腳本集成到應用程序中 時,就可以在 bean 創建過程中包括額外的邏輯(例如,確定當創建一個 bean 時使用哪種實現策略) 。還可以使用不同的腳本化 Groovy bean 為部署和打包提供更多的靈活性。

Spring 的動態語言支持中最有趣、最強大的特性也許就是在運行應用程序時 能夠監視和檢測對動態 語言腳本的更改,並將被更改的 bean 自動重新裝載 到 Spring 應用程序上下文 中。在一個正在運行的 應用程序中自動刷新 Spring bean,這樣的用例有很多。下面是一些例子:

PDF 生成(賬單、發票、銷售報告、投資報告、收據、日程安排等)

e-mail 模板

報告生成

外部化業務邏輯、特定領域語言(DSL)和規則引擎

系統管理任務

更改日志記錄級別和運行時調試

相信您可以想到更多的應用。本文展示如何將 bean 刷新添加到 Spring 應用 程序中,並探索它是如 何工作的。本文中所有例子的完整源代碼(參見 下載)都可以下載獲得。

可刷新的 Groovy bean

在 第 1 部分 中,您定義了 PdfGenerator 接口,並在 GroovyPdfGenerator.groovy 腳本文件中用 一個 Groovy 類(GroovyPdfGenerator)實現了它,這個腳本文件位於應用程序 的 CLASSPATH 中。您通 過指定 Groovy 腳本所在的位置,配置了基於 Groovy 的 pdfGenerator bean。 清單 1 顯示了這個接口 、它的實現以及使用 lang XML 模式的配置:

清單 1. PdfGenerator 接口、實現和配置

// PdfGenerator.java
public interface PdfGenerator {
   byte[] pdfFor(Invoice invoice);
}

// GroovyPdfGenerator.groovy
class GroovyPdfGenerator implements PdfGenerator {

   String companyName

   public byte[] pdfFor(Invoice invoice) {
     ... 
   }

}

// applicationContext.xml
<lang:groovy id="pdfGenerator"
        script- source="classpath:groovierspring/GroovyPdfGenerator.groovy">
   <lang:property name="companyName" value="Groovy  Bookstore"/>
</lang:groovy>

到目前為止,一切良好。您有一個名為 pdfGenerator 的 bean,它是用 Groovy 實現的,位於應用 程序 CLASSPATH 中。當創建 Spring 應用程序上下文時,Spring 讀取腳本,將 它編譯成 Java 類,並 在應用程序上下文中實例化一個 GroovyPdfGenerator。任何其他依賴 pdfGenerator 的類只需將它聲明 為一個依賴,Spring 會將它們連在一起。

Spring 如何檢測腳本修改

在內部,Spring 使用一個 Spring AOP RefreshableScriptTargetSource 攔 截對目標對象 (pdfGenerator bean)的調用,執行重新裝載檢查,並獲取一個更新版本的 bean。基本上,依賴可刷 新 bean 的 bean 都擁有對一個 AOP 代理而不是 bean 本身的引用。

事情變得真正有趣起來。假設您經常要在應用程序正在運行時對 PDF 生成代 碼進行更改,並使這些 更改立即生效。Spring 使得這種功能的添加變得很簡單。您只需為定義 bean 的 <lang:groovy> 元素添加 refresh-check-delay 屬性。該屬性定義一個毫秒數,每過這麼長時間 ,Spring 檢查對底層 Groovy 腳本的更改。如果檢測到對腳本的更改(例如,自上次檢查後,.groovy 腳本上的時間戳被改變 ),那麼 Spring 讀取腳本,編譯它,並用新的版本替換 舊的 pdfGenerator bean。Spring 這樣做時 ,任何使用 pdfGenerator 的 bean 都不需要知道這種變化。

清單 2 顯示了 pdfGenerator bean,它被配置了 10 秒(10,000 毫秒)的刷 新檢查延遲。添加 refresh-check-delay,之後,Spring 配置這個 bean,使之在底層 GroovyPdfGenerator.groovy 腳本 文件改變時自動刷新。

清單 2. 將 refresh-check-delay 添加到腳本化的 bean 定義中

<lang:groovy id="pdfGenerator"
        script- source="classpath:groovierspring/GroovyPdfGenerator.groovy"
        refresh-check-delay="10000">
   <lang:property name="companyName" value="Refreshable Groovy  Bookstore"/>
</lang:groovy>

現在,如果在應用程序正在運行時對 GroovyPdfGenerator.groovy 腳本做出 更改,Spring 將檢測到 這一更改,並在運行時重新裝載 pdfGenerator bean,而不必重新啟動。注意, 只有達到規定的延遲時 間,並且 可刷新 bean 上發生方法調用,才會發生刷新檢查。例如,假設 pdfGenerator bean 的刷新 檢查延時為 10 秒,但是連續 50 秒內沒有發生方法調用。在這種情況下, Spring 會在 50 秒之後(而 不是每過 10 秒)檢查是否需要刷新。換句話說,Spring 不會積極地輪詢腳本的 更改;相反,它判斷自 上次方法調用後經過的時間,然後計算這段時間是否超過刷新檢查延時。只有當 經過的時間超過刷新檢 查延時,Spring 才檢查腳本是否被更改,進而確定是否需要刷新。另一方面,假 設 pdfGenerator bean 處於較重的負載下,每一秒鐘它的方法被多次調用。如果 refresh-check-delay 為 10 秒,無論這個 bean 被使用多少次,它最快只能每 10 秒重新裝載一次。所以,不需要擔心 Spring 是否會因為積極地 輪詢 Groovy 腳本而消耗系統資源,它並沒有這樣做。

如果 Spring 應用程序中有不止一個腳本化的 Groovy bean,您想為所有這些 bean 的刷新檢查延時 設置一個默認值,那麼可以使用 <lang:defaults> 元素輕松做到這一點, 如清單 3 所示:

清單 3. 設置默認刷新檢查延時

<lang:defaults refresh-check-delay="20000"/>

通過使用清單 3 中顯示的 <lang:defaults>,所有 腳本化動態語言 bean(那些用 Groovy、 JRuby、BeanShell 等編寫的 bean)的刷新檢查延時都被設為 20 秒。對於要使 用不同值的 bean,只需 添加一個 refresh-check-delay 屬性覆蓋默認值。甚至可以通過將 refresh- check-delay 設置為一個 負值,關閉 單個腳本化的 bean 的自動刷新行為,如清單 4 所示:

清單 4. 覆蓋默認的 refresh-check delay

<lang:defaults refresh-check-delay="20000"/>

<lang:groovy id="pdfGenerator"
        script- source="classpath:groovierspring/GroovyPdfGenerator.groovy"
        refresh-check-delay="60000">
   <lang:property name="companyName" value="Refreshable Groovy  Bookstore"/>
</lang:groovy>

<lang:groovy id="invoiceEmailer"
        script- source="classpath:groovierspring/GroovyInvoiceEmailer.groovy"
        refresh-check-delay="-1"/>

在清單 4 中可以看到,默認的刷新檢查延時是 20 秒。但是,我已經將 pdfGenerator bean 的刷新 檢查延時配置為 60 秒,並且完全關閉了 invoiceEmailer bean 上的刷新檢查。

使用 Grails Bean Builder 配置可刷新 Groovy bean

在 第 1 部分,您看到了如何使用 Grails Bean Builder 通過編程的方式定 義 Spring bean。如果 使用 Bean Builder,可以比較輕松地為 bean 添加自動刷新 — 不過這樣一來, 就會更多地暴露 Spring 內部,因為 Bean Builder 和 <lang:groovy> 語法糖不同。清單 5 展示了如何為所有腳 本化 bean 添加一個默認的刷新檢查,以及如何為單個 bean 設置刷新延時:

清單 5. 使用 Grails Bean Builder 配置可刷新 Groovy bean

def builder = new grails.spring.BeanBuilder()
builder.beans {
   scriptFactoryPostProcessor(ScriptFactoryPostProcessor) {
     defaultRefreshCheckDelay = 20000
   }
   pdfGenerator(GroovyScriptFactory,
          'classpath:groovierspring/GroovyPdfGenerator.groovy') { bean - >
     companyName = 'Refreshable Bean Builder Bookstore'
     bean.beanDefinition.setAttribute(
       ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE, 60000)
   }
}

清單 5 中的 Bean Builder 配置在邏輯上等同於 清單 4 中的 pdfGenerator bean 的配置。您使用 ScriptFactoryPostProcessor bean 的 defaultRefreshCheckDelay 屬性為所有 腳本化 bean 設置了一 個默認的刷新檢查延時。在使用 Bean Builder 時,若要為單個的 bean 設置刷 新檢查延時,必須在底 層的 Spring bean 定義上設置一個屬性。如果使用 <lang:groovy> 基於 XML 的配置時,Spring 會負責底層的細節,而如果使用 Bean Builder,則需要您自己做這件事。注意, 為了在 bean 定義上設 置屬性,還需要為 pdfGenerator bean 上的閉包聲明一個 bean 參數。

定制 Groovy bean

您已經看到了如何使用 refreshable beans 特性使 Groovy bean 在運行時自 動更新,並使得應用程 序在運行時更加動態。為了使 Groovy bean 更加靈活,Spring 的 Groovy 支持 還提供了另一種方式: 定制。通過定制,可以將定制的邏輯注入到 Groovy bean 創建過程中。通過 GroovyObjectCustomizer 接口(清單 6 所示),可以在新創建的 GroovyObject 上執行定制邏輯:

清單 6. GroovyObjectCustomizer 接口

public interface  GroovyObjectCustomizer  {
   void customize(GroovyObject goo);
}

GroovyObjectCustomizer 是一個回調,Spring 在創建一個 Groovy bean 之 後會調用它。可以對一 個 Groovy bean 應用附加的邏輯,或者執行元編程,例如替換對象的元類。清單 7 展示了一個實現, 該實現輸出執行一個 Groovy bean 上的某個方法所花的時間:

清單 7. 性能日志記錄 GroovyObjectCustomizer

public class  PerformanceLoggingCustomizer implements GroovyObjectCustomizer  {

   public void customize(GroovyObject goo) {
     DelegatingMetaClass metaClass = new DelegatingMetaClass (goo.getMetaClass())  {
       @Override
       public Object invokeMethod(Object object, String  method, Object[]  args) {
         long start = System.currentTimeMillis();
         Object result = super.invokeMethod(object,  method, args);
         long elapsed = System.currentTimeMillis() -  start;
         System.out.printf("%s took %d millis on %s\n",  method, elapsed,  object);
         return result;
       }
     };
     metaClass.initialize();
     goo.setMetaClass(metaClass);
   }
}

清單 7 中的 PerformanceLoggingCustomizer 替換 GroovyObject 的元類, 並覆蓋 invokeMethod, 以便添加性能計時(performance-timing)邏輯。接下來,需要配置定制程序, 以便將它應用到一個或 多個 Groovy bean 上。清單 8 展示了如何使用 <lang:groovy> 中的 customizer-ref 屬性將一 個定制程序添加到一個已有的 Groovy bean 中:

清單 8. 配置一個 Groovy 對象定制程序

<bean  id="performanceLoggingCustomizer"
    class="groovierspring.PerformanceLoggingCustomizer"/>

<lang:groovy id="pdfGenerator"
   refresh-check-delay="60000"
   script- source="classpath:groovierspring/GroovyPdfGenerator.groovy"
   customizer-ref="performanceLoggingCustomizer">
   <lang:property name="companyName" value="Customized Groovy  Bookstore"/>
</lang:groovy>

現在,當 GroovyPdfGenerator 中的任何方法被調用時,您將在標准輸出中看 到如下所示的輸出。( 如果您正考慮使用一個日志記錄框架會更好,那麼您的想法是對的!)

pdfFor took 18 millis on  groovierspring.GroovyPdfGenerator@f491a6

為 Groovy bean 添加定制很簡單;較難的部分是實現實際的定制邏輯 — 也 就是說,當 Groovy bean 被創建時,您想對它們做什麼。您看到了使用 <lang:groovy> 和它 的 customizer-ref 屬 性的配置。如果您更喜歡使用 Grails Bean Builder 來構建 Spring bean,那麼 也很簡單。清單 9 展 示了如何添加 peformanceLoggingCustomizer bean:

清單 9. 使用 Grails Bean Builder 添加一個 Groovy 對象定制程序

builder.beans  {
   performanceLoggingCustomizer(PerformanceLoggingCustomizer)
   scriptFactoryPostProcessor(ScriptFactoryPostProcessor) {
     defaultRefreshCheckDelay = 20000
   }
   pdfGenerator(GroovyScriptFactory,
          'classpath:groovierspring/GroovyPdfGenerator.groovy',
          performanceLoggingCustomizer) { bean ->
     companyName = 'Refreshable Bean Builder Bookstore'
     bean.beanDefinition.setAttribute(
       ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE,  60000)
   }
}

更巧妙的數據庫

不需要使用 JAR,Spring 提供了對內聯腳本和通過 Spring Resource 抽象裝 載的腳本的支持。在 第 1 部分 中,您看到了內聯腳本和基於 Resource 的腳本 — 尤其是 CLASSPATH 資源。您使用 可刷 新 bean 添加了更多的動態行為。Spring 裝載、編譯和刷新動態語言 bena 的能 力依賴於 ScriptSource 接口,如清單 10 所示(不完整的 Javadocs):

清單 10. ScriptSource 接口

public interface ScriptSource  {

   String getScriptAsString() throws IOException;

   boolean isModified();

   String suggestedClassName();
}

ScriptSource 定義 3 個方法:一個方法獲取腳本源代碼,一個方法確定腳本 是否已被修改,還有一 個方法返回一個用於腳本的建議類名。Spring 為這個接口提供了兩種實現: StaticScriptSource 和 ResourceScriptSource。當在 Spring 配置文件中定義腳本時,可以使用 StaticScriptSource。 ResourceScriptSource 則用於從任何 Resource 裝載腳本(例如,從 CLASSPATH 上的文件中或從 URL 裝載腳本)。

可插拔腳本源代碼定位符

當我第一次實現將 Groovy 腳本存儲在數據庫中的功能時,我想到這種機制也 許應該是可插拔的,以 便用戶可以插入不同的 ScriptSource 實現和腳本定位符策略。我就此事咨詢了 SpringSource 的 Keith Donald,他表示贊同,並讓我向 Spring 提交一個新的特性請求。結果, 在 Spring 未來的一個 版本中(目前預定為 3.1RC1),腳本源代碼定位符機制將變成可插拔的。

靜態和基於 Resource 的腳本為定義腳本提供了很多位置,但是基於種種原因 ,您可能想使用數據庫 作為存放腳本的位置。例如,很多組織不允許對生產機器進行文件系統訪問,或 者他們可能需要 WAR 或 EAR 文件形式的部署。此外,數據庫是大多數組織已經在使用並且熟悉的事務性 資源。數據庫還為集中 式數據訪問提供了一種比較簡單的方式並可以保證安全性,這種方式不需要知道 關於文件系統、服務器 等的細節。最後,將腳本存儲在數據庫中意味著可以通過允許用戶編輯腳本來在 應用程序中更新腳本。 (當然,如果將活動的代碼存儲在一個數據庫中,那麼需要考慮潛在的安全性問 題,並適當地確保應用 程序的安全。)

假設您希望將 Groovy 腳本存儲在一個關系數據庫中。從 Spring 2.5 開始, 可以創建新的腳本類型 ,但是首先必須創建自己的 ScriptSource,並擴展一些 Spring 類。特別是,您 需要定義自己的 ScriptSource 實現,並修改 Spring 的 ScriptFactoryPostProcessor,使它知 道如何使用新的 ScriptSource 類型。

清單 11 實現一個 DatabaseScriptSource,它使用 Spring JDBC 從一個關系 數據庫裝載腳本:

清單 11. DatabaseScriptSource 實現

public class  DatabaseScriptSource  implements ScriptSource {

   private final String scriptName;
   private final JdbcTemplate jdbcTemplate;
   private Timestamp lastKnownUpdate;

   private final Object lastModifiedMonitor = new Object ();

   public DatabaseScriptSource(String scriptName, DataSource  dataSource) {
     this.scriptName = scriptName;
     this.jdbcTemplate = new JdbcTemplate(dataSource);
   }

   public String getScriptAsString() throws IOException {
     synchronized (this.lastModifiedMonitor) {
       this.lastKnownUpdate = retrieveLastModifiedTime();
     }
     return (String) jdbcTemplate.queryForObject(
         "select script_source from groovy_scripts where script_name =  ?",
         new Object[]{ this.scriptName },  String.class);
   }

   public boolean isModified() {
     synchronized (this.lastModifiedMonitor) {
       Timestamp lastUpdated = retrieveLastModifiedTime ();
       return lastUpdated.after(this.lastKnownUpdate);
     }
   }

   public String suggestedClassName() {
     return StringUtils.stripFilenameExtension (this.scriptName);
   }

   private Timestamp retrieveLastModifiedTime() {
     return (Timestamp) this.jdbcTemplate.queryForObject(
         "select last_updated from groovy_scripts where script_name =  ?",
         new Object[]{ this.scriptName },  Timestamp.class);
   }
}

清單 11 中的 DatabaseScriptSource 非常簡單,不過您可以讓它要求的數據 庫表結構更加通用。它 假設一個名為 groovy_scripts 的表有 script_name、script_source 和 last_updated 這幾個列。它 支持從 groovy_scripts 表裝載腳本和檢查修改情況。

現在,需要教會 Spring 識別 DatabaseScriptSource。為此,必須擴展 ScriptFactoryPostProcessor 並覆蓋 convertToScriptSource 方法,該方法負 責將一個腳本源代碼定 位符(例如 classpath:groovierspring/GroovyPdfGenerator.groovy)轉換成一 個 ScriptSource。清 單 12 顯示了 ScriptFactoryPostProcessor 中的默認實現:

清單 12. ScriptFactoryPostProcessor 的 convertToScriptSource 方法

protected  ScriptSource convertToScriptSource(
     String beanName, String scriptSourceLocator,  ResourceLoader resourceLoader)  {

   if (scriptSourceLocator.startsWith(INLINE_SCRIPT_PREFIX))  {
     return new StaticScriptSource(
         scriptSourceLocator.substring (INLINE_SCRIPT_PREFIX.length()),  beanName);
   }
   else {
     return new ResourceScriptSource(resourceLoader.getResource (scriptSourceLocator));
   }
}

可以看到,默認的實現只處理內聯和基於資源的腳本。還可以創建 ScriptFactoryPostProcessor 的 一個新的子類,並覆蓋 convertToScriptSource 方法,以便使用 DatabaseScriptSource 從數據庫裝載 腳本,如清單 13 所示:

清單 13. CustomScriptFactoryPostProcessor 實現

public class   CustomScriptFactoryPostProcessor extends ScriptFactoryPostProcessor  {

   public static final String DATABASE_SCRIPT_PREFIX =  "database:";

   private DataSource dataSource;

   @Required
   public void setDataSource(DataSource dataSource) {
     this.dataSource = dataSource;
   }

   @Override
   protected ScriptSource convertToScriptSource(String  beanName,
                          String  scriptSourceLocator,
                          ResourceLoader  resourceLoader) {
     if (scriptSourceLocator.startsWith(INLINE_SCRIPT_PREFIX))  {
       return new StaticScriptSource(
         scriptSourceLocator.substring (INLINE_SCRIPT_PREFIX.length()),  beanName);
     }
     else if (scriptSourceLocator.startsWith (DATABASE_SCRIPT_PREFIX)) {
       return new DatabaseScriptSource(
         scriptSourceLocator.substring (DATABASE_SCRIPT_PREFIX.length()),
         dataSource);
     }
     else {
       return new ResourceScriptSource(
         resourceLoader.getResource (scriptSourceLocator));
     }
   }

}

以上清單中的 CustomScriptFactoryPostProcessor 類似於 ScriptFactoryPostProcessor,但是, 如果腳本源代碼定位符以 database: 開始(例如 database:groovierspring/GroovyPdfGenerator.groovy),它還可以使用基於數 據庫的腳本。理想情況 下,這種機制將更加靈活。但是,就現在而言,您已經有了在數據庫中存儲 Groovy 腳本所需的東西。

最後要做的就是配置 pdfGenerator bean,以便從數據庫讀取它。首先,需要 使用 清單 13 中顯示 的 CustomScriptFactoryPostProcessor 定義一個 scriptFactoryPostProcessor bean。然後,使用數 據庫腳本源代碼定位符定義 pdfGenerator bean。既可以使用單純的 <bean/> 語法,也可以使用 更清晰的 <lang:groovy> 語法來定義 pdfGenerator bean。當使用 <lang:groovy> 時, Spring 檢查在名為 scriptFactoryPostProcessor 的應用程序上下文中是否有一 個 ScriptFactoryPostProcessor bean,如果沒有,則自動創建一個。如果 scriptFactoryPostProcessor 已經被定義,則 Spring 就使用這個 bean,這樣您就可以替換自己定制的實現。 清單 14 顯示了新的配 置:

清單 14. 數據庫 pdfGenerator bean 配置

<jee:jndi-lookup  id="dataSource"  jndi-name="jdbc/GroovierSpringDataSource"/>

<bean id="scriptFactoryPostProcessor"
    class="groovierspring.CustomScriptFactoryPostProcessor">
   <property name="dataSource" ref="dataSource"/>
</bean>

<lang:groovy id="pdfGenerator"
        refresh-check-delay="60000"
        script- source="database:groovierspring/GroovyPdfGenerator.groovy">
   <lang:property name="companyName" value="Database Groovy  Bookstore"/>
</lang:groovy>

清單 14 中的代碼並不比您在前面看到的代碼復雜多少。 scriptFactoryPostProcessor bean 要求注 入一個 DataSource,所以還要定義 dataSource bean。除此之外,惟一不同的地 方是基於 CLASSPATH 的腳本變成了數據庫中的腳本。如果您更傾向於使用 Grails Bean Builder,那 麼可以輕松地用它來配 置數據源和定制的 ScriptFactoryPostProcessor bean。

至此,您可以從數據庫裝載 Groovy 腳本,並在數據庫中的腳本被更改之後刷 新它們,這使得 Spring 原本已經靈活的 Groovy 支持變得更加靈活和動態。您還看到了如何添加 自己的 ScriptSource 實現,以允許從選擇的任何位置裝載腳本。

Groovy 腳本變壞

也許每個人都同意應該徹底地對應用程序進行測試,至於如何測試,大家卻各 執己見。例如,100% 的代碼覆蓋是必需的,還是可選的,抑或純粹是浪費時間?無論您個人的觀點如 何,當您突然有能力將 變化部署到一個正在運行的生產系統中,並且讓那些變化立即生效時(例如用 Spring 的動態語言支持 就可以做到),測試就變得非常重要。

如果您決定使用 refreshable beans 特性,那麼需要一個可靠的策略來確保 新的代碼能夠正確地、 符合預期地工作。至於如何有效地這樣做取決於您所處的環境:

系統的關鍵程度?

如果中斷某項內容,會有什麼影響?

修復問題的速度有多快?

您的特定環境可能還涉及更多方面的考慮,但是總而言之,bean-refresh 特 性既是強大的,但是又 存在潛在危險。您需要慎重地使用它。可能遇到的兩種主要問題是腳本編譯錯誤 和運行時錯誤。

腳本編譯錯誤

假設您在運行時更改一個腳本,使之不能編譯。當 Spring 檢測到更改時,它 嘗試重新裝載 bean, 這時會拋出一個 ScriptCompilationException,後者包裝原始的異常,例如一個 Groovy MultipleCompilationErrorsException。當出現這種情況時,Spring 不再嘗試重 新裝載 bean,原始的 bean 繼續使用,就像什麼事情也沒有發生一樣。您的應用程序需要適當地對 ScriptCompilationException 作出響應。很可能,您應該顯示某種錯誤消息,並 向開發人員或操作人員 發送一個通知(例如一條 e-mail 消息或即時消息)。當然,不管是誰部署腳本 更改,都應該監視應用 程序,以確保腳本成功編譯,並且新的 bean 替換舊的 bean。

但是沒有任何損失,因為沒有通過編譯的腳本對已經部署的 bean 沒有影響。 所以,您可以修復導致 編譯異常的問題,然後再次嘗試。當 bean 編譯成功時,Spring 用新的 bean 替 換已有的 bean,而這 對於應用程序代碼來說是透明的。現在,您的更改已經生效,這一切都不需要重 新部署或者重新啟動應 用程序。

運行時腳本錯誤

運行時腳本錯誤存在與編譯代碼拋出的運行時錯誤一樣的問題:它們導致應用 程序出現一個失敗條件 ,後者很可能被傳播到用戶,並導致他們嘗試執行的任何動作都失敗。例如,假 設您對 GroovyPdfGenerator 作了更改,使之可以編譯,但是每當它嘗試生成一個 PDF 時都會拋出一個運行時 錯誤。在此情況下,使用 pdfGenerator 的代碼必須要麼處理異常,要麼傳播它 ,並且很有可能,用戶 將收到一個錯誤消息,表明不能生成一個 PDF。(並且這個問題將盡快被修復! )

但是,和腳本編譯錯誤一樣,當出現運行時腳本錯誤時,並沒有帶來什麼損失 。實際上,由於腳本可 以在運行時更改,與編譯過的代碼相比,它們更容易被修復。您可以修復腳本中 存在的任何問題,一旦 腳本被重新裝載,問題將不復存在。所以,從某種角度來說,在運行時更改代碼 的能力不僅在做出更改 方面給予您更大的靈活性,而且也在出現錯誤時給予您更大的靈活性。但是,這 並不意味著應該讓 Spring 應用程序中的所有 bean 都是可刷新的。與很多事情一樣,可刷新 bean 最好是適度地使用。

腳本安全性

最後要考慮的重要一點是安全性。保證腳本的安全,並確保只有經過授權的用 戶或管理員能夠修改它 們,這一點很重要。在某些方面,這與如何確保應用程序其他部分的安全沒有區 別。例如,大多數應用 程序需要確保數據的完整性,並使用戶只能訪問特定於其角色或權限的功能。但 是另一方面,這個功能 也可能為黑客入侵系統並更改數據和系統行為打開新的方便之門。您當然希望減 少應用程序的攻擊面, 因此和所有設計上的權衡一樣,您必須權衡優點和缺點。

使安全性變得更加重要的是,借助 Spring 的動態語言支持,您不僅可以更改 系統數據,還可以更改 系統行為。在某一程度上確實如此:想想 SQL 注入攻擊,這種攻擊可注入惡意代 碼,還有 JavaScript 跨站點腳本編制,或者跨站點請求偽造攻擊,這些攻擊可以更改或替換系統的行 為。我認為,您需要知 道如何對 Groovy 腳本進行適當的控制,如果這些腳本是可刷新的,那麼更應該 這樣做。

取決於如何使用可刷新 bean,隨之產生的安全風險有可能超過在運行時更改 行為所帶來的好處。試 想一個面向客戶的銷售應用程序,該應用程序需要經常更改為客戶提供折扣的規 則,或者想象一個保險 應用程序,這個應用程序的業務規則可能經常被更改。在此情況下,可以設計一 個用 Groovy 編寫的 DSL,銷售人員或保險代理可以對其進行更改,以適應當前的業務需要。也許您想 添加一點邏輯,以便對 超過 50 美元的商品提供 10% 的折扣。當然,可以通過允許用戶直接在正在運行 的應用程序中編輯小塊 的 DSL,來適應這種類型的更改。或者,也可以設計一個圖形化的編輯器,以便 用戶用它來更改折扣策 略。

結束語

您已經看到了如何使用編譯過的 Groovy 類或動態編譯和裝載的腳本將 Groovy 集成到基於 Spring 的應用程序中。您還知道如何使腳本化的 Groovy bean 實現可刷新功能,如何在 創建時定制 Groovy bean,以及如何將它們存儲在關系數據庫中。您了解到腳本編譯和運行時錯誤如 何以不同的方式影響正 在運行的應用程序,以及可刷新 bean 如何使得在運行時修復 bug 比使用傳統的 架構更加容易,當使用 傳統架構時,需要重新部署或者重新啟動應用程序。最後,我簡要地談到了腳本 化的 bean 和可刷新 bean 的安全性問題,並提醒您需要充分評估應用程序所需的安全性級別。

Spring 和 Groovy 組成了一個強大的組合:Spring 提供架構和基礎設施,而 Groovy 則增加動態能 力。Spring 在 Groovy 腳本改變時重新裝載它們的能力可以將您的應用程序帶到 未知的領域。但是要記 住:“能力越大,責任越重。” 為應用程序增加更多的動態性當然可以使應用程 序更加靈活和強大,但 是這也可能帶來前所未有的問題和挑戰。

文章來源:

http://www.ibm.com/developerworks/cn/java/j-groovierspring2.html

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