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

在之前的兩篇文章中忌栅,我們看到了一些在Spring框架中實現(xiàn)的設計模式。這一次我們會發(fā)現(xiàn)這個流行框架使用的3種新模式。本文將從描述兩個創(chuàng)意設計模式開始:原型和對象池男摧。最后我們將重點關(guān)注行為模式—>觀察者。


原型模式

這篇文章的第一個設計模式是原型译打〔室校可以通過官方文檔查找有關(guān)Spring作用域中的bean作用域的文章中介紹了類似的概念(prototype)。原型設計模式與有用相同名稱的(prototype)作用域有點相似扶平。此設計模式允許通過復制已存在的對象來創(chuàng)建一個對象的實例帆离。副本應該是真正的副本。這意味著新對象的所有屬性應與復制對象的屬性相同结澄。如果不清楚哥谷,比一個簡單的JUnit案例更好的說明:

public class PrototypeTest {

  @Test
  public void test() {
    Robot firstRobot = new Robot("Droid#1");
    Robot secondRobot = (Robot) firstRobot.clone();
    assertTrue("Cloned robot's instance can't be the same as the"
      +" source robot instance", 
      firstRobot != secondRobot);
    assertTrue("Cloned robot's name should be '"+firstRobot.getName()+"'"
      +" but was '"+secondRobot.getName()+"'", 
      secondRobot.getName().equals(firstRobot.getName()));
  }

}


class Robot implements Cloneable {
  private String name;

  public Robot(String name) {
    this.name = name;
  }

  public String getName() {
    return this.name;
  }

  protected Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
}

在Spring中,在org.springframework.beans.factory.support.AbstractBeanFactory中使用一種特定的原型設計模式麻献,它將初始化bean原型作用域们妥。新對象基于配置文件中的bean定義。我們可以看到勉吻,在給定的例子中:

<bean id="shoppingCart" class="com.waitingforcode.data.ShoppingCart" scope="prototype">
  <property name="id" value="9"></property>
</bean>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"applicationContext-test.xml"})
public class SpringPrototypeTest {

  @Autowired
  private BeanFactory beanFactory;

  @Test
  public void test() {
    ShoppingCart cart1 = (ShoppingCart) beanFactory.getBean("shoppingCart");
    assertTrue("Id of cart1 should be 9 but was "+cart1.getId(), 
      cart1.getId() == 9);
    cart1.setId(100);
    ShoppingCart cart2 = (ShoppingCart) beanFactory.getBean("shoppingCart");
    assertTrue("Id of cart2 should be 9 but was "+cart2.getId(), 
      cart2.getId() == 9);
    assertTrue("Id of second cart ("+cart2.getId()+") shouldn't be the same as the first one: "+cart1.getId(), 
      cart1.getId() != cart2.getId());
    cart2.setId(cart1.getId());
    assertTrue("Now (after cart2.setId(cart1.getId())), the id of second cart ("+cart2.getId()+") should be the same as the first one: "
      +cart1.getId(), cart1.getId() == cart2.getId());
    assertTrue("Both instance shouldn't be the same", cart1 != cart2);
  }

}

從前面的例子可以看出监婶,ShoppingCart實例是直接從bean定義創(chuàng)建的。最初齿桃,cart1和cart2對象的id值為9.它在測試結(jié)束時被修改惑惶,以證明兩個引用都屬于兩個不同的對象。

對象池

Spring中使用的另一個模型是對象池設計模式短纵。其主要目的在于在一個池中保存特定數(shù)量的對象带污,并根據(jù)需要重新使用。通過它香到,我們可以改善我們想要使用巨型對象的響應時間鱼冀。巨型意味著這些對象的構(gòu)造需要很多時間(例如:持有數(shù)據(jù)庫連接的對象)报破,最好重用已經(jīng)存在的和未獲取的對象,而不是創(chuàng)建新對象千绪。

Spring還使用線程池來管理其調(diào)度部分充易。一些示例位于org.springframework.scheduling.concurrent中。我們檢索數(shù)據(jù)庫(Spring JDBC)項目中的對象池的想法荸型。數(shù)據(jù)庫連接池不是由Spring直接實現(xiàn)的蔽氨,而是適用于Spring工作方式的項目,如C3P0或Jakarta Commons DBCP連接池帆疟。

