9種設(shè)計(jì)模式在Spring中的運(yùn)用贬丛,一定要非常熟練撩银!

來自:SCDN(微信號:iCoding91
原文鏈接:
https://blog.csdn.net/caoxiaohong1005/article/details/80039656

Spring中涉及的設(shè)計(jì)模式總結(jié)

1.簡單工廠(非23種設(shè)計(jì)模式中的一種)

實(shí)現(xiàn)方式:

BeanFactory。Spring中的BeanFactory就是簡單工廠模式的體現(xiàn)豺憔,根據(jù)傳入一個唯一的標(biāo)識來獲得Bean對象额获,但是否是在傳入?yún)?shù)后創(chuàng)建還是傳入?yún)?shù)前創(chuàng)建這個要根據(jù)具體情況來定。

實(shí)質(zhì):

由一個工廠類根據(jù)傳入的參數(shù)恭应,動態(tài)決定應(yīng)該創(chuàng)建哪一個產(chǎn)品類咪啡。

實(shí)現(xiàn)原理:

bean容器的啟動階段:

  • 讀取bean的xml配置文件,將bean元素分別轉(zhuǎn)換成一個BeanDefinition對象。

  • 然后通過BeanDefinitionRegistry將這些bean注冊到beanFactory中暮屡,保存在它的一個ConcurrentHashMap中撤摸。

  • 將BeanDefinition注冊到了beanFactory之后,在這里Spring為我們提供了一個擴(kuò)展的切口,允許我們通過實(shí)現(xiàn)接口BeanFactoryPostProcessor 在此處來插入我們定義的代碼准夷。

    典型的例子就是:PropertyPlaceholderConfigurer钥飞,我們一般在配置數(shù)據(jù)庫的dataSource時使用到的占位符的值,就是它注入進(jìn)去的衫嵌。

容器中bean的實(shí)例化階段:

實(shí)例化階段主要是通過反射或者CGLIB對bean進(jìn)行實(shí)例化读宙,在這個階段Spring又給我們暴露了很多的擴(kuò)展點(diǎn):

  • 各種的Aware接口,比如 BeanFactoryAware楔绞,對于實(shí)現(xiàn)了這些Aware接口的bean结闸,在實(shí)例化bean時Spring會幫我們注入對應(yīng)的BeanFactory的實(shí)例。

  • BeanPostProcessor接口酒朵,實(shí)現(xiàn)了BeanPostProcessor接口的bean桦锄,在實(shí)例化bean時Spring會幫我們調(diào)用接口中的方法。

  • InitializingBean接口蔫耽,實(shí)現(xiàn)了InitializingBean接口的bean结耀,在實(shí)例化bean時Spring會幫我們調(diào)用接口中的方法。

  • DisposableBean接口匙铡,實(shí)現(xiàn)了BeanPostProcessor接口的bean图甜,在該bean死亡時Spring會幫我們調(diào)用接口中的方法。

設(shè)計(jì)意義:

松耦合鳖眼。可以將原來硬編碼的依賴黑毅,通過Spring這個beanFactory這個工廠來注入依賴,也就是說原來只有依賴方和被依賴方钦讳,現(xiàn)在我們引入了第三方——spring這個beanFactory博肋,由它來解決bean之間的依賴問題,達(dá)到了松耦合的效果.

bean的額外處理蜂厅。通過Spring接口的暴露匪凡,在實(shí)例化bean的階段我們可以進(jìn)行一些額外的處理,這些額外的處理只需要讓bean實(shí)現(xiàn)對應(yīng)的接口即可掘猿,那么spring就會在bean的生命周期調(diào)用我們實(shí)現(xiàn)的接口來處理該bean病游。[非常重要]

2.工廠方法

實(shí)現(xiàn)方式:

FactoryBean接口。

實(shí)現(xiàn)原理:

實(shí)現(xiàn)了FactoryBean接口的bean是一類叫做factory的bean稠通。其特點(diǎn)是衬衬,spring會在使用getBean()調(diào)用獲得該bean時,會自動調(diào)用該bean的getObject()方法改橘,所以返回的不是factory這個bean滋尉,而是這個bean.getOjbect()方法的返回值。

例子:

