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

本文是Spring框架中使用的設計模式第四篇。本文將在此呈現(xiàn)出新的3種模式。
一開始,我們會討論2種結構模式:適配器和裝飾器。在第三部分和最后一部分,我們將討論單例模式。

適配器

當我們需要在給定場景下(也就是給定接口)想要不改變自身行為而又想做到一些事情的情況下(就是我給電也就是接口了,你來做事也就是各種電器),使用適配器設計模式(這里再說一點,就相當于我們再一個規(guī)章制度的環(huán)境下,如何去適應并達到我們期待的效果,放在架構設計這里,可以拿一個php系統(tǒng)和一個Java系統(tǒng)來說,假如兩者要互相調用對方的功能,我們可以設計一套對外的api來適配)。這意味著在調用此對象之前,我們將更改使用對象而不改變機制。拿一個現(xiàn)實中的例子進行說明,想象一下你想要用電鉆來鉆一個洞。要鉆一個小洞,你會使用小鉆頭,鉆一個大的需要用大鉆頭??梢钥聪旅娴拇a:

public class AdapterTest {

  public static void main(String[] args) {
    HoleMaker maker = new HoleMakerImpl();
    maker.makeHole(1);
    maker.makeHole(2);
    maker.makeHole(30);
    maker.makeHole(40);
  }
}

interface HoleMaker {
  public void makeHole(int diameter);
}

interface DrillBit {
  public void makeSmallHole();
  public void makeBigHole();
}

// Two adaptee objects
class BigDrillBit implements DrillBit {

  @Override
  public void makeSmallHole() {
    // do nothing
  }

  @Override
  public void makeBigHole() {
    System.out.println("Big hole is made byt WallBigHoleMaker");
  }
}

class SmallDrillBit implements DrillBit {

  @Override
  public void makeSmallHole() {
    System.out.println("Small hole is made byt WallSmallHoleMaker");
  }

  @Override
  public void makeBigHole() {
    // do nothing
  }
}

// Adapter class
class Drill implements HoleMaker {

  private DrillBit drillBit;

  public Drill(int diameter) {
    drillBit = getMakerByDiameter(diameter);
  }

  @Override
  public void makeHole(int diameter) {
    if (isSmallDiameter(diameter)) {
            drillBit.makeSmallHole();
    } else {
            drillBit.makeBigHole();
    }
  }

  private DrillBit getMakerByDiameter(int diameter) {
    if (isSmallDiameter(diameter)) {
            return new SmallDrillBit();
    }
    return new BigDrillBit();
  }

  private boolean isSmallDiameter(int diameter) {
    return diameter < 10;
  }
}

// Client class
class HoleMakerImpl implements HoleMaker {

  @Override
  public void makeHole(int diameter) {
    HoleMaker maker = new Drill(diameter);
    maker.makeHole(diameter);
  }
}

以上代碼的結果如下:

Small hole is made byt SmallDrillBit
Small hole is made byt SmallDrillBit
Big hole is made byt BigDrillBit
Big hole is made byt BigDrillBit

可以看到,hole 是由所匹配的DrillBit對象制成的。如果孔的直徑小于10,則使用SmallDrillBit。如果它更大,我們使用BigDrillBit。

思路就是,要打洞,那就要有打洞的工具,這里提供一個電鉆接口和鉆頭。電鉆就是用來打洞的,所以,它就一個接口方法即可,接下來定義鉆頭的接口,無非就是鉆頭的尺寸標準,然后搞出兩個鉆頭實現(xiàn)類出來,接下來就是把鉆頭和電鉆主機組裝起來咯,也就是Drill類,里面有電鉆接口+鉆頭(根據(jù)要鉆的孔大小來確定用哪個鉆頭),其實也就是把幾個單一的東西組合起來擁有豐富的功能,最后我們進行封裝下:HoleMakerImpl,這樣只需要根據(jù)尺寸就可以打相應的孔了,對外暴露的接口極為簡單,無須管內部邏輯是多么復雜

Spring使用適配器設計模式來處理不同servlet容器中的加載時編織(load-time-weaving)。在面向切面編程(AOP)中使用load-time-weaving,一種方式是在類加載期間將AspectJ的方面注入字節(jié)碼。另一種方式是對類進行編譯時注入或對已編譯的類進行靜態(tài)注入。

