Spring 中經典的 9 種設計模式,打死也要記住啊!

?

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

實現方式:

BeanFactory。Spring中的BeanFactory就是簡單工廠模式的體現,根據傳入一個唯一的標識來獲得Bean對象,但是否是在傳入參數后創建還是傳入參數前創建這個要根據具體情況來定。

實質:

由一個工廠類根據傳入的參數,動態決定應該創建哪一個產品類。

實現原理:

bean容器的啟動階段:

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

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

將BeanDefinition注冊到了beanFactory之后,在這里Spring為我們提供了一個擴展的切口,允許我們通過實現接口BeanFactoryPostProcessor 在此處來插入我們定義的代碼。

典型的例子就是:PropertyPlaceholderConfigurer,我們一般在配置數據庫的dataSource時使用到的占位符的值,就是它注入進去的。

容器中bean的實例化階段:

實例化階段主要是通過反射或者CGLIB對bean進行實例化,在這個階段Spring又給我們暴露了很多的擴展點:

各種的Aware接口,比如 BeanFactoryAware,對于實現了這些Aware接口的bean,在實例化bean時Spring會幫我們注入對應的BeanFactory的實例。

BeanPostProcessor接口,實現了BeanPostProcessor接口的bean,在實例化bean時Spring會幫我們調用接口中的方法。

InitializingBean接口,實現了InitializingBean接口的bean,在實例化bean時Spring會幫我們調用接口中的方法。

DisposableBean接口,實現了BeanPostProcessor接口的bean,在該bean死亡時Spring會幫我們調用接口中的方法。

設計意義:

松耦合。可以將原來硬編碼的依賴,通過Spring這個beanFactory這個工廠來注入依賴,也就是說原來只有依賴方和被依賴方,現在我們引入了第三方——spring這個beanFactory,由它來解決bean之間的依賴問題,達到了松耦合的效果.

bean的額外處理。通過Spring接口的暴露,在實例化bean的階段我們可以進行一些額外的處理,這些額外的處理只需要讓bean實現對應的接口即可,那么spring就會在bean的生命周期調用我們實現的接口來處理該bean。[非常重要]

基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

項目地址:https://github.com/YunaiV/ruoyi-vue-pro

視頻教程:https://doc.iocoder.cn/video/


2.工廠方法

實現方式:

FactoryBean接口。

實現原理:

實現了FactoryBean接口的bean是一類叫做factory的bean。其特點是,spring會在使用getBean()調用獲得該bean時,會自動調用該bean的getObject()方法,所以返回的不是factory這個bean,而是這個bean.getOjbect()方法的返回值。

例子:

典型的例子有spring與mybatis的結合。

代碼示例:

說明:

我們看上面該bean,因為實現了FactoryBean接口,所以返回的不是 SqlSessionFactoryBean 的實例,而是它的 SqlSessionFactoryBean.getObject() 的返回值。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

項目地址:https://github.com/YunaiV/yudao-cloud

視頻教程:https://doc.iocoder.cn/video/

3.單例模式

Spring依賴注入Bean實例默認是單例的。

Spring的依賴注入(包括lazy-init方式)都是發生在AbstractBeanFactory的getBean里。getBean的doGetBean方法調用getSingleton進行bean的創建。

分析getSingleton()方法

public Object getSingleton(String beanName){

? ? //參數true設置標識允許早期依賴

? ? return getSingleton(beanName,true);

}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

? ? //檢查緩存中是否存在實例

? ? Object singletonObject = this.singletonObjects.get(beanName);

? ? if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

? ? ? ? //如果為空,則鎖定全局變量并進行處理。

? ? ? ? synchronized (this.singletonObjects) {

? ? ? ? ? ? //如果此bean正在加載,則不處理

? ? ? ? ? ? singletonObject = this.earlySingletonObjects.get(beanName);

? ? ? ? ? ? if (singletonObject == null && allowEarlyReference) {

? ? ? ? ? ? ? ? //當某些方法需要提前初始化的時候則會調用addSingleFactory 方法將對應的ObjectFactory初始化策略存儲在singletonFactories

? ? ? ? ? ? ? ? ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);

? ? ? ? ? ? ? ? if (singletonFactory != null) {

? ? ? ? ? ? ? ? ? ? //調用預先設定的getObject方法

? ? ? ? ? ? ? ? ? ? singletonObject = singletonFactory.getObject();

? ? ? ? ? ? ? ? ? ? //記錄在緩存中,earlysingletonObjects和singletonFactories互斥

? ? ? ? ? ? ? ? ? ? this.earlySingletonObjects.put(beanName, singletonObject);

? ? ? ? ? ? ? ? ? ? this.singletonFactories.remove(beanName);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? }

? ? return (singletonObject != NULL_OBJECT ? singletonObject : null);

}

getSingleton()過程圖

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

總結

單例模式定義:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。

spring對單例的實現:spring中的單例模式完成了后半句話,即提供了全局的訪問點BeanFactory。但沒有從構造器級別去控制單例,這是因為spring管理的是任意的java對象。

4.適配器模式

實現方式:

SpringMVC中的適配器HandlerAdatper。

實現原理:

HandlerAdatper根據Handler規則執行不同的Handler。

實現過程:

DispatcherServlet根據HandlerMapping返回的handler,向HandlerAdatper發起請求,處理Handler。