典型的例子有spring與mybatis的結(jié)合飞主。

代碼示例:

image

說明:

我們看上面該bean狮惜,因?yàn)閷?shí)現(xiàn)了FactoryBean接口高诺,所以返回的不是 SqlSessionFactoryBean 的實(shí)例,而是它的 SqlSessionFactoryBean.getObject() 的返回值碾篡。

3.單例模式

Spring依賴注入Bean實(shí)例默認(rèn)是單例的虱而。

Spring的依賴注入(包括lazy-init方式)都是發(fā)生在AbstractBeanFactory的getBean里。getBean的doGetBean方法調(diào)用getSingleton進(jìn)行bean的創(chuàng)建开泽。

分析getSingleton()方法

public Object getSingleton(String beanName){
    //參數(shù)true設(shè)置標(biāo)識允許早期依賴
    return getSingleton(beanName,true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //檢查緩存中是否存在實(shí)例
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        //如果為空牡拇,則鎖定全局變量并進(jìn)行處理。
        synchronized (this.singletonObjects) {
            //如果此bean正在加載穆律,則不處理
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {  
                //當(dāng)某些方法需要提前初始化的時候則會調(diào)用addSingleFactory 方法將對應(yīng)的ObjectFactory初始化策略存儲在singletonFactories
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    //調(diào)用預(yù)先設(shè)定的getObject方法
                    singletonObject = singletonFactory.getObject();
                    //記錄在緩存中惠呼,earlysingletonObjects和singletonFactories互斥
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

getSingleton()過程圖

ps:spring依賴注入時,使用了 雙重判斷加鎖 的單例模式

image

總結(jié)

單例模式定義:保證一個類僅有一個實(shí)例峦耘,并提供一個訪問它的全局訪問點(diǎn)剔蹋。

spring對單例的實(shí)現(xiàn):spring中的單例模式完成了后半句話,即提供了全局的訪問點(diǎn)BeanFactory贡歧。但沒有從構(gòu)造器級別去控制單例滩租,這是因?yàn)閟pring管理的是任意的java對象赋秀。

4.適配器模式

實(shí)現(xiàn)方式:

SpringMVC中的適配器HandlerAdatper利朵。

實(shí)現(xiàn)原理:

HandlerAdatper根據(jù)Handler規(guī)則執(zhí)行不同的Handler。

實(shí)現(xiàn)過程:

DispatcherServlet根據(jù)HandlerMapping返回的handler猎莲,向HandlerAdatper發(fā)起請求绍弟,處理Handler。

HandlerAdapter根據(jù)規(guī)則找到對應(yīng)的Handler并讓其執(zhí)行著洼,執(zhí)行完畢后Handler會向HandlerAdapter返回一個ModelAndView樟遣,最后由HandlerAdapter向DispatchServelet返回一個ModelAndView。

實(shí)現(xiàn)意義:

HandlerAdatper使得Handler的擴(kuò)展變得容易身笤,只需要增加一個新的Handler和一個對應(yīng)的HandlerAdapter即可豹悬。

因此Spring定義了一個適配接口,使得每一種Controller有一種對應(yīng)的適配器實(shí)現(xiàn)類液荸,讓適配器代替controller執(zhí)行相應(yīng)的方法瞻佛。這樣在擴(kuò)展Controller時,只需要增加一個適配器類就完成了SpringMVC的擴(kuò)展了娇钱。

5.裝飾器模式

實(shí)現(xiàn)方式:

Spring中用到的包裝器模式在類名上有兩種表現(xiàn):一種是類名中含有Wrapper伤柄,另一種是類名中含有Decorator。

實(shí)質(zhì):

動態(tài)地給一個對象添加一些額外的職責(zé)文搂。

就增加功能來說适刀,Decorator模式相比生成子類更為靈活。

6.代理模式

實(shí)現(xiàn)方式:

AOP底層煤蹭,就是動態(tài)代理模式的實(shí)現(xiàn)笔喉。

動態(tài)代理:

在內(nèi)存中構(gòu)建的取视,不需要手動編寫代理類

靜態(tài)代理:

需要手工編寫代理類,代理類引用被代理對象然遏。

實(shí)現(xiàn)原理:

切面在應(yīng)用運(yùn)行的時刻被織入贫途。一般情況下,在織入切面時待侵,AOP容器會為目標(biāo)對象創(chuàng)建動態(tài)的創(chuàng)建一個代理對象丢早。SpringAOP就是以這種方式織入切面的。

織入:把切面應(yīng)用到目標(biāo)對象并創(chuàng)建新的代理對象的過程秧倾。

7.觀察者模式

實(shí)現(xiàn)方式:

spring的事件驅(qū)動模型使用的是 觀察者模式 怨酝,Spring中Observer模式常用的地方是listener的實(shí)現(xiàn)。

具體實(shí)現(xiàn):

事件機(jī)制的實(shí)現(xiàn)需要三個部分,事件源,事件,事件監(jiān)聽器

ApplicationEvent抽象類[事件]

繼承自jdk的EventObject,所有的事件都需要繼承ApplicationEvent,并且通過構(gòu)造器參數(shù)source得到事件源.

該類的實(shí)現(xiàn)類ApplicationContextEvent表示ApplicaitonContext的容器事件.

代碼:

public abstract class ApplicationEvent extends EventObject {
    private static final long serialVersionUID = 7099057708183571937L;
    private final long timestamp;
    public ApplicationEvent(Object source) {
    super(source);
    this.timestamp = System.currentTimeMillis();
    }
    public final long getTimestamp() {
        return this.timestamp;
    }
}

ApplicationListener接口[事件監(jiān)聽器]

繼承自jdk的EventListener,所有的監(jiān)聽器都要實(shí)現(xiàn)這個接口那先。

這個接口只有一個onApplicationEvent()方法,該方法接受一個ApplicationEvent或其子類對象作為參數(shù),在方法體中,可以通過不同對Event類的判斷來進(jìn)行相應(yīng)的處理农猬。

當(dāng)事件觸發(fā)時所有的監(jiān)聽器都會收到消息。

代碼:

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
     void onApplicationEvent(E event);
} 

ApplicationContext接口[事件源]

ApplicationContext是spring中的全局容器售淡,翻譯過來是”應(yīng)用上下文”斤葱。

實(shí)現(xiàn)了ApplicationEventPublisher接口。

職責(zé):

負(fù)責(zé)讀取bean的配置文檔,管理bean的加載,維護(hù)bean之間的依賴關(guān)系,可以說是負(fù)責(zé)bean的整個生命周期,再通俗一點(diǎn)就是我們平時所說的IOC容器揖闸。

代碼:

public interface ApplicationEventPublisher {
        void publishEvent(ApplicationEvent event);
}   

public void publishEvent(ApplicationEvent event) {
    Assert.notNull(event, "Event must not be null");
    if (logger.isTraceEnabled()) {
         logger.trace("Publishing event in " + getDisplayName() + ": " + event);
    }
    getApplicationEventMulticaster().multicastEvent(event);
    if (this.parent != null) {
    this.parent.publishEvent(event);
    }
}

ApplicationEventMulticaster抽象類[事件源中publishEvent方法需要調(diào)用其方法getApplicationEventMulticaster]

屬于事件廣播器,它的作用是把Applicationcontext發(fā)布的Event廣播給所有的監(jiān)聽器.

代碼:

public abstract class AbstractApplicationContext extends DefaultResourceLoader
    implements ConfigurableApplicationContext, DisposableBean {  
    private ApplicationEventMulticaster applicationEventMulticaster;  
    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);  
    }  
  }  
}