我們可以從關于Spring和JBoss的處理接口這里找到一個很好的例子,它包含在org.springframework.instrument.classloading.jboss包中。我們檢索JBossLoadTimeWeaver類負責JBoss容器的編織管理。然而,類加載器對于JBoss 6(使用JBossMCAdapter實例)和JBoss 7/8(使用JBossModulesAdapter實例)是不同的。根據(jù)JBoss版本,我們在JBossLoadTimeWeaver構造函數(shù)中初始化相應的適配器(與我們示例中的Drill的構造函數(shù)完全相同):

public JBossLoadTimeWeaver(ClassLoader classLoader) {
  private final JBossClassLoaderAdapter adapter;

  Assert.notNull(classLoader, "ClassLoader must not be null");
  if (classLoader.getClass().getName().startsWith("org.jboss.modules")) {
    // JBoss AS 7 or WildFly 8
    this.adapter = new JBossModulesAdapter(classLoader);
  }
  else {
    // JBoss AS 6
    this.adapter = new JBossMCAdapter(classLoader);
  }
}

而且,此適配器所創(chuàng)建的實例用于根據(jù)運行的servlet容器版本進行編織操作:

@Override
public void addTransformer(ClassFileTransformer transformer) {
  this.adapter.addTransformer(transformer);
}

@Override
public ClassLoader getInstrumentableClassLoader() {
  return this.adapter.getInstrumentableClassLoader();
}

總結:適配器模式,其實就是我們用第一人稱的視角去看世界,我想拓展我自己的技能的時候,就實行拿來主義,就好比這里的我是電鉆的視角,那么我想擁有鉆大孔或者小孔的功能,那就把鉆頭拿到手組合起來就好。

和裝飾模式的區(qū)別:裝飾模式屬于第三人稱的視角,也就是上帝視角!我只需要把幾個功能性的組件給拿到手,進行組合一下,實現(xiàn)一個更加niubility的功能這里提前說下,這樣看下面的內容能好理解些。下面解釋裝飾模式

裝飾

這里描述的第二種設計模式看起來類似于適配器。它是裝飾模式。這種設計模式的主要作用是為給定的對象添加補充角色。舉個現(xiàn)實的例子,就拿咖啡來講。通常越黑越苦,你可以添加(裝飾)糖和牛奶,使咖啡不那么苦??Х仍谶@里被裝飾的對象,糖與牛奶是用來裝飾的。可以參考下面的例子:

public class DecoratorSample {

  @Test
  public void test() {
    Coffee sugarMilkCoffee=new MilkDecorator(new SugarDecorator(new BlackCoffee()));
    assertEquals(sugarMilkCoffee.getPrice(), 6d, 0d);
  }
}

// decorated
abstract class Coffee{
  protected int candied=0;
  protected double price=2d;
  public abstract int makeMoreCandied();
  public double getPrice(){
    return this.price;
  }
  public void setPrice(double price){
    this.price+=price;
  }
}
class BlackCoffee extends Coffee{
  @Override
  public int makeMoreCandied(){
    return 0;
  }
  @Override
  public double getPrice(){
    return this.price;
  }
}

// abstract decorator
abstract class CoffeeDecorator extends Coffee{
  protected Coffee coffee;
  public CoffeeDecorator(Coffee coffee){
    this.coffee=coffee;
  }
  @Override
  public double getPrice(){
    return this.coffee.getPrice();
  }
  @Override
  public int makeMoreCandied(){
    return this.coffee.makeMoreCandied();
  }
}

// concrete decorators
class MilkDecorator extends CoffeeDecorator{
  public MilkDecorator(Coffee coffee){
    super(coffee);
  }
  @Override
  public double getPrice(){
    return super.getPrice()+1d;
  }
  @Override
  public int makeMoreCandied(){
    return super.makeMoreCandied()+1;
  }
}
class SugarDecorator extends CoffeeDecorator{
  public SugarDecorator(Coffee coffee){
    super(coffee);
  }
  @Override
  public double getPrice(){
    return super.getPrice()+3d;
  }
  @Override
  public int makeMoreCandied(){
    return super.makeMoreCandied()+1;
  }
}

