天天看點

面試官:Spring 架構使用了哪些設計模式?

作者:Java網際網路技術棧

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

實作方式:

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

###了實質:由一個工廠類根據傳入的參數,動态決定應該建立哪一個産品類。

實作原理:

bean容器的啟動階段:

讀取bean的xml配置檔案,将bean元素分别轉換成一個 BeanDefinition 對象。

然後通過 BeanDefinitionRegistry 将這些bean注冊到beanFactory中,儲存在它的一個Con currentHashMap 中。

将 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。 [非常重要]

2.工廠方法

實作方式:

FactoryBean接口。

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

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

代碼示例:

面試官:Spring 架構使用了哪些設計模式?

說明:

我們看上面該bean,因為實作了 FactoryBean 接口,是以傳回的不是 SqlSessionFactoryBean 的執行個體,而是它的 SqlSessionFactoryBean.getObject() 的傳回值。

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對單例的實作: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 模式常用的地方是l istener 的實作。

具體實作:

事件機制的實作需要三個部分:事件源、事件、事件監聽器

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 ,進而完成了調用。

繼續閱讀