8.策略模式

實(shí)現(xiàn)方式:

Spring框架的資源訪問Resource接口揍堕。該接口提供了更強(qiáng)的資源訪問能力,Spring 框架本身大量使用了 Resource 接口來訪問底層資源汤纸。

Resource 接口介紹

source 接口是具體資源訪問策略的抽象衩茸,也是所有資源訪問類所實(shí)現(xiàn)的接口。

Resource 接口主要提供了如下幾個方法:

  • getInputStream():定位并打開資源贮泞,返回資源對應(yīng)的輸入流楞慈。每次調(diào)用都返回新的輸入流。調(diào)用者必須負(fù)責(zé)關(guān)閉輸入流啃擦。

  • exists():返回 Resource 所指向的資源是否存在囊蓝。

  • isOpen():返回資源文件是否打開,如果資源文件不能多次讀取令蛉,每次讀取結(jié)束應(yīng)該顯式關(guān)閉聚霜,以防止資源泄漏。

  • getDescription():返回資源的描述信息言询,通常用于資源處理出錯時輸出該信息俯萎,通常是全限定文件名或?qū)嶋H URL。

  • getFile:返回資源對應(yīng)的 File 對象运杭。

  • getURL:返回資源對應(yīng)的 URL 對象夫啊。

最后兩個方法通常無須使用,僅在通過簡單方式訪問無法實(shí)現(xiàn)時辆憔,Resource 提供傳統(tǒng)的資源訪問的功能撇眯。

