前段時間,一位讀者面阿里被問到了一個經典問題:說一說 Spring 框架中用到了哪些設計模式?答的不是很好。不得不說,這個面試題出現的頻次還真不低,不少同學都遇到過。 所以今天這篇文章我們就來好好梳理一下這個知識點,希望對大家有所幫助。
代理模式
所謂代理,是指它與被代理對象實現了相同的接口,客戶端必須通過代理才能與被代理的目標類進行交互,而代理一般在交互的過程中(交互前后),進行某些特定的處理,比如在調用這個方法前做前置處理,調用這個方法后做后置處理。
代理又分為靜態代理和動態代理兩種方式,Spring 的 AOP 采用的是動態代理的方式
Spring 通過動態代理對類進行方法級別的切面增強,動態生成目標對象的代理類,并在代理類的方法中設置攔截器,通過執行攔截器中的邏輯增強了代理方法的功能,從而實現 AOP。
關于動態代理可以看這篇文章,寫的很詳細:動態代理總結,你要知道的都在這里,無廢話!
策略模式
我們前面講到,Spring AOP 是通過動態代理來實現的。
具體到代碼實現,Spring 支持兩種動態代理實現方式,一種是 JDK 提供的動態代理實現方式,另一種是 Cglib 提供的動態代理實現方式。
Spring 會在運行時動態地選擇不同的動態代理實現方式。這個應用場景實際上就是策略模式的典型應用場景。
我們只需要定義一個策略接口,讓不同的策略類都實現這一個策略接口。對應到 Spring 源碼,AopProxy 是策略接口,JdkDynamicAopProxy、CglibAopProxy 是兩個實現了 AopProxy 接口的策略類。
其中,AopProxy接口的定義如下所示:
在策略模式中,策略的創建一般通過工廠方法來實現。對應到Spring源碼,AopProxyFactory 是一個工廠類接口,DefaultAopProxyFactory 是一個默認的工廠類,用來創建 AopProxy 對象。
源碼如下所示:
策略模式的典型應用場景,一般是通過環境變量、狀態值、計算結果等動態地決定使用哪個策略。
對應到 Spring 源碼中,我們可以參看剛剛給出的 DefaultAopProxyFactory 類中的 createAopProxy() 函數的代碼實現。
其中,第10行代碼是動態選擇哪種策略的判斷條件。
裝飾器模式
我們知道,緩存一般都是配合數據庫來使用的。如果寫緩存成功,但數據庫事務回滾了,那緩存中就會有臟數據。
為了解決這個問題,我們需要將緩存的寫操作和數據庫的寫操作,放到同一個事務中,要么都成功,要么都失敗。
實現這樣一個功能,Spring 使用到了裝飾器模式。
TransactionAwareCacheDecorator 增加了對事務的支持,在事務提交、回滾的時候分別對Cache的數據進行處理。
TransactionAwareCacheDecorator 實現 Cache 接口,并且將所有的操作都委托給 targetCache 來實現,對其中的寫操作添加了事務功能。這是典型的裝飾器模式的應用場景和代碼實現。
單例模式
單例模式是指一個類在整個系統運行過程中,只允許產生一個實例
在Spring中,Bean 可以被定義為兩種模式:Prototype(多例)和Singleton(單例),Spring Bean默認是單例模式。
那Spring是如何實現單例模式的呢?
答案是通過單例注冊表的方式,具體來說就是使用了HashMap。簡化代碼如下:
public?class?DefaultSingletonBeanRegistry?{ ???? ????//使用了線程安全容器ConcurrentHashMap,保存各種單實例對象 ????private?final?Map?singletonObjects?=?new?ConcurrentHashMap; ????protected?Object?getSingleton(String?beanName)?{ ????//先到HashMap中拿Object ????Object?singletonObject?=?singletonObjects.get(beanName); ???? ????//如果沒拿到通過反射創建一個對象實例,并添加到HashMap中 ????if?(singletonObject?==?null)?{ ??????singletonObjects.put(beanName, ???????????????????????????Class.forName(beanName).newInstance()); ???} ??? ???//返回對象實例 ???return?singletonObjects.get(beanName); ??} }
上面的代碼邏輯比較清晰,先到? HashMap去拿單實例對象,沒拿到就創建一個添加到 HashMap。
簡單工廠模式
有這樣一個場景:
當 A 對象需要調用 B 對象的方法時,我們需要在 A 中 new 一個 B 的實例,它的缺點是一旦需求發生變化,比如需要使用C類來代替B時,就要改寫A類的方法。
假如應用中有 100 個類以的方式耦合了 B,那改起來就費勁了。
使用簡單工廠模式:
簡單工廠模式又叫靜態工廠方法,其實質是由一個工廠類根據傳入的參數,動態決定應該創建哪一個產品類。
其中 Spring 中的 BeanFactory 就是簡單工廠模式的體現,BeanFactory 是 Spring IOC 容器中的一個核心接口,它的定義如下:
我們可以通過它的具體實現類(比如ClassPathXmlApplicationContext)來獲取Bean:
BeanFactory?bf?=?new?ClassPathXmlApplicationContext("spring.xml"); FlyFish?flyFishBean?=?(FlyFish)?bf.getBean("flyfishBean");
從上面代碼可以看到,使用者不需要自己來new對象,而是通過工廠類的方法getBean來獲取對象實例,這是典型的簡單工廠模式,只不過Spring是用反射機制來創建Bean的。
工廠方法模式
在簡單工廠中,由工廠類進行所有的邏輯判斷、實例創建;如果不想在工廠類中進行判斷,可以為不同的產品提供不同的工廠,不同的工廠生產不同的產品,每一個工廠都只對應一個相應的對象,這就是工廠方法模式。
Spring中的FactoryBean就是這種思想的體現,FactoryBean可以理解為工廠Bean,先來看看它的定義:
我們定義一個類FlyFishFactoryBean來實現FactoryBean接口,主要是在getObject方法里new一個FlyFish對象。這樣我們通過getBean(id) 獲得的是該工廠所產生的FlyFish的實例,而不是FlyFishFactoryBean本身的實例,像下面這樣:
BeanFactory?bf?=?new?ClassPathXmlApplicationContext("spring.xml"); FlyFish?flyFishBean?=?(FlyFish)?bf.getBean("flyfishBean");
觀察者模式
Spring中實現的觀察者模式包含三部分:Event事件(相當于消息)、Listener監聽者(相當于觀察者)、Publisher發送者(相當于被觀察者)
我們通過一個例子來看下Spring提供的觀察者模式是怎么使用的
// Event事件 public?class?DemoEvent?extends?ApplicationEvent?{ ??private?String?message; ??public?DemoEvent(Object?source,?String?message)?{ ????super(source); ??} ??public?String?getMessage()?{ ????return?this.message; ??} } //?Listener監聽者 @Component public?class?DemoListener?implements?ApplicationListener?{ ??@Override ??public?void?onApplicationEvent(DemoEvent?demoEvent)?{ ????String?message?=?demoEvent.getMessage(); ????System.out.println(message); ??} } //?Publisher發送者 @Component public?class?DemoPublisher?{ ??@Autowired ??private?ApplicationContext?applicationContext; ??public?void?publishEvent(DemoEvent?demoEvent)?{ ????this.applicationContext.publishEvent(demoEvent); ??} }
從代碼中,我們可以看出,主要包含三部分工作:
定義一個繼承ApplicationEvent的事件(DemoEvent);
定義一個實現了ApplicationListener的監聽器(DemoListener);
定義一個發送者(DemoPublisher),發送者調用ApplicationContext來發送事件消息。
在Spring的實現中,觀察者注冊到了哪里呢?又是如何注冊的呢?
Spring把觀察者注冊到了ApplicationContext對象中。
實際上,具體到源碼來說,ApplicationContext只是一個接口,具體的代碼實現包含在它的實現類AbstractApplicationContext中。我把跟觀察者模式相關的代碼,如下。你只需要關注它是如何發送事件和注冊監聽者就好。
從上面的代碼中,我們發現,真正的消息發送,實際上是通過ApplicationEventMulticaster這個類來完成的。
下面這個類的源碼我只摘抄了最關鍵的一部分,也就是multicastEvent()這個消息發送函數,它通過線程池,支持異步非阻塞、同步阻塞這兩種類型的觀察者模式。
借助Spring提供的觀察者模式的骨架代碼,如果我們要在Spring下實現某個事件的發送和監聽,只需要做很少的工作,定義事件、定義監聽器、往ApplicationContext中發送事件就可以了,剩下的工作都由Spring框架來完成。
實際上,這也體現了Spring框架的擴展性,也就是在不需要修改任何代碼的情況下,擴展新的事件和監聽。
模板模式
我們經常在面試中被問到的一個問題:
請你說下Spring Bean的創建過程包含哪些主要的步驟。
這其中就涉及模板模式。它也體現了Spring的擴展性。利用模板模式,Spring能讓用戶定制Bean的創建過程。
下面是Spring Bean的整個生命周期,一張圖,清晰明了:
如果你仔細看過源碼會發現,實際上,這里的模板模式的實現,并不是標準的抽象類的實現方式,而是有點類似? Callback回調的實現方式,也就是將要執行的函數封裝成對象(比如,初始化方法封裝成 InitializingBean 對象),傳遞給模板(BeanFactory)來執行。
觀察者模式和模板模式,這兩種模式能夠幫助我們創建擴展點,讓框架的使用者在不修改源碼的情況下,基于擴展點定制化框架功能。
適配器模式
在Spring MVC中,定義一個Controller最常用的方式是,通過@Controller注解來標記某個類是Controller類,通過@RequesMapping注解來標記函數對應的URL
不過,我們還可以通過讓類實現Controller接口或者Servlet接口,來定義一個Controller。
針對這三種定義方式,我寫了三段示例代碼,如下所示:
//?方法一:通過@Controller、@RequestMapping來定義 @Controller public?class?DemoController?{ ????@RequestMapping("/FlyFish") ????public?ModelAndView?getEmployeeName()?{ ????????ModelAndView?model?=?new?ModelAndView("FlyFish");???????? ????????model.addObject("message",?"FlyFish");??????? ????????return?model;? ????}?? } //?方法二:實現Controller接口?+ xml配置文件:配置DemoController與URL的對應關系 public?class?DemoController?implements?Controller?{ ????@Override ????public?ModelAndView?handleRequest(HttpServletRequest?req,?HttpServletResponse?resp)?throws?Exception?{ ????????ModelAndView?model?=?new?ModelAndView("FlyFish"); ????????model.addObject("message",?"FlyFish"); ????????return?model; ????} } //?方法三:實現Servlet接口?+ xml配置文件:配置DemoController類與URL的對應關系 public?class?DemoServlet?extends?HttpServlet?{ ??@Override ??protected?void?doGet(HttpServletRequest?req,?HttpServletResponse?resp)?throws?ServletException,?IOException?{ ????this.doPost(req,?resp); ??} ?? ??@Override ??protected?void?doPost(HttpServletRequest?req,?HttpServletResponse?resp)?throws?ServletException,?IOException?{ ????resp.getWriter().write("Hello?World."); ??} }
在應用啟動的時候,Spring容器會加載這些Controller類,并且解析出URL對應的處理函數,封裝成Handler對象,存儲到HandlerMapping對象中。當有請求到來的時候,DispatcherServlet從HanderMapping中,查找請求URL對應的Handler,然后調用執行Handler對應的函數代碼,最后將執行結果返回給客戶端。
但是,不同方式定義的Controller,其函數的定義(函數名、入參、返回值等)是不統一的。
DispatcherServlet調用的是service()方法,DispatcherServlet需要根據不同類型的Controller,調用不同的函數。
Spring利用適配器模式,我們將不同方式定義的Controller類中的函數,適配為統一的函數定義。
我們再具體看下Spring的代碼實現。
Spring定義了統一的接口HandlerAdapter,并且對每種Controller定義了對應的適配器類。
這些適配器類包括:AnnotationMethodHandlerAdapter、SimpleControllerHandlerAdapter、SimpleServletHandlerAdapter等。
在DispatcherServlet類中,我們就不需要區分對待不同的Controller對象了,統一調用HandlerAdapter的handle()函數就可以了。
編輯:黃飛
評論
查看更多