上面這個簡單的裝飾器的小例子是基于對父方法的調用,從而改變最后的屬性(我們這里是指價格和加糖多少)。在Spring中,我們在處理與Spring管理緩存同步事務的相關類中可以 發(fā)現(xiàn)裝飾器設計模式的例子。這個類是org.springframework.cache.transaction.TransactionAwareCacheDecorator。

這個類的哪些特性證明它是org.springframework.cache.Cache對象的裝飾器?首先,與我們的咖啡示例一樣,TransactionAwareCacheDecorator的構造函數(shù)接收參數(shù)裝飾對象(Cache):

private final Cache targetCache;
/**
 * Create a new TransactionAwareCache for the given target Cache.
 * @param targetCache the target Cache to decorate
 */
public TransactionAwareCacheDecorator(Cache targetCache) {
  Assert.notNull(targetCache, "Target Cache must not be null");
  this.targetCache = targetCache;
}

其次,通過這個對象,我們可以得到一個新的行為:為給定的目標緩存創(chuàng)建一個新的TransactionAwareCache。這個我們可以在TransactionAwareCacheDecorator的注釋中可以閱讀到,其主要目的是提供緩存和Spring事務之間的同步級別。這是通過org.springframework.transaction.support.TransactionSynchronizationManager中的兩種緩存方法實現(xiàn)的:put 和 evict(其實最終不還是通過targetCache來實現(xiàn)的么):

@Override
public void put(final Object key, final Object value) {
  if (TransactionSynchronizationManager.isSynchronizationActive()) {
    TransactionSynchronizationManager.registerSynchronization(
      new TransactionSynchronizationAdapter() {
        @Override
        public void afterCommit() {
          targetCache.put(key, value);
        }
    });
  }
  else {
    this.targetCache.put(key, value);
  }
}

@Override
public void evict(final Object key) {
  if (TransactionSynchronizationManager.isSynchronizationActive()) {
          TransactionSynchronizationManager.registerSynchronization(
            new TransactionSynchronizationAdapter() {
              @Override
              public void afterCommit() {
                targetCache.evict(key);
              }
          });
  }
  else {
    this.targetCache.evict(key);
  }
}

這種模式看起來類似于適配器,對吧?但是,它們還是有區(qū)別的。我們可以看到,適配器將對象適配到運行時環(huán)境,即。如果我們在JBoss 6中運行,我們使用與JBoss 7不同的類加載器。Decorator每次使用相同的主對象(Cache)工作,并且僅向其添加新行為(與本例中的Spring事務同步),另外,可以通過我在解讀這個設計模式之前的說法來區(qū)分二者。

我們再以springboot的初始化來舉個例子的,這塊后面會進行仔細的源碼分析的,這里就僅僅用設計模式來說下的:

/**
 * Event published as early as conceivably possible as soon as a {@link SpringApplication}
 * has been started - before the {@link Environment} or {@link ApplicationContext} is
 * available, but after the {@link ApplicationListener}s have been registered. The source
 * of the event is the {@link SpringApplication} itself, but beware of using its internal
 * state too much at this early stage since it might be modified later in the lifecycle.
 *
 * @author Dave Syer
 */
@SuppressWarnings("serial")
public class ApplicationStartedEvent extends SpringApplicationEvent {
    /**
     * Create a new {@link ApplicationStartedEvent} instance.
     * @param application the current application
     * @param args the arguments the application is running with
     */
    public ApplicationStartedEvent(SpringApplication application, String[] args) {
        super(application, args);
    }
}

從注釋可以看出 ApplicationListener要先行到位的,然后就是started的時候Event published走起,接著就是Environment配置好,ApplicationContext進行初始化完畢,那我們去看ApplicationListener的源碼:

/**
 * Listener for the {@link SpringApplication} {@code run} method.
 * {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader}
 * and should declare a public constructor that accepts a {@link SpringApplication}
 * instance and a {@code String[]} of arguments. A new
 * {@link SpringApplicationRunListener} instance will be created for each run.
 *
 * @author Phillip Webb
 * @author Dave Syer
 */
