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。[非常重要]
2. 工廠方法
實現方式:
FactoryBean 接口。
實現原理:
實現了 FactoryBean 接口的 bean 是一類叫做 factory 的 bean。其特點是,spring 會在使用 getBean() 調用獲得該 bean 時,會自動調用該 bean 的 getObject() 方法,所以返回的不是 factory 這個 bean,而是這個 bean.getOjbect() 方法的返回值。
例子:
典型的例子有 spring 與 mybatis 的結合。
代碼示例:
說明:
我們看上面該 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 中的單例模式完成了後半句話,即提供了全局的訪問點 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,從而完成了調用。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/pdsPIsypGkVRoo5TJ9YIvg