觀察者

這里呈現(xiàn)的最后一個設計模式是觀察者鹉究。當一個或幾個課程正在等待具體事件時可以使用它。觀察者模式由一個科目和觀察員名單組成踪宠。一個很好的例子就是GUI界面自赔,其中點擊按鈕(按鈕是主題)會引起聽眾(觀察者)啟動的一些操作(再說的直白點就是電影院一場電影這個subject,需要觀眾(也就是觀察者咯),電影產(chǎn)生的一些畫面產(chǎn)生的事件,比如恐怖 電影給男人女人帶來的不同的感官的感受柳琢,傳播到觀察者也就是觀眾的眼里所帶來的不一樣的反應绍妨,這個中間一般會添加一個事件傳播者,在后面解釋Spring的例子的時候會說到)柬脸,例如:打開一個新頁面這個動作他去。可以參考下面的例子:

public class ObserverTest {

  @Test
  public void test() {
    Observer pageOpener = new PageOpener();
    Observer register = new Register();
    Button btn = new Button();
    btn.addListener(pageOpener);
    btn.addListener(register);
    btn.clickOn();
    assertTrue("Button should be clicked but it wasn't", 
      btn.wasClicked());
    assertTrue("Page opener should be informed about click but it wasn't", 
      pageOpener.wasInformed());
    assertTrue("Register should be informed about click but it wasn't", 
      register.wasInformed());
  }

}

class Button {

  private boolean clicked;
  private List<observer> listeners;

  public List<observer> getListeners() {
    if (this.listeners == null) {
      this.listeners = new ArrayList<observer>();
    }
    return this.listeners;
  }

  public void addListener(Observer observer) {
    getListeners().add(observer);
  }

  public boolean wasClicked() {
    return this.clicked;
  }

  public void clickOn() {
    this.clicked = true;
    informAll();
  }

  private void informAll() {
    for (Observer observer : getListeners()) {
      observer.informAboutEvent();
    }
  }

}

abstract class Observer {
  protected boolean informed;

  public void informAboutEvent() {
    this.informed = true;
  }

  public boolean wasInformed() {
    return this.informed;
  }
}

class PageOpener extends Observer {

  @Override
  public void informAboutEvent() {
    System.out.println("Preparing download of new page");
    super.informAboutEvent();
  }

}

class Register extends Observer {

  @Override
  public void informAboutEvent() {
    System.out.println("Adding the action to register");
    super.informAboutEvent();
  }
}

可以看到倒堕,關(guān)于我們的Button實例點擊的事件被發(fā)送到所有的觀察者對象灾测。從這些對象開始下載頁面內(nèi)容,第二個將在事件的信息保存在注冊表中垦巴。在Spring中媳搪,觀察者設計模式用于將與應用程序上下文相關(guān)的事件傳輸?shù)給rg.springframework.context.ApplicationListener的實現(xiàn)。要了解它們的實現(xiàn)方法骤宣,我們來看一下AbstractApplicationContext類(老版本的代碼秦爆,新版本的請自行對照):

public abstract class AbstractApplicationContext extends DefaultResourceLoader
  implements ConfigurableApplicationContext, DisposableBean {
  /** Statically specified listeners */
  private Set<applicationlistener<?>> applicationListeners = new LinkedHashSet<applicationlistener<?>>();

  // some other fields and methods
  @Override
  public void addApplicationListener(ApplicationListener<?> listener) {
    if (this.applicationEventMulticaster != null) {
      this.applicationEventMulticaster.addApplicationListener(listener);
    }
    else {//新版本這里直接咔嚓掉,上面的applicationEventMulticaster一旦為空憔披,就會報錯的
      this.applicationListeners.add(listener);
    }
  }

  /**
    * Return the list of statically specified ApplicationListeners.
    */
  public Collection<applicationlistener<?>> getApplicationListeners() {
    return this.applicationListeners;
  }

  /**
    * Add beans that implement ApplicationListener as listeners.
    * Doesn't affect other listeners, which can be added without being beans.
    */
  protected void registerListeners() {
    // Register statically specified listeners first.
    for (ApplicationListener<?> listener : getApplicationListeners()) {
      getApplicationEventMulticaster().addApplicationListener(listener);
    }
    // Do not initialize FactoryBeans here: We need to leave all regular beans
    // uninitialized to let post-processors apply to them!
    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    for (String lisName : listenerBeanNames) {
      getApplicationEventMulticaster().addApplicationListenerBean(lisName);
    }
  }
}