public interface SpringApplicationRunListener {
    /**
     * Called immediately when the run method has first started. Can be used for very
     * early initialization.
     */
    void started();
    /**
     * Called once the environment has been prepared, but before the
     * {@link ApplicationContext} has been created.
     * @param environment the environment
     */
    void environmentPrepared(ConfigurableEnvironment environment);
    /**
     * Called once the {@link ApplicationContext} has been created and prepared, but
     * before sources have been loaded.
     * @param context the application context
     */
    void contextPrepared(ConfigurableApplicationContext context);
    /**
     * Called once the application context has been loaded but before it has been
     * refreshed.
     * @param context the application context
     */
    void contextLoaded(ConfigurableApplicationContext context);
    /**
     * Called immediately before the run method finishes.
     * @param context the application context or null if a failure occurred before the
     * context was created
     * @param exception any run exception or null if run completed successfully.
     */
    void finished(ConfigurableApplicationContext context, Throwable exception);
}

看類注釋我們可以知道,需要實現(xiàn)此接口內所定義的這幾個方法,ok,來看個實現(xiàn)類:

/**
 * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
 * <p>
 * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
 * before the context is actually refreshed.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 */
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    private final SpringApplication application;
    private final String[] args;
    private final ApplicationEventMulticaster initialMulticaster;
    public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }
    @Override
    public int getOrder() {
        return 0;
    }
    @Override
    public void started() {
        this.initialMulticaster
                .multicastEvent(new ApplicationStartedEvent(this.application, this.args));
    }
    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
                this.application, this.args, environment));
    }
    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
    }
    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        for (ApplicationListener<?> listener : this.application.getListeners()) {
            if (listener instanceof ApplicationContextAware) {
                ((ApplicationContextAware) listener).setApplicationContext(context);
            }
            context.addApplicationListener(listener);
        }
        this.initialMulticaster.multicastEvent(
                new ApplicationPreparedEvent(this.application, this.args, context));
    }
    @Override
    public void finished(ConfigurableApplicationContext context, Throwable exception) {
        // Listeners have been registered to the application context so we should
        // use it at this point
        context.publishEvent(getFinishedEvent(context, exception));
    }
    private SpringApplicationEvent getFinishedEvent(
            ConfigurableApplicationContext context, Throwable exception) {
        if (exception != null) {
            return new ApplicationFailedEvent(this.application, this.args, context,
                    exception);
        }
        return new ApplicationReadyEvent(this.application, this.args, context);
    }
}

從上可以看出,EventPublishingRunListener里對接口功能的實現(xiàn),主要是通過SpringApplicationApplicationEventMulticaster 來實現(xiàn)的,自己不干活,掛個虛名,從上帝模式的角度來看,這不就是應用了裝飾模式來實現(xiàn)的么。

更多源碼解析請關注后續(xù)的本人對Spring框架全面的重點部分解析系列博文

單例

單例,我們最常用的設計模式。正如我們在很多Spring Framework中關于單例和原型bean的文章(網(wǎng)上太多了)中已經(jīng)看到過的,單例是幾個bean作用域中的中的一個。此作用域在每個應用程序上下文中僅創(chuàng)建一個給定bean的實例。與signleton設計模式有所區(qū)別的是,Spring將實例的數(shù)量限制的作用域在整個應用程序的上下文。而Singleton設計模式在Java應用程序中是將這些實例的數(shù)量限制在給定類加載器管理的整個空間中。這意味著我們可以為兩個Spring的上下文(同一份配置文件起兩個容器,也就是不同端口的容器實例)使用相同的類加載器,并檢索兩個單例作用域的bean。

在看Spring單例應用之前,讓我們來看一個Java的單例例子:

public class SingletonTest {

  @Test
  public void test() {
    President president1 = (President) SingletonsHolder.PRESIDENT.getHoldedObject();
    President president2 = (President) SingletonsHolder.PRESIDENT.getHoldedObject();
    assertTrue("Both references of President should point to the same object", president1 == president2);
    System.out.println("president1 = "+president1+" and president2 = "+president2);
    // sample output
    // president1 = com.waitingforcode.test.President@17414c8 and president2 = com.waitingforcode.test.President@17414c8

  }

}

enum SingletonsHolder {

  PRESIDENT(new President());

  private Object holdedObject;

