歡迎您光臨本站 註冊首頁
  
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 腳本改變時重新裝載它們的能力可以將您的應用程序帶到未知的領域。但是要記住:“能力越大,責任越重。” 為應用程序增加更多的動態性當然可以使應用程序更加靈活和強大,但是這也可能帶來前所未有的問題和挑戰。 (責任編輯:A6)



[火星人 ] Groovy 使 Spring 更出色,第 2 部分: 在運行時改變應用程序的行為已經有791次圍觀

http://coctec.com/docs/linux/show-post-68970.html