解讀Spring框架中常用的設(shè)計模式
一、淺談控制反轉(zhuǎn)(IOC)與依賴注入(DI)
IOC(Inversion of Control)是Spring中一個非常重要的概念,它不是什么技術(shù),而是一種解耦的設(shè)計思想。
它主要的額目的是借助于第三方(Spring中的IOC容器)實現(xiàn)具有依賴關(guān)系的的對象之間的解耦(IOC容易管理對象,你只管使用即可),從而降低代碼之間的耦合度。
它不是一個模式,而是一種設(shè)計原則,但以下模式(但不限于)實現(xiàn)了IOC的設(shè)計原則。
補充:對控制反轉(zhuǎn)的理解:
舉個例子“對象A依賴了對象B,當對象A需要使用對象B的時候必須自己去創(chuàng)建。
但是當系統(tǒng)引入了IOC容器后,對象A和對象B之前就失去了直接的聯(lián)系。
這個時候,當對象A需要使用對象B的時候,我們可以指定IOC容器去創(chuàng)建一個對象B注入到對象A中”。
對象A獲得對象B的過程,由主動行為變成了被動行為,控制權(quán)實現(xiàn)了反轉(zhuǎn),這就是控制反轉(zhuǎn)名字的由來。
DI(Dependency Inject)依賴注入實現(xiàn)控制反轉(zhuǎn)的的一種實現(xiàn)方式,依賴注入就是將實例變量傳入到一個對象中去。
二、Spring框架中的設(shè)計模式
1)工廠設(shè)計模式(簡單工廠和工廠方法)
Spring使用工廠模式可以通過BeanFactory或ApplicationContext創(chuàng)建bean對象。
兩者對比:
BeanFactory:延遲注入(使用到某個 bean 的時候才會注入),相比于BeanFactory來說會占用更少的內(nèi)存,程序啟動速度更快。ApplicationContext:容器啟動的時候,不管你用沒用到,一次性創(chuàng)建所有 bean 。BeanFactory 僅提供了最基本的依賴注入支持,ApplicationContext 擴展了 BeanFactory ,除了有BeanFactory的功能還有額外更多功能,所以一般開發(fā)人員使用ApplicationContext會更多。
2)單例設(shè)計模式
Spring中bean的默認作用域就是singleton。
除了singleton作用域,Spring bean還有下面幾種作用域:
prototype:每次請求都會創(chuàng)建一個新的 bean 實例。request:每一次HTTP請求都會產(chǎn)生一個新的bean,該bean僅在當前HTTP request內(nèi)有效。session:每一次HTTP請求都會產(chǎn)生一個新的 bean,該bean僅在當前 HTTP session 內(nèi)有效。global-session:全局session作用域,僅僅在基于portlet的web應(yīng)用中才有意義,Spring5已經(jīng)沒有了。Portlet是能夠生成語義代碼(例如:HTML)片段的小型Java Web插件。它們基于portlet容器,可以像servlet一樣處理HTTP請求。但是,與 servlet 不同,每個 portlet 都有不同的會話。
Spring實現(xiàn)單例的方式:
xml格式:
<bean id="userService" class="top.snailclimb.UserService" scope="singleton"/>
注解:
@Scope(value = "singleton")
Spring通過ConcurrentHashMap實現(xiàn)單例注冊表的特殊方式實現(xiàn)單例模式。
Spring實現(xiàn)單例的核心代碼如下:
// 通過 ConcurrentHashMap(線程安全) 實現(xiàn)單例注冊表
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) {
// 檢查緩存中是否存在實例
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//...省略了很多代碼
try {
singletonObject = singletonFactory.getObject();
}
//...省略了很多代碼
// 如果實例對象在不存在,我們注冊到單例注冊表中。
addSingleton(beanName, singletonObject);
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
//將對象添加到單例注冊表
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
}
}
} 3)代理設(shè)計模式
Spring AOP就是基于動態(tài)代理的,如果要代理的對象,實現(xiàn)了某個接口,那么Spring AOP會使用JDK Proxy,去創(chuàng)建代理對象,而對于沒有實現(xiàn)接口的對象,就無法使用JDK Proxy去進行代理了,這時候Spring AOP會使用Cglib,這時候Spring AOP會使用Cglib生成一個被代理對象的子類來作為代理。
如下圖所示:

當然你也可以使用AspectJ,Spring AOP已經(jīng)繼承了AspectJ,AspectJ應(yīng)該算的上是java生態(tài)系統(tǒng)中最完整的AOP框架了。
Spring AOP和AspectJ AOP有什么區(qū)別?
Spring AOP屬于運行時增強,而AspectJ是編譯時增強。Spring AOP基于代理,而AspectJ基于字節(jié)碼操作。
Spring AOP已經(jīng)集成了AspectJ,AsectJ應(yīng)該算的上是Java生態(tài)系統(tǒng)中最完整的AOP框架了。
AspectJ相比于Spring AOP功能更加強大,但是Spring AOP相對來說更簡單,如果我們的切面比較少,那么兩者的性能差異不大。
但是當切面太多的話,最好選擇AspectJ,它比Spring AOP快很多。
4)模板方法設(shè)計模式
模板方法模式是一種行為設(shè)計模式,它定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。
模板方法使得子類可以在不改變一個算法的結(jié)構(gòu)即可重定義該算法的默寫特定步驟的實現(xiàn)方式。
例如:
public abstract class Template {
//這是我們的模板方法
public final void TemplateMethod(){
PrimitiveOperation1();
PrimitiveOperation2();
PrimitiveOperation3();
}
protected void PrimitiveOperation1(){
//當前類實現(xiàn)
}
//被子類實現(xiàn)的方法
protected abstract void PrimitiveOperation2();
protected abstract void PrimitiveOperation3();
}
public class TemplateImpl extends Template {
@Override
public void PrimitiveOperation2() {
//當前類實現(xiàn)
}
@Override
public void PrimitiveOperation3() {
//當前類實現(xiàn)
}
} Spring中jdbcTemplate、hibernateTemplate等以Template結(jié)尾的對數(shù)據(jù)庫操作的類,它們就使用到模板模式。
一般情況下,我們都是使用繼承的方式來實現(xiàn)模板模式,但是Spring并沒有使用這種方式,而是使用Callback模式與模板方法配合,既達到了代碼復(fù)用的效果,同時增加了靈活性。
5)觀察者設(shè)計模式
觀察者設(shè)計模式是一種對象行為模式。它表示的是一種對象與對象之間具有依賴關(guān)系,當一個對象發(fā)生改變時,這個對象鎖依賴的對象也會做出反應(yīng)。Spring事件驅(qū)動模型就是觀察者模式很經(jīng)典的應(yīng)用。
事件角色:ApplicationEvent(org.springframework.context包下)充當事件的角色,這是一個抽象類。
事件監(jiān)聽者角色:ApplicationListener充當了事件監(jiān)聽者的角色,它是一個接口,里面只定義了一個onApplicationEvent()方法來處理ApplicationEvent。
事件發(fā)布者角色:ApplicationEventPublisher充當了事件的發(fā)布者,它也是個接口。
Spring事件流程總結(jié):
- 定義一個事件: 實現(xiàn)一個繼承自 ApplicationEvent,并且寫相應(yīng)的構(gòu)造函數(shù);
- 定義一個事件監(jiān)聽者:實現(xiàn) ApplicationListener 接口,重寫 onApplicationEvent() 方法;
- 使用事件發(fā)布者發(fā)布消息: 可以通過 ApplicationEventPublisher 的 publishEvent() 方法發(fā)布消息。
例如:
// 定義一個事件,繼承自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;
}
// 定義一個事件監(jiān)聽者,實現(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è)計模式
適配器設(shè)計模式將一個接口轉(zhuǎn)換成客戶希望的另一個接口,適配器模式使得接口不兼容的那些類可以一起工作,其別名為包裝器。
在Spring MVC中,DispatcherServlet根據(jù)請求信息調(diào)用HandlerMapping,解析請求對應(yīng)的Handler,解析到對應(yīng)的Handler(也就是我們常說的Controller控制器)后,開始由HandlerAdapter適配器處理。
為什么要在Spring MVC中使用適配器模式?
Spring MVC中的Controller種類眾多不同類型的Controller通過不同的方法來對請求進行處理,有利于代碼的維護拓展。
7)裝飾者設(shè)計模式
裝飾者設(shè)計模式可以動態(tài)地給對象增加些額外的屬性或行為。
相比于使用繼承,裝飾者模式更加靈活。
Spring 中配置DataSource的時候,DataSource可能是不同的數(shù)據(jù)庫和數(shù)據(jù)源。
我們能否根據(jù)客戶的需求在少修改原有類的代碼下切換不同的數(shù)據(jù)源?
這個時候據(jù)需要用到裝飾者模式。
8)策略設(shè)計模式
Spring 框架的資源訪問接口就是基于策略設(shè)計模式實現(xiàn)的。該接口提供了更強的資源訪問能力,Spring框架本身大量使用了Resource接口來訪問底層資源。
Resource接口本身沒有提供訪問任何底層資源的實現(xiàn)邏輯,針對不同的額底層資源,Spring將會提供不同的Resource實現(xiàn)類,不同的實現(xiàn)類負責不同的資源訪問類型。
Spring 為 Resource 接口提供了如下實現(xiàn)類:
UrlResource:訪問網(wǎng)絡(luò)資源的實現(xiàn)類。ClassPathResource:訪問類加載路徑里資源的實現(xiàn)類。FileSystemResource:訪問文件系統(tǒng)里資源的實現(xiàn)類。ServletContextResource:訪問相對于 ServletContext 路徑里的資源的實現(xiàn)類.InputStreamResource:訪問輸入流資源的實現(xiàn)類。ByteArrayResource:訪問字節(jié)數(shù)組資源的實現(xiàn)類。
這些 Resource 實現(xiàn)類,針對不同的的底層資源,提供了相應(yīng)的資源訪問邏輯,并提供便捷的包裝,以利于客戶端程序的資源訪問。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
IDEA?Error:java:無效的源發(fā)行版:13的解決過程
之前用idea運行時,也會出現(xiàn)這種情況,后面通過網(wǎng)上的資料解決了這個問題,下面這篇文章主要給大家介紹了關(guān)于IDEA?Error:java:無效的源發(fā)行版:13的解決過程,需要的朋友可以參考下2023-01-01
詳解Intellij IDEA中.properties文件中文顯示亂碼問題的解決
這篇文章主要介紹了詳解Intellij IDEA中.properties文件中文顯示亂碼問題的解決,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-11-11
SpringMVC訪問controller報錯404的解決辦法(總結(jié)超詳細)
純注解配置SpringMVC程序,使用tomcat8.5.95版本啟動,能啟動成功并且訪問index.jsp頁面,但是訪問/save時出現(xiàn)404無法訪問,本文給大家介紹了SpringMVC訪問controller報錯404的解決辦法,文章總結(jié)的非常詳細,需要的朋友可以參考下2024-05-05