Resource 接口本身沒有提供訪問任何底層資源的實(shí)現(xiàn)邏輯报嵌,針對不同的底層資源,Spring 將會提供不同的 Resource 實(shí)現(xiàn)類熊榛,不同的實(shí)現(xiàn)類負(fù)責(zé)不同的資源訪問邏輯锚国。

Spring 為 Resource 接口提供了如下實(shí)現(xiàn)類:

  • UrlResource:訪問網(wǎng)絡(luò)資源的實(shí)現(xiàn)類。

  • ClassPathResource:訪問類加載路徑里資源的實(shí)現(xiàn)類玄坦。

  • FileSystemResource:訪問文件系統(tǒng)里資源的實(shí)現(xiàn)類血筑。

  • ServletContextResource:訪問相對于 ServletContext 路徑里的資源的實(shí)現(xiàn)類.

  • InputStreamResource:訪問輸入流資源的實(shí)現(xiàn)類。

  • ByteArrayResource:訪問字節(jié)數(shù)組資源的實(shí)現(xiàn)類煎楣。

這些 Resource 實(shí)現(xiàn)類豺总,針對不同的的底層資源,提供了相應(yīng)的資源訪問邏輯择懂,并提供便捷的包裝喻喳,以利于客戶端程序的資源訪問。

9.模版方法模式

經(jīng)典模板方法定義:

父類定義了骨架(調(diào)用哪些方法及順序)困曙,某些特定方法由子類實(shí)現(xiàn)表伦。

最大的好處:代碼復(fù)用,減少重復(fù)代碼慷丽。除了子類要實(shí)現(xiàn)的特定方法蹦哼,其他方法及方法調(diào)用順序都在父類中預(yù)先寫好了。

所以父類模板方法中有兩類方法:

共同的方法:所有子類都會用到的代碼

不同的方法:子類要覆蓋的方法盈魁,分為兩種:

  • 抽象方法:父類中的是抽象方法翔怎,子類必須覆蓋

  • 鉤子方法:父類中是一個空方法窃诉,子類繼承了默認(rèn)也是空的

注:為什么叫鉤子杨耙,子類可以通過這個鉤子(方法),控制父類飘痛,因?yàn)檫@個鉤子實(shí)際是父類的方法(空方法)珊膜!

Spring模板方法模式實(shí)質(zhì):

是模板方法模式和回調(diào)模式的結(jié)合,是Template Method不需要繼承的另一種實(shí)現(xiàn)方式宣脉。Spring幾乎所有的外接擴(kuò)展都采用這種模式车柠。

具體實(shí)現(xiàn):

JDBC的抽象和對Hibernate的集成,都采用了一種理念或者處理方式塑猖,那就是模板方法模式與相應(yīng)的Callback接口相結(jié)合。

采用模板方法模式是為了以一種統(tǒng)一而集中的方式來處理資源的獲取和釋放,以JdbcTempalte為例:

public abstract class JdbcTemplate {  
     public final Object execute(String sql){  
        Connection con=null;  
        Statement stmt=null;  
        try{  
            con=getConnection();  
            stmt=con.createStatement();  
            Object retValue=executeWithStatement(stmt,sql);  
            return retValue;  
        }catch(SQLException e){  
             ...  
        }finally{  
            closeStatement(stmt);  
            releaseConnection(con);  
        }  
    }   
    protected abstract Object executeWithStatement(Statement   stmt, String sql);  
}  

