Java面試:談?wù)凷pring中使用的常用設(shè)計模式

1. 設(shè)計模式概述

設(shè)計模式(Design Patterns)是軟件開發(fā)人員在軟件開發(fā)過程中面臨的一般問題的解決方案。它表示面向?qū)ο筌浖_發(fā)過程中最好的計算機(jī)編程世間,Spring框架作為業(yè)內(nèi)比較優(yōu)秀的框架之一,大量應(yīng)用了不同的設(shè)計模式,也被大家認(rèn)可與熟知,我們在面試時經(jīng)常會被問及,下面針對Spring中的常用設(shè)計模式做一個匯總,希望能幫助到廣大學(xué)子。

2. Spring中的設(shè)計模式

2.1 工廠模式

工廠模式(Factory Pattern)是 Java 中最常用的設(shè)計模式之一。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。

在工廠模式中,我們在創(chuàng)建對象時不會對客戶端暴露創(chuàng)建邏輯,并且是通過使用一個共同的接口來指向新創(chuàng)建的對象。

Spring使用工廠模式可以通過 `BeanFactory` 或 `ApplicationContext` 創(chuàng)建 bean 對象。

兩者對比:
(1)BeanFactory:延遲注入(使用到某個 bean 的時候才會注入),相比于BeanFactory`來說會占用更少的內(nèi)存,程序啟動速度更快。

(2)ApplicationContext :容器啟動的時候,不管你用沒用到,一次性創(chuàng)建所有 bean 。BeanFactory 僅提供了最基本的依賴注入支持,ApplicationContext 擴(kuò)展了 BeanFactory ,除了有BeanFactory的功能之外還有額外更多功能,所以一般開發(fā)人員使用ApplicationContext會更多。

ApplicationContext的三個實現(xiàn)類:

①`ClassPathXmlApplication`:把上下文文件當(dāng)成類路徑資源。

② FileSystemXmlApplication:從文件系統(tǒng)中的 XML 文件載入上下文定義信息。

③ XmlWebApplicationContext`:從Web系統(tǒng)中的XML文件載入上下文定義信息。
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new FileSystemXmlApplicationContext(
                "application.xml");

        HelloApplicationContext obj = (HelloApplicationContext) context.getBean("helloApplicationContext");
        obj.getMsg();
    }
}

2.2 單例模式

單例模式(Singleton Pattern)是 Java 中最簡單的設(shè)計模式之一。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。

這種模式涉及到一個單一的類,該類負(fù)責(zé)創(chuàng)建自己的對象,同時確保只有單個對象被創(chuàng)建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。

使用單例模式的好處:

(1)對于頻繁使用的對象,可以省略創(chuàng)建對象所花費(fèi)的時間,這對于那些重量級對象而言,是非??捎^的一筆系統(tǒng)開銷;

(2)由于new操作的次數(shù)減少,因而對系統(tǒng)內(nèi)存的使用頻率也會降低,這將減輕GC壓力,縮短GC停頓時間。

Spring中bean的默認(rèn)作用域就是singleton(單例)的,除了singleton作用域,Spring中bean還有下面幾種作用域:

  • prototype : 每次請求都會創(chuàng)建一個新的 bean 實例。
  • request : 每一次HTTP請求都會產(chǎn)生一個新的bean,該bean僅在當(dāng)前HTTP request內(nèi)有效。
  • session : 每一次HTTP請求都會產(chǎn)生一個新的 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請求。但是,與 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));

            }
        }
}

2.3 代理模式

在代理模式(Proxy Pattern)中,一個類代表另一個類的功能。這種類型的設(shè)計模式屬于結(jié)構(gòu)型模式。

在代理模式中,我們創(chuàng)建具有現(xiàn)有對象的對象,以便向外界提供功能接口。

AOP(Aspect-Oriented Programming:面向切面編程)能夠?qū)⒛切┡c業(yè)務(wù)無關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任(例如事務(wù)處理、日志管理、權(quán)限控制等)封裝起來,便于減少系統(tǒng)的重復(fù)代碼,降低模塊間的耦合度,并有利于未來的可拓展性和可維護(hù)性。

Spring AOP就是基于動態(tài)代理的,如果要代理的對象實現(xiàn)了某個接口,那么Spring AOP會使用JDK Proxy,去創(chuàng)建代理對象,而對于沒有實現(xiàn)接口的對象,Spring AOP會使用Cglib,這時候Spring AOP會使用Cglib生成一個被代理對象的子類來作為代理,如下圖所示:
12.webp.jpg

當(dāng)然你也可以使用AspectJ,Spring AOP以及集成了AspectJ,AspectJ應(yīng)該算得上是Java生態(tài)系統(tǒng)中最完整的AOP框架了。

使用AOP之后我們可以把一些通用的功能抽象出來,在在需要用到的地方直接使用即可,這樣大大簡化了代碼量。我們需要增加新功能時也方便,這樣也提高了系統(tǒng)擴(kuò)展性。日志功能、事務(wù)管理等等場景都用到了 AOP 。

Spring AOP 和 AspectJ AOP 的區(qū)別:

Spring AOP 屬于運(yùn)行時增強(qiáng),而 AspectJ 是編譯時增強(qiáng)。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字節(jié)碼操作(Bytecode Manipulation)。

Spring AOP 已經(jīng)集成了 AspectJ ,AspectJ 應(yīng)該算的上是 Java 生態(tài)系統(tǒng)中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加強(qiáng)大,但是 Spring AOP 相對來說更簡單,功能更弱。

如果我們的切面比較少,那么兩者性能差異不大。但是,當(dāng)切面太多的話,最好選擇 AspectJ ,它比Spring AOP 快很多。

2.4 模板方法模式

模板方法模式是一種行為設(shè)計模式,它定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。 模板方法使得子類可以不改變一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟的實現(xiàn)方式。
13.webp.jpg
public abstract class Template {
    //這是我們的模板方法
    public final void TemplateMethod(){
        PrimitiveOperation1();  
        PrimitiveOperation2();
        PrimitiveOperation3();
    }

    protected void  PrimitiveOperation1(){
        //當(dāng)前類實現(xiàn)
    }

    //被子類實現(xiàn)的方法
    protected abstract void PrimitiveOperation2();
    protected abstract void PrimitiveOperation3();

}
public class TemplateImpl extends Template {

    @Override
    public void PrimitiveOperation2() {
        //當(dāng)前類實現(xiàn)
    }

    @Override
    public void PrimitiveOperation3() {
        //當(dāng)前類實現(xiàn)
    }
}

Spring 中 jdbcTemplate、hibernateTemplate以 Template 結(jié)尾的對數(shù)據(jù)庫操作的類,它們就使用到了模板模式。一般情況下,我們都是使用繼承的方式來實現(xiàn)模板模式,但是 Spring 并沒有使用這種方式,而是使用Callback 模式與模板方法模式配合,既達(dá)到了代碼復(fù)用的效果,同時增加了靈活性。

2.5 觀察者模式

觀察者模式是一種對象行為型模式。它表示的是一種對象與對象之間具有依賴關(guān)系,當(dāng)一個對象發(fā)生改變的時候,這個對象所依賴的對象也會做出反應(yīng)。Spring **事件驅(qū)動模型**就是觀察者模式很經(jīng)典的一個應(yīng)用。Spring 事件驅(qū)動模型非常有用,在很多場景都可以解耦我們的代碼。比如我們每次添加商品的時候都需要重新更新商品索引,這個時候就可以利用觀察者模式來解決這個問題。

Spring 事件驅(qū)動模型中的三種角色

(1)事件角色

ApplicationEvent (org.springframework.context包下)充當(dāng)事件的角色,這是一個抽象類,它繼承了java.util.EventObject并實現(xiàn)了 java.io.Serializable接口。

Spring 中默認(rèn)存在以下事件,他們都是對 ApplicationContextEvent 的實現(xiàn)(繼承自ApplicationContextEvent):

  • ContextStartedEventApplicationContext 啟動后觸發(fā)的事件;
  • ContextStoppedEventApplicationContext 停止后觸發(fā)的事件;
  • ContextRefreshedEventApplicationContext 初始化或刷新完成后觸發(fā)的事件;
  • ContextClosedEventApplicationContext 關(guān)閉后觸發(fā)的事件。
    15.png

(2)事件監(jiān)聽者角色

ApplicationListener 充當(dāng)了事件監(jiān)聽者角色,它是一個接口,里面只定義了一個 onApplicationEvent()方法來處理ApplicationEvent。ApplicationListener接口類源碼如下,可以看出接口定義看出接口中的事件只要實現(xiàn)了 ApplicationEvent就可以了。所以,在 Spring中我們只要實現(xiàn) ApplicationListener 接口實現(xiàn) onApplicationEvent() 方法即可完成監(jiān)聽事件

package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E var1);
}

(3)事件發(fā)布者角色

ApplicationEventPublisher 充當(dāng)了事件的發(fā)布者,它也是一個接口。

@FunctionalInterface
public interface ApplicationEventPublisher {
    default void publishEvent(ApplicationEvent event) {
        this.publishEvent((Object)event);
    }

    void publishEvent(Object var1);
}

ApplicationEventPublisher 接口的publishEvent()這個方法在AbstractApplicationContext類中被實現(xiàn),閱讀這個方法的實現(xiàn),你會發(fā)現(xiàn)實際上事件真正是通過ApplicationEventMulticaster來廣播出去的。

Spring 的事件流程總結(jié)

①定義一個事件: 實現(xiàn)一個繼承自 ApplicationEvent,并且寫相應(yīng)的構(gòu)造函數(shù);

②定義一個事件監(jiān)聽者:實現(xiàn) ApplicationListener 接口,重寫 onApplicationEvent() 方法;

③使用事件發(fā)布者發(fā)布消息: 可以通過 ApplicationEventPublisherpublishEvent() 方法發(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));
    }
}

當(dāng)調(diào)用 DemoPublisherpublish() 方法的時候,比如 demoPublisher.publish("你好") ,控制臺就會打印出:接收到的信息是:你好 。

