Spring中使用的設計模式,你都能說全嗎?[上]

這是 Spring 面試題系列的第二篇,本文的主題:Spring 中涉及到的設計模式,如何在面試中回答的儘量全面、準確、有深度。java

本篇只回答一個問題:面試

Spring 中使用了哪些設計模式?分別都是如何實現的?

首先做一個概述,從總體上講,SpringFramework 中使用了 11 種設計模式:設計模式

  • 單例模式+原型模式
  • 工廠模式
  • 代理模式
  • 策略模式
  • 模板方法模式
  • 觀察者模式
  • 適配器模式
  • 裝飾者模式
  • 外觀模式
  • 委派模式(不屬於GoF23)

固然,若是隻是這麼回答,面試官會怎麼想:你這。。。不會是在背答案吧!隨便揪出一個來細問,可能就翻皮水了 ~ ~ 因此咱不光要知道用了啥,並且還要知道如何用的,在哪裏用的,這樣才能用本身真正的技術儲備征服面試官。緩存

下面咱詳細的介紹 11 種設計模式的設計場景和原理。markdown

因爲 11 種設計模式所有展開篇幅過長,會分紅兩篇專欄介紹。框架

單例模式+原型模式

SpringFramework 的 IOC 容器中放了不少不少的 Bean ,默認狀況下,Bean 的做用域( Scope )是 singleton ,就是單實例的;若是顯式聲明做用域爲 prototype ,那 Bean 的做用域就會變爲每次獲取都是一個新的,即原型 Bean 。這個知識點本應該在 SpringFramework 最基礎階段就應該知道的,咱也很少囉嗦。關鍵的問題是,若是我定義的 Bean 聲明瞭 prototype ,那 SpringFramework 確定知道我要搞原型 Bean ;但我定義 Bean 的時候沒聲明 Scope ,它怎麼就給我默認成單實例的呢?ide

下面咱先從最熟悉的 Bean 註冊場景出發。(原型模式相對簡單,內容已穿插在解釋單例模式之中)post

Bean的註冊

xml方式註冊Bean
<bean class="com.example.demo.bean.Person" scope="singleton"/>
複製代碼

這是最最簡單的 Bean 的註冊了,這裏面若是顯式聲明瞭 scope="singleton" 後,IDE 會報黃(警告):學習

很明顯它提示了默認值就是 singleton ,咱不必再主動聲明瞭。這個提示是 IDEA 智能識別的,咱很差找出處,不過咱能夠點進去這個 scope ,看一眼 xsd 中的註釋:ui