引入回調(diào)原因:

JdbcTemplate是抽象類箫措,不能夠獨(dú)立使用爱态,我們每次進(jìn)行數(shù)據(jù)訪問的時候都要給出一個相應(yīng)的子類實(shí)現(xiàn),這樣肯定不方便,所以就引入了回調(diào)蜡励。

回調(diào)代碼

public interface StatementCallback{  
    Object doWithStatement(Statement stmt);  
}   

利用回調(diào)方法重寫JdbcTemplate方法

public class JdbcTemplate {  
    public final Object execute(StatementCallback callback){  
        Connection con=null;  
        Statement stmt=null;  
        try{  
            con=getConnection();  
            stmt=con.createStatement();  
            Object retValue=callback.doWithStatement(stmt);  
            return retValue;  
        }catch(SQLException e){  
            ...  
        }finally{  
            closeStatement(stmt);  
            releaseConnection(con);  
        }  
    }  

    ...//其它方法定義  
}   

Jdbc使用方法如下:

JdbcTemplate jdbcTemplate=...;  
    final String sql=...;  
    StatementCallback callback=new StatementCallback(){  
    public Object=doWithStatement(Statement stmt){  
        return ...;  
    }  
}    
jdbcTemplate.execute(callback);  

為什么JdbcTemplate沒有使用繼承令花?

因?yàn)檫@個類的方法太多阻桅,但是我們還是想用到JdbcTemplate已有的穩(wěn)定的、公用的數(shù)據(jù)庫連接兼都,那么我們怎么辦呢嫂沉?

我們可以把變化的東西抽出來作為一個參數(shù)傳入JdbcTemplate的方法中。但是變化的東西是一段代碼扮碧,而且這段代碼會用到JdbcTemplate中的變量趟章。怎么辦?

那我們就用回調(diào)對象吧慎王。在這個回調(diào)對象中定義一個操縱JdbcTemplate中變量的方法尤揣,我們?nèi)?shí)現(xiàn)這個方法,就把變化的東西集中到這里了柬祠。然后我們再傳入這個回調(diào)對象到JdbcTemplate北戏,從而完成了調(diào)用。

參考

https://www.cnblogs.com/digdeep/p/4518571.html
https://www.cnblogs.com/tongkey/p/7919401.html
https://www.cnblogs.com/fingerboy/p/6393644.html
https://blog.csdn.net/ovoo_8/article/details/51189401
https://blog.csdn.net/z69183787/article/details/65628166
《spring源碼深度分析》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末漫蛔,一起剝皮案震驚了整個濱河市嗜愈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌莽龟,老刑警劉巖蠕嫁,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異毯盈,居然都是意外死亡剃毒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門搂赋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赘阀,“玉大人,你說我怎么就攤上這事脑奠』” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵宋欺,是天一觀的道長轰豆。 經(jīng)常有香客問我,道長齿诞,這世上最難降的妖魔是什么酸休? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮祷杈,結(jié)果婚禮上斑司,老公的妹妹穿的比我還像新娘。我一直安慰自己吠式,他們只是感情好陡厘,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布抽米。 她就那樣靜靜地躺著,像睡著了一般糙置。 火紅的嫁衣襯著肌膚如雪云茸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天谤饭,我揣著相機(jī)與錄音标捺,去河邊找鬼。 笑死揉抵,一個胖子當(dāng)著我的面吹牛亡容,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冤今,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼闺兢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了戏罢?” 一聲冷哼從身側(cè)響起屋谭,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎龟糕,沒想到半個月后桐磁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡讲岁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年我擂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缓艳。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡校摩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出郎任,到底是詐尸還是另有隱情秧耗,我是刑警寧澤备籽,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布舶治,位于F島的核電站,受9級特大地震影響车猬,放射性物質(zhì)發(fā)生泄漏霉猛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一珠闰、第九天 我趴在偏房一處隱蔽的房頂上張望惜浅。 院中可真熱鬧,春花似錦伏嗜、人聲如沸坛悉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽裸影。三九已至挣轨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間轩猩,已是汗流浹背卷扮。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留均践,地道東北人晤锹。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像彤委,于是被迫代替她去往敵國和親鞭铆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345