Spring框架中的設計模式(二)
原創: 瑞查德-Jack
在 上一篇 中我們在Spring中所談到的設計模式涉及到了創建模式三劍客和1個行爲模式(解釋器模式)。這次我們會將眼光更多地關注在具有結構性和行爲性的設計模式上。
在這篇文章中,我們將看到每個類型的兩種模式。首先將關注類型是的結構設計模式。它將包含代理和複合。下一個將介紹行爲模式:策略和模板方法。
代理模式
面向對象編程(OOP)可能是編程中最流行的概念。然而,Spring引入了另一種編碼規範,面向切面編程(AOP)。爲了簡化定義,AOP是面向系統特定點的一種編程,如:異常拋出,特定類別方法的執行等.AOP允許在執行這些特定點之前或之後執行補充動作。如何實現這種操作?它可以通過監聽器(listeners)進行。但在這種情況下,我們應該在只要可能存在調用的地方都需要定義監聽器來進行監聽(比如在一個方法的開始的地方)。這就是爲什麼Spring不採用這個idea。相反,Spring實現了一種能夠通過額外的方法調用完成任務的設計模式 - 代理設計模式。
代理就像對象的鏡像一樣。也正因爲如此,代理對象不僅可以覆蓋真實對象,還可以擴展其功能。因此,對於只能在屏幕上打印一些文本的對象,我們可以添加另一個對象來過濾顯示單詞。可以通過代理來定義第二個對象的調用。代理是封裝真實對象的對象。例如,如果您嘗試調用Waiter bean,那麼您將調用該Bean的代理,其行爲方式完全相同。
代理設計模式的一個很好的例子是org.springframework.aop.framework.ProxyFactoryBean。該工廠根據Spring bean構建AOP代理。該類實現了定義getObject()方法的 FactoryBean
接口。此方法用於將需求 Bean
的實例返回給 bean factory
。在這種情況下,它不是返回的實例,而是 AOP代理
。在執行代理對象的方法之前,可以通過調用補充方法來進一步“修飾”代理對象(其實所謂的靜態代理不過是在裝飾模式上加了個要不要你來幹動作行爲而已,而不是裝飾模式什麼也不做就加了件衣服,其他還得由你來全權完成)。
ProxyFactory
的一個例子是:
public class TestProxyAop {
@Test
public void test() {
ProxyFactory factory = new ProxyFactory(new House());
factory.addInterface(Construction.class);
factory.addAdvice(new BeforeConstructAdvice());
factory.setExposeProxy(true);
Construction construction = (Construction) factory.getProxy();
construction.construct();
assertTrue("Construction is illegal. "
+ "Supervisor didn't give a permission to build "
+ "the house", construction.isPermitted());
}
}
interface Construction {
public void construct();
public void givePermission();
public boolean isPermitted();
}
class House implements Construction{
private boolean permitted = false;
@Override
public boolean isPermitted() {
return this.permitted;
}
@Override
public void construct() {
System.out.println("I'm constructing a house");
}
@Override
public void givePermission() {
System.out.println("Permission is given to construct a simple house");
this.permitted = true;
}
}
class BeforeConstructAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] arguments, Object target) throws Throwable {
if (method.getName().equals("construct")) {
((Construction) target).givePermission();
}
}
}
這個測試應該通過,因爲我們不直接在House實例上操作,而是代理它。代理調用第一個 BeforeConstructAdvice
的 before
方法(指向在執行目標方法之前執行,在我們的例子中爲 construct()
)通過它,給出了一個“權限”來構造對象的字段(house)。代理層提供了一個額外新功能,因爲它可以簡單地分配給另一個對象。要做到這一點,我們只能在before方法之前修改過濾器。
複合模式
另一種結構模式是複合模式。在關於Spring中設計模式的第一篇文章中,我們使用構建器來構造複雜對象。另一種實現方法是使用複合模式。這種模式是基於具有共同行爲的多個對象的存在,用於構建更大的對象。較大的對象仍然具有與最小對象相同的特徵。那麼用它來定義相同的行爲。
複合對象的非Spring示例可以是一個寫入HTML的文本對象,由包含span或em標籤的段落組成:
-
public class CompositeTest { @Test public void test() { TextTagComposite composite = new PTag(); composite.addTag(new SpanTag()); composite.addTag(new EmTag()); // sample client code composite.startWrite(); for (TextTag leaf : composite.getTags()) { leaf.startWrite(); leaf.endWrite(); } composite.endWrite(); assertTrue("Composite should contain 2 tags but it contains "+composite.getTags().size(), composite.getTags().size() == 2); } } interface TextTag { public void startWrite(); public void endWrite(); } interface TextTagComposite extends TextTag { public List<TextTag> getTags(); public void addTag(TextTag tag); } class PTag implements TextTagComposite { private List<TextTag> tags = new ArrayList<TextTag>(); @Override public void startWrite() { System.out.println("<p>"); } @Override public void endWrite() { System.out.println("</p>"); } @Override public List<TextTag> getTags() { return tags; } @Override public void addTag(TextTag tag) { tags.add(tag); } } class SpanTag implements TextTag { @Override public void startWrite() { System.out.println("<span>"); } @Override public void endWrite() { System.out.println("</span>"); } } class EmTag implements TextTag { @Override public void startWrite() { System.out.println("<em>"); } @Override public void endWrite() { System.out.println("</em>"); } }
在這種情況下,可以看到一個複合對象。我們可以區分複合與非複合對象,因爲第一個可以容納一個或多個非複合對象(
PTag
類中的privateListtags
字段)。非複合對象稱爲葉子。TextTag
接口被稱爲組件,因爲它爲兩個對象類型提供了共同的行爲規範(有點像Linux
文件管理系統的有共同點的文件放在一個文件夾下進行管理,其實就是節點管理)。
在 Spring
世界中,我們檢索複合對象的概念是org.springframework.beans.BeanMetadataElement接口,用於配置 bean
對象。它是所有繼承對象的基本界面。現在,在一方面,我們有一個葉子,由org.springframework.beans.factory.parsing.BeanComponentDefinition表示,另一邊是複合org.springframework.beans.factory.parsing.CompositeComponentDefinition。 CompositeComponentDefinition
類似於組件,因爲它包含addNestedComponent(ComponentDefinition component)方法,它允許將葉添加到私有final列表中 nestedComponents
。您可以看到,由於此列表, BeanComponentDefinition
和 CompositeComponentDefinition
的組件是org.springframework.beans.factory.parsing.ComponentDefinition。
策略模式
本文描述的第三個概念是策略設計模式。策略定義了通過不同方式完成相同事情的幾個對象。完成任務的方式取決於採用的策略。舉個例子說明,我們可以去一個國家。我們可以乘公共汽車,飛機,船甚至汽車去那裏。所有這些方法將把我們運送到目的地國家。但是,我們將通過檢查我們的銀行帳戶來選擇最適應的方式。如果我們有很多錢,我們將採取最快的方式(可能是私人飛行)。如果我們沒有足夠的話,我們會採取最慢的(公車,汽車)。該銀行賬戶作爲確定適應策略的因素。
Spring在org.springframework.web.servlet.mvc.multiaction.MethodNameResolver類(過時,但不影響拿來研究)中使用策略設計模式。它是 MultiActionController
(同樣過時)的參數化實現。在開始解釋策略之前,我們需要了解MultiActionController的實用性。這個類允許同一個類處理幾種類型的請求。作爲Spring中的每個控制器,MultiActionController執行方法來響應提供的請求。策略用於檢測應使用哪種方法。解析過程在MethodNameResolver實現中實現,例如在同一個包中的ParameterMethodNameResolver中。方法可以通過多個條件解決:屬性映射,HTTP請求參數或URL路徑。
-
@Override public String getHandlerMethodName(HttpServletRequest request) throws NoSuchRequestHandlingMethodException { String methodName = null; // Check parameter names where the very existence of each parameter // means that a method of the same name should be invoked, if any. if (this.methodParamNames != null) { for (String candidate : this.methodParamNames) { if (WebUtils.hasSubmitParameter(request, candidate)) { methodName = candidate; if (logger.isDebugEnabled()) { logger.debug("Determined handler method '" + methodName + "' based on existence of explicit request parameter of same name"); } break; } } } // Check parameter whose value identifies the method to invoke, if any. if (methodName == null && this.paramName != null) { methodName = request.getParameter(this.paramName); if (methodName != null) { if (logger.isDebugEnabled()) { logger.debug("Determined handler method '" + methodName + "' based on value of request parameter '" + this.paramName + "'"); } } } if (methodName != null && this.logicalMappings != null) { // Resolve logical name into real method name, if appropriate. String originalName = methodName; methodName = this.logicalMappings.getProperty(methodName, methodName); if (logger.isDebugEnabled()) { logger.debug("Resolved method name '" + originalName + "' to handler method '" + methodName + "'"); } } if (methodName != null && !StringUtils.hasText(methodName)) { if (logger.isDebugEnabled()) { logger.debug("Method name '" + methodName + "' is empty: treating it as no method name found"); } methodName = null; } if (methodName == null) { if (this.defaultMethodName != null) { // No specific method resolved: use default method. methodName = this.defaultMethodName; if (logger.isDebugEnabled()) { logger.debug("Falling back to default handler method '" + this.defaultMethodName + "'"); } } else { // If resolution failed completely, throw an exception. throw new NoSuchRequestHandlingMethodException(request); } } return methodName; }
正如我們在前面的代碼中可以看到的,方法的名稱通過提供的參數映射,URL中的預定義屬性或參數存在來解決(默認情況下,該參數的名稱是action)。
模板模式
本文提出的最後一個設計模式是模板方法。此模式定義了類行爲的骨架,並將子步驟的某些步驟的延遲執行(具體就是下面例子中一個方法放在另一個方法中,只有調用另一方方法的時候這個方法纔會執行,而且還可能會在其他行爲方法之後按順序執行)。其中寫了一種方法(下面例子中的construct()),注意定義爲final,起着同步器的角色。它以給定的順序執行由子類定義的方法。在現實世界中,我們可以將模板方法與房屋建設進行比較。獨立於建造房屋的公司,我們需要從建立基礎開始,只有在我們完成之後才能做其他的工作。這個執行邏輯將被保存在一個我們不能改變的方法中。例如基礎建設或刷牆會被作爲一個模板方法中的方法,具體到建築房屋的公司。我們可以在給定的例子中看到它:
-
public class TemplateMethod { public static void main(String[] args) { HouseAbstract house = new SeaHouse(); house.construct(); } } abstract class HouseAbstract { protected abstract void constructFoundations(); protected abstract void constructWall(); // template method public final void construct() { constructFoundations(); constructWall(); } } class EcologicalHouse extends HouseAbstract { @Override protected void constructFoundations() { System.out.println("Making foundations with wood"); } @Override protected void constructWall() { System.out.println("Making wall with wood"); } } class SeaHouse extends HouseAbstract { @Override protected void constructFoundations() { System.out.println("Constructing very strong foundations"); } @Override protected void constructWall() { System.out.println("Constructing very strong wall"); } }
該代碼應該輸出:
Constructing very strong foundations Constructing very strong wall
-
Spring在org.springframework.context.support.AbstractApplicationContext類中使用模板方法。他們不是一個模板方法(在我們的例子中是construct ),而是多個。例如,getsFreshBeanFactory返回內部
bean工廠
的新版本,調用兩個抽象方法:refreshBeanFactory
(刷新工廠bean)和getBeanFactory
(以獲取更新的工廠bean)。這個方法和其他一些方法一樣,用在public void refresh()中,拋出構造應用程序上下文的BeansException,IllegalStateException方法(這裏會在後面Spring中與應用程序上下文分析中再次提到)。
我們可以從同一個包中的GenericApplicationContext找到一些通過模板方法所實現的抽象方法的實現的例子(說的有點拗口,多讀幾遍就好):
/**
* Do nothing: We hold a single internal BeanFactory and rely on callers
* to register beans through our public methods (or the BeanFactory's).
* @see #registerBeanDefinition
*/
@Override
protected final void refreshBeanFactory() throws IllegalStateException {
if (this.refreshed) {
throw new IllegalStateException(
"GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
}
this.beanFactory.setSerializationId(getId());
this.refreshed = true;
}
@Override
protected void cancelRefresh(BeansException ex) {
this.beanFactory.setSerializationId(null);
super.cancelRefresh(ex);
}
/**
* Not much to do: We hold a single internal BeanFactory that will never
* get released.
*/
@Override
protected final void closeBeanFactory() {
this.beanFactory.setSerializationId(null);
}
/**
* Return the single internal BeanFactory held by this context
* (as ConfigurableListableBeanFactory).
*/
@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
/**
* Return the underlying bean factory of this context,
* available for registering bean definitions.
* <p><b>NOTE:</b> You need to call {@link #refresh()} to initialize the
* bean factory and its contained beans with application context semantics
* (autodetecting BeanFactoryPostProcessors, etc).
* @return the internal bean factory (as DefaultListableBeanFactory)
*/
public final DefaultListableBeanFactory getDefaultListableBeanFactory() {
return this.beanFactory;
}
經過上面這些可以讓我們發現Spring如何通過使用行爲和結構設計模式來更好地組織上下文(模板方法),並通過相應策略來解決執行方法。它使用兩種結構設計模式,通過代理模式來簡化AOP部分並通過複合模式來構造複雜對象。