<xsd:attribute name="scope" type="xsd:string">
    <xsd:annotation>
        <xsd:documentation><![CDATA[
	The scope of this bean: typically "singleton" (one shared instance,
	which will be returned by all calls to getBean with the given id), ......
複製代碼

很明顯文檔註釋的第一句就說了:一般它是 singleton

註解驅動註冊Bean

註解驅動的方式,都是使用一個 @Scope 註解來聲明做用域的:

@Scope
@Component
public class Person {
    
}
複製代碼

點開 @Scope 註解看源碼,能夠發現只標註 @Scope 註解,不聲明做用域,默認值是空字符串(不是 singleton ):

public @interface Scope {

	@AliasFor("scopeName")
	String value() default "";
複製代碼

這個地方可能就會有疑惑了,它聲明的是空字符串,但是在 xml 中我配置的是 singleton 啊,這怎麼不同呢?莫慌,下面咱來解析這其中的緣由。

默認的做用域標註

對 SpringFramework 有一些深刻了解的小夥伴應該能意識到我接下來要說什麼了:BeanDefinition 。全部 Bean 的定義信息都被 SpringFramework 封裝到 BeanDefinition 中了,而做用域的定義就在 BeanDefinition 的抽象實現類 AbstractBeanDefinition 中:

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable {

	public static final String SCOPE_DEFAULT = "";
複製代碼

這裏面一上來就聲明瞭默認的做用域就是空字符串,不是 singleton

這個時候可能有的小夥伴就更迷惑了,這裏面它都聲明瞭單實例 Bean 是空字符串,那 singleton 還有個卵用呢?判斷單實例 Bean 不是應該看做用域是否爲 singleton 嗎?

哎,說到這裏了,那咱就看看 BeanDefinition 中是如何獲取做用域的:

public String getScope() {
    return this.scope;
}
複製代碼

獲取做用域的方式很是簡單,這個沒啥看的。可是!!!注意繼續往下翻,緊跟着下面有一個方法叫 isSingleton

/** * Return whether this a <b>Singleton</b>, with a single shared instance * returned from all calls. * @see #SCOPE_SINGLETON */
@Override
public boolean isSingleton() {
    return SCOPE_SINGLETON.equals(this.scope) || SCOPE_DEFAULT.equals(this.scope);
}
複製代碼

看這裏面的判斷,它分爲了兩個部分:**是不是 singleton ,或者是否爲空串!**那這就說得過去了吧,人家設置成空串,意義上也是單實例 Bean 。

Bean的實例化

上面咱也知道了,默認狀況下 Bean 是單實例的,那 SpringFramework 在 IOC 容器初始化的時候,是如何知道這些 Bean 是不是單實例的,同時初始化並保存的呢?下面咱跟進底層初始化邏輯中看一眼。

本部分只粗略介紹 Bean 的初始化流程,詳細的解析能夠參照個人 SpringBoot 源碼小冊 14 章詳細學習。

AbstractBeanFactory 中,getBean 方法會調用到 doGetBean ,這個方法的篇幅很是長,這裏只剪出框框:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    final String beanName = transformedBeanName(name);
    Object bean;

    // Eagerly check singleton cache for manually registered singletons.
    // 最開始先檢查單實例對象緩存池中是否已經有對應的bean了
    Object sharedInstance = getSingleton(beanName);
    // ......
    else {
        // ......
        try {
            final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
            // 檢查 ......

            // Create bean instance.
            if (mbd.isSingleton()) {
                // 單實例Bean
                sharedInstance = getSingleton(beanName, () -> {
                    try {
                        return createBean(beanName, mbd, args);
                    } // catch ......
                });
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }

            else if (mbd.isPrototype()) {
                // 原型Bean
                // It's a prototype -> create a new instance.
                Object prototypeInstance = null;
                try {
                    beforePrototypeCreation(beanName);
                    // 一定建立全新的對象
                    prototypeInstance = createBean(beanName, mbd, args);
                }
                finally {
                    afterPrototypeCreation(beanName);
                }
                bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            }

            else {
                // 自定義scope
                String scopeName = mbd.getScope();
                final Scope scope = this.scopes.get(scopeName);
                // ......
            }
        } // catch ......
    }
    // ......
    return (T) bean;
}
複製代碼

仔細閱讀這個框框流程,一上來它就要先檢查單實例對象的緩存池中是否有現成的 Bean 了,沒有再往下走。那咱說建立流程的話仍是往下走,在 else 塊的 try 部分,它會取出當前 Bean 的 BeanDefinition 來判斷做用域:若是是 singleton 單實例的,就執行 getSingleton 方法建立單實例對象(底層走 lambda 表達式中的 createBean 方法);若是是 prototype 原型 Bean ,就執行原型 Bean 的建立流程(直接建立);若是這些都不是,那就能夠認定爲自定義 scope ,使用特殊的初始化流程。

因此由此看下來,單實例 Bean 的建立核心方法仍是 getSingleton 了,那咱就進到這裏面看一眼:(仍是隻有大框框的流程哈)

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // 檢查
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            } // catch finally ......
            if (newSingleton) {
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}
複製代碼

注意看這裏面的設計:它會先去單實例對象緩存池中找是否已經有對應的 bean 了,若是沒有,就執行建立 bean 的動做。在建立完成後,它還會將 bean 放入緩存池中,這樣之後再取的時候就不會再二次建立了。

小結

因此這裏面的核心邏輯也就能夠總結出來了:

SpringFramework 中實現的單例模式,是在 BeanDefinition 中默認配置 singleton 的做用域,在 IOC 容器初始化階段,將 Bean 建立好,放入單實例對象緩存池( singletonObjects )中,實現 Bean 的單實例。

工廠模式

提起工廠模式,在 SpringFramework 中最容易聯想到的就是 FactoryBean 了吧!但其實 SpringFramework 中不止這一個是工廠,還有不少種其餘的,下面咱來列舉。

FactoryBean

FactoryBean 自己是一個接口,它自己就是一個建立對象的工廠。若是一個類實現了 FactoryBean 接口,則它自己將再也不是一個普通的 bean 對象,不會在實際的業務邏輯中起做用,而是由建立的對象來起做用。

FactoryBean 接口有三個方法:

public interface FactoryBean<T> {
    // 返回建立的對象
    @Nullable
    T getObject() throws Exception;

    // 返回建立的對象的類型(即泛型類型)
    @Nullable
    Class<?> getObjectType();

    // 建立的對象是單實例Bean仍是原型Bean,默認單實例
    default boolean isSingleton() {
        return true;
    }
}
複製代碼

靜態工廠

這種方式很像咱在最開始學習簡單工廠模式中看到的核心工廠,比方說下面這樣:

public class CalculatorFactory {
    // 簡單工廠
    public static Calculator getCalculator(String operationType) {
        switch (operationType) {
            case "+": 
                return new AddCalculator();
            case "-":
                return new SubtractCalculator();
            default: 
                return null;
        }
    }
    
    // 靜態工廠
    public static Calculator getAddCalculator() {
        return new AddCalculator();
    }
}
複製代碼

在 SpringFramework 中使用靜態工廠,就沒有參數這個說法了,只須要聲明工廠類和方法便可(因此上面的工廠中我額外寫了一個方法):

<bean id="addCalculator" class="com.example.demo.bean.CalculatorFactory" factory-method="getAddCalculator"/>
複製代碼

這樣註冊後獲得的 bean ,類型是 AddCalculator

實例工廠

實例工廠的使用方式與靜態工廠很像,只不過靜態工廠自己不會註冊到 IOC 容器中,但實例工廠會一塊兒註冊到 IOC 容器

調整上面的代碼,就能夠實現實例工廠的 Bean 註冊:

public class CalculatorFactory {
    // 工廠方法
    public Calculator getAddCalculator() {
        return new AddCalculator();
    }
}
複製代碼
<bean id="calculatorFactory" class="com.example.demo.bean.CalculatorFactory"/>
<bean id="addCalculator" factory-bean="calculatorFactory" factory-method="getAddCalculator"/>
複製代碼

ObjectFactory

這個類型可能有些小夥伴會感受有些陌生,因此我放到了最後寫。

@FunctionalInterface
public interface ObjectFactory<T> {
	T getObject() throws BeansException;
}
複製代碼

結構比 FactoryBean 簡單,固然也能夠簡單地將其理解爲 FactoryBean ,但又與其不一樣。ObjectFactory 通常狀況下會做爲一個 Bean 注入到其餘 Bean 中,在須要用對應的 bean 時主動調用 ObjectFactorygetObject 方法獲取真正須要的 BeanFactoryBeangetObject 方法是在 SpringFramework 初始化 Bean 時調用的,因此由此也能夠知道二者的調用時機也不同

其實這個接口在上面看 Bean 的實例化過程中就遇到過了,在 getSingleton 的兩參數方法中,第二個參數就是 ObjectFactory 類型,由它就能夠調用 createBean 建立出單實例對象。

小結

SpringFramework 中的工廠模式包括內置的 FactoryBeanObjectFactory ,以及自定義聲明的靜態工廠、實例工廠。

代理模式

咱都知道,SpringFramework 的兩大核心:IOC 、AOP ,AOP 就是體現了代理模式的使用。不過若是隻說 AOP 體現了代理模式,那這個也太沒水準了,咱要回答的更多更全,才能讓面試官意識到你真的有研究過,你真的很懂!

AOP的底層實現

SpringFramework 中對 Bean 進行 AOP 加強生成代理對象,核心是一個 BeanPostProcessorAnnotationAwareAspectJAutoProxyCreator ,這個名字很長,不過很好記:

  • Annotation:註解式,
  • Aware:注入式
  • AspectJ:基於 AspectJ 的 AOP
  • AutoProxy:自動代理
  • Creator:建立器

這樣一拆分,是否是感受容易理解多了呢?

它的核心做用方法是父類 AbstractAutoProxyCreatorpostProcessAfterInitialization 方法,底層會調用 wrapIfNessary 方法建立代理對象:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // 建立AOP代理對象
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}
複製代碼

至於再往底下,這個就麻煩大了,這裏簡單總結一下吧,詳盡的代理對象建立能夠參考 SpringBoot 源碼小冊的 19 章學習。

被 AOP 加強的 Bean ,會在初始化階段(此時對象已經建立)被 AnnotationAwareAspectJAutoProxyCreator 處理,整合該 Bean 可能被覆蓋到的切面,最終根據 Bean 是否有接口實現,採用 jdk 動態代理或者 Cglib 動態代理構建生成代理對象。

代理對象的建立

上面的總結中提到了最終的動態代理建立,這裏能夠帶小夥伴看一眼最底層大家熟悉的建立代理對象的源碼。

jdk 動態代理的建立,在 JdkDynamicAopProxy 中,有一個 getProxy 方法,底層實現以下:

public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
    }
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    // jdk原生方法
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
複製代碼

