這是 Spring 面試題系列的第二篇,本文的主題:Spring 中涉及到的設計模式,如何在面試中回答的儘量全面、準確、有深度。java
本篇只回答一個問題:面試
首先做一個概述,從總體上講,SpringFramework 中使用了 11 種設計模式:設計模式
固然,若是隻是這麼回答,面試官會怎麼想:你這。。。不會是在背答案吧!隨便揪出一個來細問,可能就翻皮水了 ~ ~ 因此咱不光要知道用了啥,並且還要知道如何用的,在哪裏用的,這樣才能用本身真正的技術儲備征服面試官。緩存
下面咱詳細的介紹 11 種設計模式的設計場景和原理。markdown
因爲 11 種設計模式所有展開篇幅過長,會分紅兩篇專欄介紹。框架
SpringFramework 的 IOC 容器中放了不少不少的 Bean ,默認狀況下,Bean 的做用域( Scope )是 singleton
,就是單實例的;若是顯式聲明做用域爲 prototype
,那 Bean 的做用域就會變爲每次獲取都是一個新的,即原型 Bean 。這個知識點本應該在 SpringFramework 最基礎階段就應該知道的,咱也很少囉嗦。關鍵的問題是,若是我定義的 Bean 聲明瞭 prototype ,那 SpringFramework 確定知道我要搞原型 Bean ;但我定義 Bean 的時候沒聲明 Scope ,它怎麼就給我默認成單實例的呢?ide
下面咱先從最熟悉的 Bean 註冊場景出發。(原型模式相對簡單,內容已穿插在解釋單例模式之中)post
<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
的。
註解驅動的方式,都是使用一個 @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 是單實例的,那 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
接口,則它自己將再也不是一個普通的 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"/> 複製代碼
這個類型可能有些小夥伴會感受有些陌生,因此我放到了最後寫。
@FunctionalInterface public interface ObjectFactory<T> { T getObject() throws BeansException; } 複製代碼
結構比 FactoryBean
簡單,固然也能夠簡單地將其理解爲 FactoryBean
,但又與其不一樣。ObjectFactory
通常狀況下會做爲一個 Bean 注入到其餘 Bean 中,在須要用對應的 bean 時主動調用 ObjectFactory
的 getObject
方法獲取真正須要的 Bean ;FactoryBean
的 getObject
方法是在 SpringFramework 初始化 Bean 時調用的,因此由此也能夠知道二者的調用時機也不同。
其實這個接口在上面看 Bean 的實例化過程中就遇到過了,在 getSingleton
的兩參數方法中,第二個參數就是 ObjectFactory
類型,由它就能夠調用 createBean
建立出單實例對象。
SpringFramework 中的工廠模式包括內置的 FactoryBean
、ObjectFactory
,以及自定義聲明的靜態工廠、實例工廠。
咱都知道,SpringFramework 的兩大核心:IOC 、AOP ,AOP 就是體現了代理模式的使用。不過若是隻說 AOP 體現了代理模式,那這個也太沒水準了,咱要回答的更多更全,才能讓面試官意識到你真的有研究過,你真的很懂!
SpringFramework 中對 Bean 進行 AOP 加強生成代理對象,核心是一個 BeanPostProcessor
:AnnotationAwareAspectJAutoProxyCreator
,這個名字很長,不過很好記:
這樣一拆分,是否是感受容易理解多了呢?
它的核心做用方法是父類 AbstractAutoProxyCreator
的 postProcessAfterInitialization
方法,底層會調用 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 動態代理的建立,在 CglibAopProxy
的 createProxyClassAndInstance
方法中有建立代理對象的實現:
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 個設計模式的體現會放在下篇介紹 ~ 小夥伴們記得關注點贊呀,有源碼學習須要的能夠看我小冊 ~ 奧利給】