在提供的代碼中等限,監(jiān)聽器在內(nèi)部添加到應用程序上下文類中,并且在registerListeners()方法之后芬膝,它們被注冊到由接口org.springframework.context.event.ApplicationEventMulticaster表示的適當?shù)氖录嗦窂V播器(因為有很多l(xiāng)isteners)望门。EventMulticaster負責管理不同的listener和向他們發(fā)布事件。

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
    private Executor taskExecutor;
    private ErrorHandler errorHandler;
    public SimpleApplicationEventMulticaster() {
    }
    public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
        this.setBeanFactory(beanFactory);
    }
    public void setTaskExecutor(Executor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }
    protected Executor getTaskExecutor() {
        return this.taskExecutor;
    }
    public void setErrorHandler(ErrorHandler errorHandler) {
        this.errorHandler = errorHandler;
    }
    protected ErrorHandler getErrorHandler() {
        return this.errorHandler;
    }
    public void multicastEvent(ApplicationEvent event) {
        this.multicastEvent(event, this.resolveDefaultEventType(event));
    }
    //發(fā)布事件:通過池執(zhí)行任務的方式來做并發(fā)處理蔗候,這樣就把之前的對象池模式給利用上了
    public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
        ResolvableType type = eventType != null?eventType:this.resolveDefaultEventType(event);
        Iterator var4 = this.getApplicationListeners(event, type).iterator();
        while(var4.hasNext()) {
            final ApplicationListener<?> listener = (ApplicationListener)var4.next();
            Executor executor = this.getTaskExecutor();
            if(executor != null) {
                executor.execute(new Runnable() {
                    public void run() {
                        SimpleApplicationEventMulticaster.this.invokeListener(listener, event);
                    }
                });
            } else {
                this.invokeListener(listener, event);
            }
        }
    }
...
}

這次我們講3種設計模式:用于在同一個調(diào)用作用域內(nèi)創(chuàng)建bean的原型怒允,避免重新創(chuàng)建巨型對象的對象池埂软,以及將應用程序的上下文事件分派給適當?shù)谋O(jiān)聽器的觀察者锈遥。


原文:Spring框架中的設計模式(三)
轉(zhuǎn)載:極樂科技

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纫事,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子所灸,更是在濱河造成了極大的恐慌丽惶,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爬立,死亡現(xiàn)場離奇詭異钾唬,居然都是意外死亡,警方通過查閱死者的電腦和手機侠驯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門抡秆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吟策,你說我怎么就攤上這事儒士。” “怎么了檩坚?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵着撩,是天一觀的道長。 經(jīng)常有香客問我匾委,道長拖叙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任赂乐,我火速辦了婚禮薯鳍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘挨措。我一直安慰自己辐啄,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布运嗜。 她就那樣靜靜地躺著壶辜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪担租。 梳的紋絲不亂的頭發(fā)上砸民,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音奋救,去河邊找鬼岭参。 笑死,一個胖子當著我的面吹牛尝艘,可吹牛的內(nèi)容都是我干的演侯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼背亥,長吁一口氣:“原來是場噩夢啊……” “哼秒际!你這毒婦竟也來了悬赏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤娄徊,失蹤者是張志新(化名)和其女友劉穎闽颇,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寄锐,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡兵多,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了橄仆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剩膘。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖盆顾,靈堂內(nèi)的尸體忽然破棺而出援雇,到底是詐尸還是另有隱情,我是刑警寧澤椎扬,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布惫搏,位于F島的核電站,受9級特大地震影響蚕涤,放射性物質(zhì)發(fā)生泄漏筐赔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一揖铜、第九天 我趴在偏房一處隱蔽的房頂上張望茴丰。 院中可真熱鬧,春花似錦天吓、人聲如沸贿肩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汰规。三九已至,卻和暖如春物邑,著一層夾襖步出監(jiān)牢的瞬間溜哮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工色解, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留茂嗓,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓科阎,卻偏偏與公主長得像述吸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子锣笨,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容