看最後一句,是否是忽然熟悉了!這個地方就能夠在面試中拿出來吹一吹,這樣面試官可能就真的認爲你把這部分原理都搞明白了哦(狗頭)。

Cglib 動態代理的建立,在 CglibAopProxycreateProxyClassAndInstance 方法中有建立代理對象的實現:

protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
    enhancer.setInterceptDuringConstruction(false);
    enhancer.setCallbacks(callbacks);
    return (this.constructorArgs != null && this.constructorArgTypes != null ?
            enhancer.create(this.constructorArgTypes, this.constructorArgs) :
            enhancer.create());
}
複製代碼

看到這裏的 Enhancer#create() 方法,是否是又是熟悉的一幕呢?因此由此也知道,框架也只是在咱學過的基礎上層層包裝加強罷了,最底層的仍是不變的。

小結

SpringFramework 中的代理模式體如今 AOP 上,它經過後置處理器,整合切面(加強器 Advice )的邏輯,將原有的 Bean (目標對象 Target )使用 jdk 或者 Cglib 動態代理加強爲代理 Bean 。

策略模式

提及 SpringFramework 中實現的策略模式,其實剛剛就提到了:AOP 生成代理對象時,會根據原始 Bean 是否有接口實現,決定使用 jdk 動態代理仍是 Cglib 動態代理,這就是典型的策略模式體現。

直接說原理吧,在 DefaultAopProxyFactory 中有策略模式的體現:

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        // 策略判斷
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}
複製代碼

中間的這個判斷當前要代理的目標對象,類型是不是一個接口,或者目標對象是否爲一個代理類。若是是兩者之一,則能夠直接使用 jdk 的動態代理便可,不然纔會使用 Cglib 代理。

【篇幅限制,剩餘 6 個設計模式的體現會放在下篇介紹 ~ 小夥伴們記得關注點贊呀,有源碼學習須要的能夠看我小冊 ~ 奧利給】

相關文章
相關標籤/搜索