解讀Spring框架中常用的設(shè)計(jì)模式
一、淺談控制反轉(zhuǎn)(IOC)與依賴注入(DI)
IOC(Inversion of Control)是Spring中一個(gè)非常重要的概念,它不是什么技術(shù),而是一種解耦的設(shè)計(jì)思想。
它主要的額目的是借助于第三方(Spring中的IOC容器)實(shí)現(xiàn)具有依賴關(guān)系的的對(duì)象之間的解耦(IOC容易管理對(duì)象,你只管使用即可),從而降低代碼之間的耦合度。
它不是一個(gè)模式,而是一種設(shè)計(jì)原則,但以下模式(但不限于)實(shí)現(xiàn)了IOC的設(shè)計(jì)原則。
補(bǔ)充:對(duì)控制反轉(zhuǎn)的理解:
舉個(gè)例子“對(duì)象A依賴了對(duì)象B,當(dāng)對(duì)象A需要使用對(duì)象B的時(shí)候必須自己去創(chuàng)建。
但是當(dāng)系統(tǒng)引入了IOC容器后,對(duì)象A和對(duì)象B之前就失去了直接的聯(lián)系。
這個(gè)時(shí)候,當(dāng)對(duì)象A需要使用對(duì)象B的時(shí)候,我們可以指定IOC容器去創(chuàng)建一個(gè)對(duì)象B注入到對(duì)象A中”。
對(duì)象A獲得對(duì)象B的過程,由主動(dòng)行為變成了被動(dòng)行為,控制權(quán)實(shí)現(xiàn)了反轉(zhuǎn),這就是控制反轉(zhuǎn)名字的由來。
DI(Dependency Inject)依賴注入實(shí)現(xiàn)控制反轉(zhuǎn)的的一種實(shí)現(xiàn)方式,依賴注入就是將實(shí)例變量傳入到一個(gè)對(duì)象中去。
二、Spring框架中的設(shè)計(jì)模式
1)工廠設(shè)計(jì)模式(簡(jiǎn)單工廠和工廠方法)
Spring使用工廠模式可以通過BeanFactory或ApplicationContext創(chuàng)建bean對(duì)象。
兩者對(duì)比:
BeanFactory
:延遲注入(使用到某個(gè) bean 的時(shí)候才會(huì)注入),相比于BeanFactory來說會(huì)占用更少的內(nèi)存,程序啟動(dòng)速度更快。ApplicationContext
:容器啟動(dòng)的時(shí)候,不管你用沒用到,一次性創(chuàng)建所有 bean 。BeanFactory 僅提供了最基本的依賴注入支持,ApplicationContext 擴(kuò)展了 BeanFactory ,除了有BeanFactory的功能還有額外更多功能,所以一般開發(fā)人員使用ApplicationContext會(huì)更多。
2)單例設(shè)計(jì)模式
Spring中bean的默認(rèn)作用域就是singleton。
除了singleton作用域,Spring bean還有下面幾種作用域:
prototype
:每次請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的 bean 實(shí)例。request
:每一次HTTP請(qǐng)求都會(huì)產(chǎn)生一個(gè)新的bean,該bean僅在當(dāng)前HTTP request內(nèi)有效。session
:每一次HTTP請(qǐng)求都會(huì)產(chǎn)生一個(gè)新的 bean,該bean僅在當(dāng)前 HTTP session 內(nèi)有效。global-session
:全局session作用域,僅僅在基于portlet的web應(yīng)用中才有意義,Spring5已經(jīng)沒有了。Portlet是能夠生成語義代碼(例如:HTML)片段的小型Java Web插件。它們基于portlet容器,可以像servlet一樣處理HTTP請(qǐng)求。但是,與 servlet 不同,每個(gè) portlet 都有不同的會(huì)話。
Spring實(shí)現(xiàn)單例的方式:
xml格式:
<bean id="userService" class="top.snailclimb.UserService" scope="singleton"/>
注解:
@Scope(value = "singleton")
Spring通過ConcurrentHashMap實(shí)現(xiàn)單例注冊(cè)表的特殊方式實(shí)現(xiàn)單例模式。
Spring實(shí)現(xiàn)單例的核心代碼如下:
// 通過 ConcurrentHashMap(線程安全) 實(shí)現(xiàn)單例注冊(cè)表 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64); public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "'beanName' must not be null"); synchronized (this.singletonObjects) { // 檢查緩存中是否存在實(shí)例 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { //...省略了很多代碼 try { singletonObject = singletonFactory.getObject(); } //...省略了很多代碼 // 如果實(shí)例對(duì)象在不存在,我們注冊(cè)到單例注冊(cè)表中。 addSingleton(beanName, singletonObject); } return (singletonObject != NULL_OBJECT ? singletonObject : null); } } //將對(duì)象添加到單例注冊(cè)表 protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT)); } } }
3)代理設(shè)計(jì)模式
Spring AOP就是基于動(dòng)態(tài)代理的,如果要代理的對(duì)象,實(shí)現(xiàn)了某個(gè)接口,那么Spring AOP會(huì)使用JDK Proxy,去創(chuàng)建代理對(duì)象,而對(duì)于沒有實(shí)現(xiàn)接口的對(duì)象,就無法使用JDK Proxy去進(jìn)行代理了,這時(shí)候Spring AOP會(huì)使用Cglib,這時(shí)候Spring AOP會(huì)使用Cglib生成一個(gè)被代理對(duì)象的子類來作為代理。
如下圖所示:
當(dāng)然你也可以使用AspectJ,Spring AOP已經(jīng)繼承了AspectJ,AspectJ應(yīng)該算的上是java生態(tài)系統(tǒng)中最完整的AOP框架了。
Spring AOP和AspectJ AOP有什么區(qū)別?
Spring AOP屬于運(yùn)行時(shí)增強(qiáng),而AspectJ是編譯時(shí)增強(qiáng)。Spring AOP基于代理,而AspectJ基于字節(jié)碼操作。
Spring AOP已經(jīng)集成了AspectJ,AsectJ應(yīng)該算的上是Java生態(tài)系統(tǒng)中最完整的AOP框架了。
AspectJ相比于Spring AOP功能更加強(qiáng)大,但是Spring AOP相對(duì)來說更簡(jiǎn)單,如果我們的切面比較少,那么兩者的性能差異不大。
但是當(dāng)切面太多的話,最好選擇AspectJ,它比Spring AOP快很多。
4)模板方法設(shè)計(jì)模式
模板方法模式是一種行為設(shè)計(jì)模式,它定義一個(gè)操作中的算法的骨架,而將一些步驟延遲到子類中。
模板方法使得子類可以在不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的默寫特定步驟的實(shí)現(xiàn)方式。
例如:
public abstract class Template { //這是我們的模板方法 public final void TemplateMethod(){ PrimitiveOperation1(); PrimitiveOperation2(); PrimitiveOperation3(); } protected void PrimitiveOperation1(){ //當(dāng)前類實(shí)現(xiàn) } //被子類實(shí)現(xiàn)的方法 protected abstract void PrimitiveOperation2(); protected abstract void PrimitiveOperation3(); } public class TemplateImpl extends Template { @Override public void PrimitiveOperation2() { //當(dāng)前類實(shí)現(xiàn) } @Override public void PrimitiveOperation3() { //當(dāng)前類實(shí)現(xiàn) } }
Spring中jdbcTemplate、hibernateTemplate等以Template結(jié)尾的對(duì)數(shù)據(jù)庫(kù)操作的類,它們就使用到模板模式。
一般情況下,我們都是使用繼承的方式來實(shí)現(xiàn)模板模式,但是Spring并沒有使用這種方式,而是使用Callback模式與模板方法配合,既達(dá)到了代碼復(fù)用的效果,同時(shí)增加了靈活性。
5)觀察者設(shè)計(jì)模式
觀察者設(shè)計(jì)模式是一種對(duì)象行為模式。它表示的是一種對(duì)象與對(duì)象之間具有依賴關(guān)系,當(dāng)一個(gè)對(duì)象發(fā)生改變時(shí),這個(gè)對(duì)象鎖依賴的對(duì)象也會(huì)做出反應(yīng)。Spring事件驅(qū)動(dòng)模型就是觀察者模式很經(jīng)典的應(yīng)用。
事件角色:ApplicationEvent(org.springframework.context包下)充當(dāng)事件的角色,這是一個(gè)抽象類。
事件監(jiān)聽者角色:ApplicationListener充當(dāng)了事件監(jiān)聽者的角色,它是一個(gè)接口,里面只定義了一個(gè)onApplicationEvent()方法來處理ApplicationEvent。
事件發(fā)布者角色:ApplicationEventPublisher充當(dāng)了事件的發(fā)布者,它也是個(gè)接口。
Spring事件流程總結(jié):
- 定義一個(gè)事件: 實(shí)現(xiàn)一個(gè)繼承自 ApplicationEvent,并且寫相應(yīng)的構(gòu)造函數(shù);
- 定義一個(gè)事件監(jiān)聽者:實(shí)現(xiàn) ApplicationListener 接口,重寫 onApplicationEvent() 方法;
- 使用事件發(fā)布者發(fā)布消息: 可以通過 ApplicationEventPublisher 的 publishEvent() 方法發(fā)布消息。
例如:
// 定義一個(gè)事件,繼承自ApplicationEvent并且寫相應(yīng)的構(gòu)造函數(shù) public class DemoEvent extends ApplicationEvent{ private static final long serialVersionUID = 1L; private String message; public DemoEvent(Object source,String message){ super(source); this.message = message; } public String getMessage() { return message; } // 定義一個(gè)事件監(jiān)聽者,實(shí)現(xiàn)ApplicationListener接口,重寫 onApplicationEvent() 方法; @Component public class DemoListener implements ApplicationListener<DemoEvent>{ //使用onApplicationEvent接收消息 @Override public void onApplicationEvent(DemoEvent event) { String msg = event.getMessage(); System.out.println("接收到的信息是:"+msg); } } // 發(fā)布事件,可以通過ApplicationEventPublisher 的 publishEvent() 方法發(fā)布消息。 @Component public class DemoPublisher { @Autowired ApplicationContext applicationContext; public void publish(String message){ //發(fā)布事件 applicationContext.publishEvent(new DemoEvent(this, message)); } }
6)適配器設(shè)計(jì)模式
適配器設(shè)計(jì)模式將一個(gè)接口轉(zhuǎn)換成客戶希望的另一個(gè)接口,適配器模式使得接口不兼容的那些類可以一起工作,其別名為包裝器。
在Spring MVC中,DispatcherServlet根據(jù)請(qǐng)求信息調(diào)用HandlerMapping,解析請(qǐng)求對(duì)應(yīng)的Handler,解析到對(duì)應(yīng)的Handler(也就是我們常說的Controller控制器)后,開始由HandlerAdapter適配器處理。
為什么要在Spring MVC中使用適配器模式?
Spring MVC中的Controller種類眾多不同類型的Controller通過不同的方法來對(duì)請(qǐng)求進(jìn)行處理,有利于代碼的維護(hù)拓展。
7)裝飾者設(shè)計(jì)模式
裝飾者設(shè)計(jì)模式可以動(dòng)態(tài)地給對(duì)象增加些額外的屬性或行為。
相比于使用繼承,裝飾者模式更加靈活。
Spring 中配置DataSource的時(shí)候,DataSource可能是不同的數(shù)據(jù)庫(kù)和數(shù)據(jù)源。
我們能否根據(jù)客戶的需求在少修改原有類的代碼下切換不同的數(shù)據(jù)源?
這個(gè)時(shí)候據(jù)需要用到裝飾者模式。
8)策略設(shè)計(jì)模式
Spring 框架的資源訪問接口就是基于策略設(shè)計(jì)模式實(shí)現(xiàn)的。該接口提供了更強(qiáng)的資源訪問能力,Spring框架本身大量使用了Resource接口來訪問底層資源。
Resource接口本身沒有提供訪問任何底層資源的實(shí)現(xiàn)邏輯,針對(duì)不同的額底層資源,Spring將會(huì)提供不同的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
:訪問相對(duì)于 ServletContext 路徑里的資源的實(shí)現(xiàn)類.InputStreamResource
:訪問輸入流資源的實(shí)現(xiàn)類。ByteArrayResource
:訪問字節(jié)數(shù)組資源的實(shí)現(xiàn)類。
這些 Resource 實(shí)現(xiàn)類,針對(duì)不同的的底層資源,提供了相應(yīng)的資源訪問邏輯,并提供便捷的包裝,以利于客戶端程序的資源訪問。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java獲取文件的類型和擴(kuò)展名的實(shí)現(xiàn)方法
這篇文章主要介紹了Java獲取文件的類型和擴(kuò)展名的實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2017-02-02IDEA?Error:java:無效的源發(fā)行版:13的解決過程
之前用idea運(yùn)行時(shí),也會(huì)出現(xiàn)這種情況,后面通過網(wǎng)上的資料解決了這個(gè)問題,下面這篇文章主要給大家介紹了關(guān)于IDEA?Error:java:無效的源發(fā)行版:13的解決過程,需要的朋友可以參考下2023-01-01Java如何處理json字符串value多余雙引號(hào)
這篇文章主要介紹了Java如何處理json字符串value多余雙引號(hào),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03詳解Intellij IDEA中.properties文件中文顯示亂碼問題的解決
這篇文章主要介紹了詳解Intellij IDEA中.properties文件中文顯示亂碼問題的解決,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11SpringMVC訪問controller報(bào)錯(cuò)404的解決辦法(總結(jié)超詳細(xì))
純注解配置SpringMVC程序,使用tomcat8.5.95版本啟動(dòng),能啟動(dòng)成功并且訪問index.jsp頁面,但是訪問/save時(shí)出現(xiàn)404無法訪問,本文給大家介紹了SpringMVC訪問controller報(bào)錯(cuò)404的解決辦法,文章總結(jié)的非常詳細(xì),需要的朋友可以參考下2024-05-05