HandlerAdapter根據規則找到對應的Handler并讓其執行,執行完畢后Handler會向HandlerAdapter返回一個ModelAndView,最后由HandlerAdapter向DispatchServelet返回一個ModelAndView。

實現意義:

HandlerAdatper使得Handler的擴展變得容易,只需要增加一個新的Handler和一個對應的HandlerAdapter即可。

因此Spring定義了一個適配接口,使得每一種Controller有一種對應的適配器實現類,讓適配器代替controller執行相應的方法。這樣在擴展Controller時,只需要增加一個適配器類就完成了SpringMVC的擴展了。

5.裝飾器模式

實現方式:

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

實質:

動態地給一個對象添加一些額外的職責。

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

6.代理模式

實現方式:

AOP底層,就是動態代理模式的實現。

動態代理:

在內存中構建的,不需要手動編寫代理類

靜態代理:

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

實現原理:

切面在應用運行的時刻被織入。一般情況下,在織入切面時,AOP容器會為目標對象創建動態的創建一個代理對象。SpringAOP就是以這種方式織入切面的。

織入:把切面應用到目標對象并創建新的代理對象的過程。

7.觀察者模式

實現方式:

spring的事件驅動模型使用的是 觀察者模式 ,Spring中Observer模式常用的地方是listener的實現。

具體實現:

事件機制的實現需要三個部分,事件源,事件,事件監聽器

ApplicationEvent抽象類[事件]

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

該類的實現類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接口[事件監聽器]

繼承自jdk的EventListener,所有的監聽器都要實現這個接口。

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

當事件觸發時所有的監聽器都會收到消息。

代碼:

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

? ? void onApplicationEvent(E event);

}

ApplicationContext接口[事件源]

ApplicationContext是spring中的全局容器,翻譯過來是”應用上下文”。

實現了ApplicationEventPublisher接口。

職責:

負責讀取bean的配置文檔,管理bean的加載,維護bean之間的依賴關系,可以說是負責bean的整個生命周期,再通俗一點就是我們平時所說的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方法需要調用其方法getApplicationEventMulticaster]

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

代碼:

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.策略模式

實現方式:

Spring框架的資源訪問Resource接口。該接口提供了更強的資源訪問能力,Spring 框架本身大量使用了 Resource 接口來訪問底層資源。

Resource 接口介紹

source 接口是具體資源訪問策略的抽象,也是所有資源訪問類所實現的接口。

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

getInputStream():定位并打開資源,返回資源對應的輸入流。每次調用都返回新的輸入流。調用者必須負責關閉輸入流。

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

isOpen():返回資源文件是否打開,如果資源文件不能多次讀取,每次讀取結束應該顯式關閉,以防止資源泄漏。

getDescription():返回資源的描述信息,通常用于資源處理出錯時輸出該信息,通常是全限定文件名或實際 URL。

getFile:返回資源對應的 File 對象。

getURL:返回資源對應的 URL 對象。

最后兩個方法通常無須使用,僅在通過簡單方式訪問無法實現時,Resource 提供傳統的資源訪問的功能。

Resource 接口本身沒有提供訪問任何底層資源的實現邏輯,針對不同的底層資源,Spring 將會提供不同的 Resource 實現類,不同的實現類負責不同的資源訪問邏輯。

Spring 為 Resource 接口提供了如下實現類:

UrlResource:訪問網絡資源的實現類。

ClassPathResource:訪問類加載路徑里資源的實現類。

FileSystemResource:訪問文件系統里資源的實現類。

ServletContextResource:訪問相對于 ServletContext 路徑里的資源的實現類.

InputStreamResource:訪問輸入流資源的實現類。

ByteArrayResource:訪問字節數組資源的實現類。

這些 Resource 實現類,針對不同的的底層資源,提供了相應的資源訪問邏輯,并提供便捷的包裝,以利于客戶端程序的資源訪問。

9.模版方法模式

經典模板方法定義:

父類定義了骨架(調用哪些方法及順序),某些特定方法由子類實現。

最大的好處:代碼復用,減少重復代碼。除了子類要實現的特定方法,其他方法及方法調用順序都在父類中預先寫好了。

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

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

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

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

鉤子方法:父類中是一個空方法,子類繼承了默認也是空的

注:為什么叫鉤子,子類可以通過這個鉤子(方法),控制父類,因為這個鉤子實際是父類的方法(空方法)!

Spring模板方法模式實質:

是模板方法模式和回調模式的結合,是Template Method不需要繼承的另一種實現方式。Spring幾乎所有的外接擴展都采用這種模式。

具體實現:

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

采用模板方法模式是為了以一種統一而集中的方式來處理資源的獲取和釋放,以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);

}

引入回調原因:

JdbcTemplate是抽象類,不能夠獨立使用,我們每次進行數據訪問的時候都要給出一個相應的子類實現,這樣肯定不方便,所以就引入了回調。

回調代碼

public interface StatementCallback{

? ? Object doWithStatement(Statement stmt);

}

利用回調方法重寫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沒有使用繼承?

因為這個類的方法太多,但是我們還是想用到JdbcTemplate已有的穩定的、公用的數據庫連接,那么我們怎么辦呢?

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

那我們就用回調對象吧。在這個回調對象中定義一個操縱JdbcTemplate中變量的方法,我們去實現這個方法,就把變化的東西集中到這里了。然后我們再傳入這個回調對象到JdbcTemplate,從而完成了調用。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容