Spring 對 Groovy 等動態語言的支援,賦予應用程式在運行時熱更新業務邏輯的能力。本文深入探討可刷新 Bean、客製化器、資料庫腳本存儲等進階特性,並分析動態部署的風險與對策。
在 <lang:groovy> 定義中添加 refresh-check-delay 屬性,即可啟用腳本自動重載:
<lang:groovy id="pdfGenerator"
script-source="classpath:groovierspring/GroovyPdfGenerator.groovy"
refresh-check-delay="10000">
<lang:property name="companyName" value="Refreshable Groovy Bookstore"/>
</lang:groovy>
Spring 內部使用 AOP 代理攔截方法調用,按延遲間隔檢查腳本時間戳。僅在方法調用觸發且超過延遲時才檢查,避免主動輪詢消耗資源。若腳本編譯失敗,原 Bean 繼續服務,保證系統穩定。
透過 <lang:defaults> 設定預設延遲,個別 Bean 可覆寫或禁用(負值):
<lang:defaults refresh-check-delay="20000"/> <lang:groovy id="pdfGenerator" refresh-check-delay="60000" ... /> <lang:groovy id="invoiceEmailer" refresh-check-delay="-1" ... />
使用 ScriptFactoryPostProcessor 設定預設延遲,並在 Bean 定義屬性中指定單個延遲:
builder.beans {
scriptFactoryPostProcessor(ScriptFactoryPostProcessor) {
defaultRefreshCheckDelay = 20000
}
pdfGenerator(GroovyScriptFactory, 'classpath:.../GroovyPdfGenerator.groovy') { bean ->
companyName = 'Refreshable BeanBuilder Bookstore'
bean.beanDefinition.setAttribute(ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE, 60000)
}
}
實作 GroovyObjectCustomizer 介面,在 Bean 建立後進行元編程增強。例如效能日誌記錄器:
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);
}
}
配置時以 customizer-ref 引用:
<bean id="performanceLoggingCustomizer" class="groovierspring.PerformanceLoggingCustomizer"/> <lang:groovy id="pdfGenerator" customizer-ref="performanceLoggingCustomizer" ... />
實作 ScriptSource 介面從資料庫讀取腳本,並擴展 ScriptFactoryPostProcessor 識別 database: 前綴:
public class CustomScriptFactoryPostProcessor extends ScriptFactoryPostProcessor {
public static final String DATABASE_SCRIPT_PREFIX = "database:";
private DataSource dataSource;
@Override
protected ScriptSource convertToScriptSource(String beanName, String scriptSourceLocator, ResourceLoader resourceLoader) {
if (scriptSourceLocator.startsWith(DATABASE_SCRIPT_PREFIX)) {
return new DatabaseScriptSource(scriptSourceLocator.substring(DATABASE_SCRIPT_PREFIX.length()), dataSource);
}
// ... 處理內聯與資源腳本
}
}
配置時指定資料來源與資料庫腳本路徑:
<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" script-source="database:groovierspring/GroovyPdfGenerator.groovy" ... />
Spring 與 Groovy 的結合將應用推向「未知領域」——運行時熱更新、外部化業務規則、資料庫腳本管理等能力極大提升了系統靈活性。但「能力越大,責任越重」,必須建立完善的測試、監控與安全機制,方能駕馭這股動態力量。