javascript
谈谈Spring中都用到了那些设计模式
控制反轉(zhuǎn)(IoC)和依賴注入(DI)
IoC(Inversion of Control,控制翻轉(zhuǎn))?是Spring 中一個(gè)非常非常重要的概念,它不是什么技術(shù),而是一種解耦的設(shè)計(jì)思想。它的主要目的是借助于“第三方”(Spring 中的 IOC 容器) 實(shí)現(xiàn)具有依賴關(guān)系的對(duì)象之間的解耦(IOC容器管理對(duì)象,你只管使用即可),從而降低代碼之間的耦合度。IOC 是一個(gè)原則,而不是一個(gè)模式,以下模式(但不限于)實(shí)現(xiàn)了IoC原則。
Spring IOC 容器就像是一個(gè)工廠一樣,當(dāng)我們需要?jiǎng)?chuàng)建一個(gè)對(duì)象的時(shí)候,只需要配置好配置文件/注解即可,完全不用考慮對(duì)象是如何被創(chuàng)建出來(lái)的。?IOC 容器負(fù)責(zé)創(chuàng)建對(duì)象,將對(duì)象連接在一起,配置這些對(duì)象,并從創(chuàng)建中處理這些對(duì)象的整個(gè)生命周期,直到它們被完全銷毀。
在實(shí)際項(xiàng)目中一個(gè) Service 類如果有幾百甚至上千個(gè)類作為它的底層,我們需要實(shí)例化這個(gè) Service,你可能要每次都要搞清這個(gè) Service 所有底層類的構(gòu)造函數(shù),這可能會(huì)把人逼瘋。如果利用 IOC 的話,你只需要配置好,然后在需要的地方引用就行了,這大大增加了項(xiàng)目的可維護(hù)性且降低了開(kāi)發(fā)難度。
控制翻轉(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 的過(guò)程,由主動(dòng)行為變?yōu)榱吮粍?dòng)行為,控制權(quán)翻轉(zhuǎn),這就是控制反轉(zhuǎn)名字的由來(lái)。
DI(Dependecy Inject,依賴注入)是實(shí)現(xiàn)控制反轉(zhuǎn)的一種設(shè)計(jì)模式,依賴注入就是將實(shí)例變量傳入到一個(gè)對(duì)象中去。
工廠設(shè)計(jì)模式
Spring使用工廠模式可以通過(guò)?BeanFactory?或?ApplicationContext?創(chuàng)建 bean 對(duì)象。
兩者對(duì)比:
- BeanFactory?:延遲注入(使用到某個(gè) bean 的時(shí)候才會(huì)注入),相比于BeanFactory來(lái)說(shuō)會(huì)占用更少的內(nèi)存,程序啟動(dòng)速度更快。
- ApplicationContext?:容器啟動(dòng)的時(shí)候,不管你用沒(méi)用到,一次性創(chuàng)建所有 bean 。BeanFactory?僅提供了最基本的依賴注入支持,ApplicationContext?擴(kuò)展了?BeanFactory?,除了有BeanFactory的功能還有額外更多功能,所以一般開(kāi)發(fā)人員使用ApplicationContext會(huì)更多。
ApplicationContext的三個(gè)實(shí)現(xiàn)類:
Example:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext;public class App {public static void main(String[] args) {ApplicationContext context = new FileSystemXmlApplicationContext("C:/work/IOC Containers/springframework.applicationcontext/src/main/resources/bean-factory-config.xml");HelloApplicationContext obj = (HelloApplicationContext) context.getBean("helloApplicationContext");obj.getMsg();} }單例設(shè)計(jì)模式
在我們的系統(tǒng)中,有一些對(duì)象其實(shí)我們只需要一個(gè),比如說(shuō):線程池、緩存、對(duì)話框、注冊(cè)表、日志對(duì)象、充當(dāng)打印機(jī)、顯卡等設(shè)備驅(qū)動(dòng)程序的對(duì)象。事實(shí)上,這一類對(duì)象只能有一個(gè)實(shí)例,如果制造出多個(gè)實(shí)例就可能會(huì)導(dǎo)致一些問(wèn)題的產(chǎn)生,比如:程序的行為異常、資源使用過(guò)量、或者不一致性的結(jié)果。
使用單例模式的好處:
- 對(duì)于頻繁使用的對(duì)象,可以省略創(chuàng)建對(duì)象所花費(fèi)的時(shí)間,這對(duì)于那些重量級(jí)對(duì)象而言,是非常可觀的一筆系統(tǒng)開(kāi)銷;
- 由于 new 操作的次數(shù)減少,因而對(duì)系統(tǒng)內(nèi)存的使用頻率也會(huì)降低,這將減輕 GC 壓力,縮短 GC 停頓時(shí)間。
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)沒(méi)有了。Portlet是能夠生成語(yǔ)義代碼(例如:HTML)片段的小型Java Web插件。它們基于portlet容器,可以像servlet一樣處理HTTP請(qǐng)求。但是,與 servlet 不同,每個(gè) portlet 都有不同的會(huì)話
Spring 實(shí)現(xiàn)單例的方式:
- xml :
- 注解:@Scope(value = "singleton")
Spring 通過(guò)?ConcurrentHashMap?實(shí)現(xiàn)單例注冊(cè)表的特殊方式實(shí)現(xiàn)單例模式。Spring 實(shí)現(xiàn)單例的核心代碼如下
// 通過(guò) 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));}} }代理設(shè)計(jì)模式
代理模式在 AOP 中的應(yīng)用
AOP(Aspect-Oriented Programming:面向切面編程)能夠?qū)⒛切┡c業(yè)務(wù)無(wú)關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任(例如事務(wù)處理、日志管理、權(quán)限控制等)封裝起來(lái),便于減少系統(tǒng)的重復(fù)代碼,降低模塊間的耦合度,并有利于未來(lái)的可拓展性和可維護(hù)性。
Spring AOP 就是基于動(dòng)態(tài)代理的,如果要代理的對(duì)象,實(shí)現(xiàn)了某個(gè)接口,那么Spring AOP會(huì)使用JDK Proxy,去創(chuàng)建代理對(duì)象,而對(duì)于沒(méi)有實(shí)現(xiàn)接口的對(duì)象,就無(wú)法使用 JDK Proxy 去進(jìn)行代理了,這時(shí)候Spring AOP會(huì)使用Cglib?,這時(shí)候Spring AOP會(huì)使用?Cglib?生成一個(gè)被代理對(duì)象的子類來(lái)作為代理。
當(dāng)然你也可以使用 AspectJ ,Spring AOP 已經(jīng)集成了AspectJ ,AspectJ 應(yīng)該算的上是 Java 生態(tài)系統(tǒng)中最完整的 AOP 框架了。
使用 AOP 之后我們可以把一些通用功能抽象出來(lái),在需要用到的地方直接使用即可,這樣大大簡(jiǎn)化了代碼量。我們需要增加新功能時(shí)也方便,這樣也提高了系統(tǒng)擴(kuò)展性。日志功能、事務(wù)管理等等場(chǎng)景都用到了 AOP 。
Spring AOP 和 AspectJ AOP 有什么區(qū)別?
Spring AOP 屬于運(yùn)行時(shí)增強(qiáng),而 AspectJ 是編譯時(shí)增強(qiáng)。?Spring AOP 基于代理(Proxying),而 AspectJ 基于字節(jié)碼操作(Bytecode Manipulation)。
AspectJ 相比于 Spring AOP 功能更加強(qiáng)大,但是 Spring AOP 相對(duì)來(lái)說(shuō)更簡(jiǎn)單,
如果我們的切面比較少,那么兩者性能差異不大。但是,當(dāng)切面太多的話,最好選擇 AspectJ ,它比Spring AOP 快很多。
模板方法
模板方法模式是一種行為設(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 {@Overridepublic void PrimitiveOperation2() {//當(dāng)前類實(shí)現(xiàn)}@Overridepublic void PrimitiveOperation3() {//當(dāng)前類實(shí)現(xiàn)} }Spring 中?jdbcTemplate、hibernateTemplate?等以 Template 結(jié)尾的對(duì)數(shù)據(jù)庫(kù)操作的類,它們就使用到了模板模式。一般情況下,我們都是使用繼承的方式來(lái)實(shí)現(xiàn)模板模式,但是 Spring 并沒(méi)有使用這種方式,而是使用Callback 模式與模板方法模式配合,既達(dá)到了代碼復(fù)用的效果,同時(shí)增加了靈活性。
觀察者模式
觀察者模式是一種對(duì)象行為型模式。它表示的是一種對(duì)象與對(duì)象之間具有依賴關(guān)系,當(dāng)一個(gè)對(duì)象發(fā)生改變的時(shí)候,這個(gè)對(duì)象所依賴的對(duì)象也會(huì)做出反應(yīng)。Spring 事件驅(qū)動(dòng)模型就是觀察者模式很經(jīng)典的一個(gè)應(yīng)用。Spring 事件驅(qū)動(dòng)模型非常有用,在很多場(chǎng)景都可以解耦我們的代碼。比如我們每次添加商品的時(shí)候都需要重新更新商品索引,這個(gè)時(shí)候就可以利用觀察者模式來(lái)解決這個(gè)問(wèn)題。
Spring 事件驅(qū)動(dòng)模型中的三種角色
事件角色
ApplicationEvent?(org.springframework.context包下)充當(dāng)事件的角色,這是一個(gè)抽象類,它繼承了java.util.EventObject并實(shí)現(xiàn)了?java.io.Serializable接口。
Spring 中默認(rèn)存在以下事件,他們都是對(duì)?ApplicationContextEvent?的實(shí)現(xiàn)(繼承自ApplicationContextEvent):
- ContextStartedEvent:ApplicationContext?啟動(dòng)后觸發(fā)的事件;
- ContextStoppedEvent:ApplicationContext?停止后觸發(fā)的事件;
- ContextRefreshedEvent:ApplicationContext?初始化或刷新完成后觸發(fā)的事件;
- ContextClosedEvent:ApplicationContext?關(guān)閉后觸發(fā)的事件。
事件監(jiān)聽(tīng)者角色
ApplicationListener?充當(dāng)了事件監(jiān)聽(tīng)者角色,它是一個(gè)接口,里面只定義了一個(gè)?onApplicationEvent()方法來(lái)處理ApplicationEvent。ApplicationListener接口類源碼如下,可以看出接口定義看出接口中的事件只要實(shí)現(xiàn)了?ApplicationEvent就可以了。所以,在 Spring中我們只要實(shí)現(xiàn)?ApplicationListener?接口實(shí)現(xiàn)?onApplicationEvent()?方法即可完成監(jiān)聽(tīng)事件
package org.springframework.context; import java.util.EventListener; @FunctionalInterface public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E var1); }事件發(fā)布者角色
ApplicationEventPublisher?充當(dāng)了事件的發(fā)布者,它也是一個(gè)接口。
@FunctionalInterface public interface ApplicationEventPublisher {default void publishEvent(ApplicationEvent event) {this.publishEvent((Object)event);}void publishEvent(Object var1); }ApplicationEventPublisher?接口的publishEvent()這個(gè)方法在AbstractApplicationContext類中被實(shí)現(xiàn),閱讀這個(gè)方法的實(shí)現(xiàn),你會(huì)發(fā)現(xiàn)實(shí)際上事件真正是通過(guò)ApplicationEventMulticaster來(lái)廣播出去的。具體內(nèi)容過(guò)多,就不在這里分析了,后面可能會(huì)單獨(dú)寫(xiě)一篇文章提到。
Spring 的事件流程總結(jié)
Example:
// 定義一個(gè)事件,繼承自ApplicationEvent并且寫(xiě)相應(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)聽(tīng)者,實(shí)現(xiàn)ApplicationListener接口,重寫(xiě) onApplicationEvent() 方法; @Component public class DemoListener implements ApplicationListener<DemoEvent>{//使用onApplicationEvent接收消息@Overridepublic void onApplicationEvent(DemoEvent event) {String msg = event.getMessage();System.out.println("接收到的信息是:"+msg);}} // 發(fā)布事件,可以通過(guò)ApplicationEventPublisher 的 publishEvent() 方法發(fā)布消息。 @Component public class DemoPublisher {@AutowiredApplicationContext applicationContext;public void publish(String message){//發(fā)布事件applicationContext.publishEvent(new DemoEvent(this, message));} }當(dāng)調(diào)用?DemoPublisher?的?publish()?方法的時(shí)候,比如?demoPublisher.publish("你好")?,控制臺(tái)就會(huì)打印出:接收到的信息是:你好?。
適配器模式
適配器模式(Adapter Pattern) 將一個(gè)接口轉(zhuǎn)換成客戶希望的另一個(gè)接口,適配器模式使接口不兼容的那些類可以一起工作,其別名為包裝器(Wrapper)。
spring MVC中的適配器模式
在Spring MVC中,DispatcherServlet?根據(jù)請(qǐng)求信息調(diào)用?HandlerMapping,解析請(qǐng)求對(duì)應(yīng)的?Handler。解析到對(duì)應(yīng)的?Handler(也就是我們平常說(shuō)的?Controller?控制器)后,開(kāi)始由HandlerAdapter?適配器處理。HandlerAdapter?作為期望接口,具體的適配器實(shí)現(xiàn)類用于對(duì)目標(biāo)類進(jìn)行適配,Controller?作為需要適配的類。
為什么要在 Spring MVC 中使用適配器模式??Spring MVC 中的?Controller?種類眾多,不同類型的?Controller?通過(guò)不同的方法來(lái)對(duì)請(qǐng)求進(jìn)行處理。如果不利用適配器模式的話,DispatcherServlet?直接獲取對(duì)應(yīng)類型的?Controller,需要的自行來(lái)判斷,像下面這段代碼一樣:
if(mappedHandler.getHandler() instanceof MultiActionController){ ((MultiActionController)mappedHandler.getHandler()).xxx }else if(mappedHandler.getHandler() instanceof XXX){ ... }else if(...){ ... }假如我們?cè)僭黾右粋€(gè)?Controller類型就要在上面代碼中再加入一行 判斷語(yǔ)句,這種形式就使得程序難以維護(hù),也違反了設(shè)計(jì)模式中的開(kāi)閉原則 – 對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。
裝飾者模式
裝飾者模式可以動(dòng)態(tài)地給對(duì)象添加一些額外的屬性或行為。相比于使用繼承,裝飾者模式更加靈活。簡(jiǎn)單點(diǎn)兒說(shuō)就是當(dāng)我們需要修改原有的功能,但我們又不愿直接去修改原有的代碼時(shí),設(shè)計(jì)一個(gè)Decorator套在原有代碼外面。其實(shí)在 JDK 中就有很多地方用到了裝飾者模式,比如?InputStream家族,InputStream?類下有?FileInputStream?(讀取文件)、BufferedInputStream?(增加緩存,使讀取文件速度大大提升)等子類都在不修改InputStream?代碼的情況下擴(kuò)展了它的功能。
Spring 中配置 DataSource 的時(shí)候,DataSource 可能是不同的數(shù)據(jù)庫(kù)和數(shù)據(jù)源。我們能否根據(jù)客戶的需求在少修改原有類的代碼下動(dòng)態(tài)切換不同的數(shù)據(jù)源?這個(gè)時(shí)候就要用到裝飾者模式。Spring 中用到的包裝器模式在類名上含有?Wrapper或者?Decorator。這些類基本上都是動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)
總結(jié)
Spring 框架中用到了哪些設(shè)計(jì)模式?
?
?
-
- 工廠設(shè)計(jì)模式?: Spring使用工廠模式通過(guò)?BeanFactory、ApplicationContext?創(chuàng)建 bean 對(duì)象。
- 代理設(shè)計(jì)模式?: Spring AOP 功能的實(shí)現(xiàn)。
- 單例設(shè)計(jì)模式?: Spring 中的 Bean 默認(rèn)都是單例的。
- 模板方法模式?: Spring 中?jdbcTemplate、hibernateTemplate?等以 Template 結(jié)尾的對(duì)數(shù)據(jù)庫(kù)操作的類,它們就使用到了模板模式。
- 包裝器設(shè)計(jì)模式?: 我們的項(xiàng)目需要連接多個(gè)數(shù)據(jù)庫(kù),而且不同的客戶在每次訪問(wèn)中根據(jù)需要會(huì)去訪問(wèn)不同的數(shù)據(jù)庫(kù)。這種模式讓我們可以根據(jù)客戶的需求能夠動(dòng)態(tài)切換不同的數(shù)據(jù)源。
- 觀察者模式:?Spring 事件驅(qū)動(dòng)模型就是觀察者模式很經(jīng)典的一個(gè)應(yīng)用。
- 適配器模式?:Spring AOP 的增強(qiáng)或通知(Advice)使用到了適配器模式、spring MVC 中也是用到了適配器模式適配Controller。
總結(jié)
以上是生活随笔為你收集整理的谈谈Spring中都用到了那些设计模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 分布式事务六种解决方案
- 下一篇: 工作中用到的设计模式?