2.6 適配器模式

適配器模式(Adapter Pattern) 將一個接口轉(zhuǎn)換成客戶希望的另一個接口,適配器模式使接口不兼容的那些類可以一起工作,其別名為包裝器(Wrapper)。

(1)spring AOP中的適配器模式

我們知道 Spring AOP 的實現(xiàn)是基于代理模式,但是 Spring AOP 的增強(qiáng)或通知(Advice)使用到了適配器模式,與之相關(guān)的接口是AdvisorAdapter 。Advice 常用的類型有:BeforeAdvice(目標(biāo)方法調(diào)用前,前置通知)、AfterAdvice(目標(biāo)方法調(diào)用后,后置通知)、AfterReturningAdvice(目標(biāo)方法執(zhí)行結(jié)束后,return之前)等等。每個類型Advice(通知)都有對應(yīng)的攔截器:MethodBeforeAdviceInterceptor、AfterReturningAdviceAdapterAfterReturningAdviceInterceptor。Spring預(yù)定義的通知要通過對應(yīng)的適配器,適配成 MethodInterceptor接口(方法攔截器)類型的對象(如:MethodBeforeAdviceInterceptor 負(fù)責(zé)適配 MethodBeforeAdvice)。

(2)spring MVC中的適配器模式

在Spring MVC中,DispatcherServlet 根據(jù)請求信息調(diào)用 HandlerMapping,解析請求對應(yīng)的 Handler。解析到對應(yīng)的 Handler(也就是我們平常說的 Controller 控制器)后,開始由HandlerAdapter 適配器處理。HandlerAdapter 作為期望接口,具體的適配器實現(xiàn)類用于對目標(biāo)類進(jìn)行適配,Controller 作為需要適配的類。

為什么要在 Spring MVC 中使用適配器模式?
Spring MVC 中的 Controller 種類眾多,不同類型的 Controller 通過不同的方法來對請求進(jìn)行處理。如果不利用適配器模式的話,DispatcherServlet 直接獲取對應(yīng)類型的 Controller,需要的自行來判斷,像下面這段代碼一樣:

if(mappedHandler.getHandler() instanceof MultiActionController){  
   ((MultiActionController)mappedHandler.getHandler()).xxx  
}else if(mappedHandler.getHandler() instanceof XXX){  
    ...  
}else if(...){  
   ...  
}  

假如我們再增加一個 Controller類型就要在上面代碼中再加入一行 判斷語句,這種形式就使得程序難以維護(hù),也違反了設(shè)計模式中的開閉原則 – 對擴(kuò)展開放,對修改關(guān)閉。

2.7 裝飾模式

裝飾者模式可以動態(tài)地給對象添加一些額外的屬性或行為。相比于使用繼承,裝飾者模式更加靈活。簡單點兒說就是當(dāng)我們需要修改原有的功能,但我們又不愿直接去修改原有的代碼時,設(shè)計一個Decorator套在原有代碼外面。其實在 JDK 中就有很多地方用到了裝飾者模式,比如 `InputStream`家族,`InputStream` 類下有 `FileInputStream` (讀取文件)、`BufferedInputStream` (增加緩存,使讀取文件速度大大提升)等子類都在不修改`InputStream` 代碼的情況下擴(kuò)展了它的功能。
17.png

裝飾者模式示意圖

Spring 中配置 DataSource 的時候,DataSource 可能是不同的數(shù)據(jù)庫和數(shù)據(jù)源。我們能否根據(jù)客戶的需求在少修改原有類的代碼下動態(tài)切換不同的數(shù)據(jù)源?這個時候就要用到裝飾者模式(這一點我自己還沒太理解具體原理)。Spring 中用到的包裝器模式在類名上含有 Wrapper或者 Decorator。這些類基本上都是動態(tài)地給一個對象添加一些額外的職責(zé)

3. 總結(jié)

Spring 框架中用到了哪些設(shè)計模式:

  • 工廠設(shè)計模式 : Spring使用工廠模式通過 BeanFactory、ApplicationContext 創(chuàng)建 bean 對象。
  • 代理設(shè)計模式 : Spring AOP 功能的實現(xiàn)。
  • 單例設(shè)計模式 : Spring 中的 Bean 默認(rèn)都是單例的。
  • 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 結(jié)尾的對數(shù)據(jù)庫操作的類,它們就使用到了模板模式。
  • 包裝器設(shè)計模式 : 我們的項目需要連接多個數(shù)據(jù)庫,而且不同的客戶在每次訪問中根據(jù)需要會去訪問不同的數(shù)據(jù)庫。這種模式讓我們可以根據(jù)客戶的需求能夠動態(tài)切換不同的數(shù)據(jù)源。
  • 觀察者模式: Spring 事件驅(qū)動模型就是觀察者模式很經(jīng)典的一個應(yīng)用。
  • 適配器模式 :Spring AOP 的增強(qiáng)或通知(Advice)使用到了適配器模式、spring MVC 中也是用到了適配器模式適配Controller
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容