Spring框架中的設計模式(二)

知秋z發表於2017-10-18

在上一篇中我們在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例項上操作,而是代理它。代理呼叫第一個BeforeConstructAdvicebefore方法(指向在執行目標方法之前執行,在我們的例子中為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類中的private List tags欄位)。非複合物件稱為葉子。TextTag介面被稱為元件,因為它為兩個物件型別提供了共同的行為規範(有點像Linux檔案管理系統的有共同點的檔案放在一個資料夾下進行管理,其實就是節點管理)。

Spring世界中,我們檢索複合物件的概念是org.springframework.beans.BeanMetadataElement介面,用於配置bean物件。它是所有繼承物件的基本介面。現在,在一方面,我們有一個葉子,由org.springframework.beans.factory.parsing.BeanComponentDefinition表示,另一邊是複合org.springframework.beans.factory.parsing.CompositeComponentDefinitionCompositeComponentDefinition類似於元件,因為它包含addNestedComponent(ComponentDefinition component)方法,它允許將葉新增到私有final列表中nestedComponents。您可以看到,由於此列表,BeanComponentDefinitionCompositeComponentDefinition的元件是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部分並通過複合模式來構造複雜物件。

有問題可以加qq群523409180 討論的

相關文章