  private SingletonsHolder(Object o) {
          this.holdedObject = o;
  }

  public Object getHoldedObject() {
          return this.holdedObject;
  }

}

class President {
}

這個測試例子證明,只有一個由SingletonsHolder所持有的President實例。在Spring中,我們可以在bean工廠中找到單例應用的影子(例如在org.springframework.beans.factory.config.AbstractFactoryBean中):

/**
 * Expose the singleton instance or create a new prototype instance.
 * @see #createInstance()
 * @see #getEarlySingletonInterfaces()
 */
@Override
public final T getObject() throws Exception {
  if (isSingleton()) {
    return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
  }
  else {
    return createInstance();
  }
}

我們看到,當需求對象被視為單例時,它只被初始化一次,并且在每次使用同一個bean類的實例后返回。我們可以在給定的例子中看到,類似于我們以前看到的President情況。將測試bean定義為:

<bean id="shoppingCart" class="com.waitingforcode.data.ShoppingCart" />

測試用例如下所示:

public class SingletonSpringTest {

  @Test
  public void test() {
    // retreive two different contexts
    ApplicationContext firstContext = new FileSystemXmlApplicationContext("applicationContext-test.xml");
    ApplicationContext secondContext = new FileSystemXmlApplicationContext("applicationContext-test.xml");

    // prove that both contexts are loaded by the same class loader
    assertTrue("Class loaders for both contexts should be the same", 
      firstContext.getClassLoader() == secondContext.getClassLoader());
    // compare the objects from different contexts
    ShoppingCart firstShoppingCart = (ShoppingCart) firstContext.getBean("shoppingCart");
    ShoppingCart secondShoppingCart = (ShoppingCart) secondContext.getBean("shoppingCart");
    assertFalse("ShoppingCart instances got from different application context shouldn't be the same", 
      firstShoppingCart == secondShoppingCart);

    // compare the objects from the same context
    ShoppingCart firstShoppingCartBis = (ShoppingCart) firstContext.getBean("shoppingCart");
    assertTrue("ShoppingCart instances got from the same application context should be the same", 
      firstShoppingCart == firstShoppingCartBis);
  }
}

這個測試案例顯示了Spring單例模式與純粹的單例設計模式的主要區(qū)別。盡管使用相同的類加載器來加載兩個應用程序上下文,但是ShoppingCart的實例是不一樣的。但是,當我們比較兩次創(chuàng)建并屬于相同上下文的實例時,我們認為它們是相等的。

也正因為有了單例,Spring可以控制在每個應用程序上下文中只有一個這樣指定的bean的實例可用。因為適配器,Spring可以決定使用由誰來處理JBoss servlet容器中的加載時編織,也可以實現(xiàn)ConfigurableListableBeanFactory的相應實例。第三種設計模式,裝飾器,用于向Cache對象添加同步功能,還有Springboot的容器初始化。

其實對于適配器和裝飾者確實有太多的相似的地方,一個是運行時選擇,一個是加料組合產生新的化學效應,還有從看待事物的角度不同得到不同的行為,適配適配,更注重面向接口的實現(xiàn),而內部又根據(jù)不同情況調用面向一套接口的多套實現(xiàn)的實例的相應方法來實現(xiàn)所要實現(xiàn)的具體功能,裝飾者更注重添油加醋,通過組合一些其他對象實例來讓自己的功能實現(xiàn)的更加華麗一些(達到1+1>2的這種效果)。一家之言,有更好的理解可以聯(lián)系我。

原文:Spring框架中的設計模式(四)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,599評論 19 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,275評論 6 342
  • 原文鏈接:http://blog.csdn.net/zhangerqing http://www.cnblogs....
    孤獨雜貨鋪閱讀 1,639評論 0 3
  • 一、設計模式的分類 總體來說設計模式分為三大類: 創(chuàng)建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者...
    lichengjin閱讀 1,000評論 0 8
  • 風沙漠漠,夜正濃。交河飲馬鳴長空,石陣不出鬼見愁。嗚嗚∽ 耳畔但聞冷風嘯,長河日落人渺渺。 身披黃沙,手可摘星辰,...
    曲無由閱讀 683評論 0 1

友情鏈接更多精彩內容