Spring 常見的一些面試題整理

搜雲庫技術團隊發表於2018-03-21

本人收集了一些在大家在面試時被經常問及的關於Spring的主要問題,這些問題有可能在你下次面試時就會被問到。對於本文中未提及的Spring其他模組,我會單獨分享面試的問題和答案。 歡迎大家向我推薦你在面試過程中遇到關於Spring的問題。我會把大家推薦的問題新增到下面的Spring常用面試題清單中供大家參考。

以下問題,用於複習面試也是可以的。

  • IOC和DI是什麼?
  • Spring IOC 的理解,其初始化過程?
  • BeanFactory 和 FactoryBean的區別?
  • BeanFactory和ApplicationContext的區別?
  • ApplicationContext 上下文的生命週期?
  • Spring Bean 的生命週期?
  • Spring AOP的實現原理?
  • Spring 是如何管理事務的,事務管理機制?
  • Spring 的不同事務傳播行為有哪些,幹什麼用的?
  • Spring 中用到了那些設計模式?
  • Spring MVC 的工作原理?
  • Spring如何解決迴圈依賴?
  • Spring 如何保證 Controller 併發的安全?

IOC和DI是什麼?

IoC (Inversion of Control) 控制反轉什麼是控制反轉?

控制反轉是就是應用本身不負責依賴物件的建立和維護,依賴物件的建立及維護是由外部容器負責的,這樣控制權就有應用轉移到了外部容器,控制權的轉移就是控制反轉。

DI (Dependency Injection) 依賴注入什麼是依賴注入?

依賴注入是指:在程式執行期間,由外部容器動態地將依賴物件注入到元件中 如:一般,通過建構函式注入或者setter注入。

Spring IOC 的理解,其初始化過程?

blog.csdn.net/u010723709/…

IOC 容器的初始化分為三個過程實現:

  1. Resource資源定位,這個Resouce指的是BeanDefinition的資源定位,這個過程就是容器找資料的過程。
  2. BeanDefinition的載入過程,這個載入過程是把使用者定義好的Bean表示成Ioc容器內部的資料結構,而這個容器內部的資料結構就是BeanDefition。
  3. 向IOC容器註冊這些BeanDefinition的過程,這個過程就是將前面的BeanDefition儲存到HashMap中的過程。

Resource定位

我們一般使用外部資源來描述Bean物件,所以IOC容器第一步就是需要定位Resource外部資源。Resource的定位其實就是BeanDefinition的資源定位,它是由ResourceLoader通過統一的Resource介面來完成的,這個Resource對各種形式的BeanDefinition的使用都提供了統一介面。

載入

第二個過程就是BeanDefinition的載入,BeanDefinitionReader讀取,解析Resource定位的資源,也就是將使用者定義好的Bean表示成IOC容器的內部資料結構也就是BeanDefinition,在IOC容器內部維護著一個BeanDefinition Map的資料結構,通過這樣的資料結構,IOC容器能夠對Bean進行更好的管理。

在配置檔案中每一個都對應著一個BeanDefinition物件。

註冊

第三個過程則是註冊,即向IOC容器註冊這些BeanDefinition,這個過程是通過BeanDefinitionRegistery介面來實現的。

在IOC容器內部其實是將第二個過程解析得到的BeanDefinition注入到一個HashMap容器中,IOC容器就是通過這個HashMap來維護這些BeanDefinition的。

上面提到的過程一般是不包括Bean的依賴注入的實現,Bean的載入和依賴注入是兩個獨立的過程,依賴注入是發生在應用第一次呼叫getBean向容器所要Bean時。

當然我們可以通過設定預處理,即對某個Bean設定lazyinit屬性,那麼這個Bean的依賴注入就會在容器初始化的時候完成。

經過這 (Resource定位,載入,註冊)三個步驟,IOC容器的初始化過程就已經完成了。

BeanFactory 和 FactoryBean的區別?

  • BeanFactory是個Factory,也就是IOC容器或物件工廠,在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)來進行管理的,提供了例項化物件和拿物件的功能。
  • FactoryBean是個Bean,這個Bean不是簡單的Bean,而是一個能生產或者修飾物件生成的工廠Bean,它的實現與設計模式中的工廠模式和修飾器模式類似。

BeanFactory和ApplicationContext的區別?

BeanFactory

是Spring裡面最低層的介面,提供了最簡單的容器的功能,只提供了例項化物件和拿物件的功能。

兩者裝載bean的區別

  • BeanFactory:在啟動的時候不會去例項化Bean,中有從容器中拿Bean的時候才會去例項化;
  • ApplicationContext:在啟動的時候就把所有的Bean全部例項化了。它還可以為Bean配置lazy-init=true來讓Bean延遲例項化;

我們該用BeanFactory還是ApplicationContent

BeanFactory 延遲例項化的優點:

應用啟動的時候佔用資源很少,對資源要求較高的應用,比較有優勢;

缺點:速度會相對來說慢一些。而且有可能會出現空指標異常的錯誤,而且通過bean工廠建立的bean生命週期會簡單一些

ApplicationContext 不延遲例項化的優點:

  • 所有的Bean在啟動的時候都載入,系統執行的速度快;
  • 在啟動的時候所有的Bean都載入了,我們就能在系統啟動的時候,儘早的發現系統中的配置問題
  • 建議web應用,在啟動的時候就把所有的Bean都載入了。

缺點:把費時的操作放到系統啟動中完成,所有的物件都可以預載入,缺點就是消耗伺服器的記憶體

ApplicationContext其他特點

除了提供BeanFactory所支援的所有功能外,ApplicationContext還有額外的功能

  • 預設初始化所有的Singleton,也可以通過配置取消預初始化。
  • 繼承MessageSource,因此支援國際化。
  • 資源訪問,比如訪問URL和檔案(ResourceLoader);
  • 事件機制,(有繼承關係)上下文 ,使得每一個上下文都專注於一個特定的層次,比如應用的web層;
  • 同時載入多個配置檔案。
  • 訊息傳送、響應機制(ApplicationEventPublisher);
  • 以宣告式方式啟動並建立Spring容器。

由於ApplicationContext會預先初始化所有的Singleton Bean,於是在系統建立前期會有較大的系統開銷,但一旦ApplicationContext初始化完成,程式後面獲取Singleton Bean例項時候將有較好的效能。

也可以為bean設定lazy-init屬性為true,即Spring容器將不會預先初始化該bean。

spring的AOP(常用的是攔截器)

一般攔截器都是實現HandlerInterceptor,其中有三個方法preHandle、postHandle、afterCompletion

  1. preHandle:執行controller之前執行
  2. postHandle:執行完controller,return modelAndView之前執行,主要操作modelAndView的值
  3. afterCompletion:controller返回後執行

spring載入多個上下文

不同專案使用不同分模組策略,spring配置檔案分為

  • applicationContext.xml(主檔案,包括JDBC配置,hibernate.cfg.xml,與所有的Service與DAO基類)
  • applicationContext-cache.xml(cache策略,包括hibernate的配置)
  • applicationContext-jmx.xml(JMX,除錯hibernate的cache效能)
  • applicationContext-security.xml(acegi安全)
  • applicationContext-transaction.xml(事務)
  • moduleName-Service.xml
  • moduleName-dao.xml

ApplicationContext 上下文的生命週期?

PS:可以借鑑Servlet的生命週期,例項化、初始init、接收請求service、銷燬destroy;

Spring上下文中的Bean也類似,【Spring上下文的生命週期】

  1. 例項化一個Bean,也就是我們通常說的new;
  2. 按照Spring上下文對例項化的Bean進行配置,也就是IOC注入
  3. 如果這個Bean實現了BeanNameAware介面,會呼叫它實現的setBeanName(String beanId)方法,此處傳遞的是Spring配置檔案中Bean的ID;
  4. 如果這個Bean實現了BeanFactoryAware介面,會呼叫它實現的setBeanFactory(),傳遞的是Spring工廠本身(可以用這個方法獲取到其他Bean);
  5. 如果這個Bean實現了ApplicationContextAware介面,會呼叫setApplicationContext(ApplicationContext)方法,傳入Spring上下文,該方式同樣可以實現步驟4,但比4更好,以為ApplicationContext是BeanFactory的子介面,有更多的實現方法;
  6. 如果這個Bean關聯了BeanPostProcessor介面,將會呼叫postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor經常被用作是Bean內容的更改,並且由於這個是在Bean初始化結束時呼叫After方法,也可用於記憶體或快取技術;
  7. 如果這個Bean在Spring配置檔案中配置了init-method屬性會自動呼叫其配置的初始化方法;
  8. 如果這個Bean關聯了BeanPostProcessor介面,將會呼叫postAfterInitialization(Object obj, String s)方法;

注意:以上工作完成以後就可以用這個Bean了,那這個Bean是一個single的,所以一般情況下我們呼叫同一個ID的Bean會是在內容地址相同的例項

  1. 當Bean不再需要時,會經過清理階段,如果Bean實現了DisposableBean介面,會呼叫其實現的destroy方法
  2. 最後,如果這個Bean的Spring配置中配置了destroy-method屬性,會自動呼叫其配置的銷燬方法

以上10步驟可以作為面試或者筆試的模板,另外這裡描述的是應用Spring上下文Bean的生命週期,如果應用Spring的工廠也就是BeanFactory的話去掉第5步就Ok了;

Spring Bean 的生命週期?

Spring框架中,一旦把一個Bean納入Spring IOC容器之中,這個Bean的生命週期就會交由容器進行管理,一般擔當管理角色的是BeanFactory或者ApplicationContext,認識一下Bean的生命週期活動,對更好的利用它有很大的幫助:

下面以BeanFactory為例,說明一個Bean的生命週期活動。

  1. Bean的建立, 由BeanFactory讀取Bean定義檔案,並生成各個例項;
  2. Setter注入,執行Bean的屬性依賴注入;
  3. BeanNameAware的setBeanName(), 如果實現該介面,則執行其setBeanName方法;
  4. BeanFactoryAware的setBeanFactory(),如果實現該介面,則執行其setBeanFactory方法;
  5. BeanPostProcessor的processBeforeInitialization(),如果有關聯的processor,則在Bean初始化之前都會執行這個例項的processBeforeInitialization()方法;
  6. InitializingBean的afterPropertiesSet(),如果實現了該介面,則執行其afterPropertiesSet()方法;
  7. Bean定義檔案中定義init-method;
  8. BeanPostProcessors的processAfterInitialization(),如果有關聯的processor,則在Bean初始化之前都會執行這個例項的processAfterInitialization()方法;
  9. DisposableBean的destroy(),在容器關閉時,如果Bean類實現了該介面,則執行它的destroy()方法;
  10. Bean定義檔案中定義destroy-method,在容器關閉時,可以在Bean定義檔案中使用“destory-method”定義的方法;

如果使用ApplicationContext來維護一個Bean的生命週期,則基本上與上邊的流程相同,只不過在執行BeanNameAware的setBeanName()後,若有Bean類實現了org.springframework.context.ApplicationContextAware介面,則執行其setApplicationContext()方法,然後再進行BeanPostProcessors的processBeforeInitialization() 實際上,ApplicationContext除了向BeanFactory那樣維護容器外,還提供了更加豐富的框架功能,如Bean的訊息,事件處理機制等

Spring AOP的實現原理?

所謂AOP,即Aspect orientied program,就是面向方面(切面)的程式設計。

特點

讓關注點程式碼與業務程式碼分離,可以動態地新增和刪除在切面上的邏輯而不影響原來的執行程式碼。

  1. 模組之間的耦合度;
  2. 統容易擴充套件;
  3. 的程式碼複用;

AOP核心概念

1、橫切關注點

對哪些方法進行攔截,攔截後怎麼處理,這些關注點稱之為橫切關注點

2、切面(aspect)

類是對物體特徵的抽象,切面就是對橫切關注點的抽象,面向切面程式設計,就是指 對很多功能都有的重複的程式碼抽取,再在執行的時候往業務方法上動態植入“切面類程式碼”。

3、連線點(joinpoint)

被攔截到的點,因為Spring只支援方法型別的連線點,所以在Spring中連線點指的就是被攔截到的方法,實際上連線點還可以是欄位或者構造器

4、切入點(pointcut)

切入點在AOP中的通知和切入點表示式關聯,指定攔截哪些類的哪些方法, 給指定的類在執行的時候動態的植入切面類程式碼。

5、通知(advice)

所謂通知指的就是指攔截到連線點之後要執行的程式碼,通知分為前置、後置、異常、最終、環繞通知五類

6、目標物件

被一個或者多個切面所通知的物件。

7、織入(weave)

將切面應用到目標物件並導致代理物件建立的過程

8、引入(introduction)

在不修改程式碼的前提下,引入可以在執行期為類動態地新增一些方法或欄位

9、AOP代理(AOP Proxy)

在Spring AOP中有兩種代理方式,JDK動態代理和CGLIB代理。

使用註解

  • @Aspect指定一個類為切面類;
  • @Pointcut("execution(* cn.itcast.e_aop_anno..(..))") 指定切入點表示式;
  • @Before("pointCut_()")前置通知: 目標方法之前執行;
  • @After("pointCut_()")後置通知:目標方法之後執行(始終執行);
  • @AfterReturning("pointCut_()")返回後通知: 執行方法結束前執行(異常不執行);
  • @AfterThrowing("pointCut_()")異常通知: 出現異常時候執行;
  • @Around("pointCut_()")環繞通知:環繞目標方法執行;

www.cnblogs.com/guzhou-ing/…

AOP的實現原理

www.importnew.com/24305.html

Spring AOP使用的動態代理,所謂的動態代理就是說AOP框架不會去修改位元組碼,而是在記憶體中臨時為方法生成一個AOP物件,這個AOP物件包含了目標物件的全部方法,並且在特定的切點做了增強處理,並回撥原物件的方法。

Spring AOP中的動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理。JDK動態代理通過反射來接收被代理的類,並且要求被代理的類必須實現一個介面。JDK動態代理的核心是InvocationHandler介面和Proxy類。

如果目標類沒有實現介面,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個程式碼生成的類庫,可以在執行時動態的生成某個類的子類,注意,CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記為final,那麼它是無法使用CGLIB做動態代理的。

Spring對AOP的支援

www.cnblogs.com/hongwz/p/57…

Spring中AOP代理由Spring的IOC容器負責生成、管理,其依賴關係也由IOC容器負責管理。因此,AOP代理可以直接使用容器中的其它bean例項作為目標,這種關係可由IOC容器的依賴注入提供。

Spring建立代理的規則為

  1. 預設使用Java動態代理來建立AOP代理,這樣就可以為任何介面例項建立代理了
  2. 當需要代理的類不是代理介面的時候,Spring會切換為使用CGLIB代理,也可強制使用CGLIB

AOP程式設計其實是很簡單的事情,程式設計師只需要參與三個部分:

  • 定義普通業務元件
  • 定義切入點,一個切入點可能橫切多個業務元件
  • 定義增強處理,增強處理就是在AOP框架為普通業務元件織入的處理動作

所以進行AOP程式設計的關鍵就是定義切入點和定義增強處理,一旦定義了合適的切入點和增強處理,AOP框架將自動生成AOP代理

即:代理物件的方法=增強處理+被代理物件的方法。

Spring 是如何管理事務的,事務管理機制?

事務管理可以幫助我們保證資料的一致性,對應企業的實際應用很重要。

Spring的事務機制包括宣告式事務和程式設計式事務。

  • 程式設計式事務管理:Spring推薦使用TransactionTemplate,實際開發中使用宣告式事務較多。
  • 宣告式事務管理:將我們從複雜的事務處理中解脫出來,獲取連線,關閉連線、事務提交、回滾、異常處理等這些操作都不用我們處理了,Spring都會幫我們處理。

宣告式事務管理使用了AOP面向切面程式設計實現的,本質就是在目標方法執行前後進行攔截。在目標方法執行前加入或建立一個事務,在執行方法執行後,根據實際情況選擇提交或是回滾事務

如何管理的

Spring事務管理主要包括3個介面,Spring的事務主要是由它們(PlatformTransactionManager,TransactionDefinition,TransactionStatus)三個共同完成的。

1. PlatformTransactionManager:事務管理器--主要用於平臺相關事務的管理

主要有三個方法:

  • commit 事務提交;
  • rollback 事務回滾;
  • getTransaction 獲取事務狀態。

2. TransactionDefinition:事務定義資訊--用來定義事務相關的屬性,給事務管理器PlatformTransactionManager使用

這個介面有下面四個主要方法:

  • getIsolationLevel:獲取隔離級別;
  • getPropagationBehavior:獲取傳播行為;
  • getTimeout:獲取超時時間;
  • isReadOnly:是否只讀(儲存、更新、刪除時屬性變為false--可讀寫,查詢時為true--只讀)

事務管理器能夠根據這個返回值進行優化,這些事務的配置資訊,都可以通過配置檔案進行配置。

3. TransactionStatus:事務具體執行狀態--事務管理過程中,每個時間點事務的狀態資訊。

例如它的幾個方法:

  • hasSavepoint():返回這個事務內部是否包含一個儲存點,
  • isCompleted():返回該事務是否已完成,也就是說,是否已經提交或回滾
  • isNewTransaction():判斷當前事務是否是一個新事務

宣告式事務的優缺點

  • 優點:不需要在業務邏輯程式碼中編寫事務相關程式碼,只需要在配置檔案配置或使用註解(@Transaction),這種方式沒有侵入性。
  • 缺點:宣告式事務的最細粒度作用於方法上,如果像程式碼塊也有事務需求,只能變通下,將程式碼塊變為方法。

blog.csdn.net/jie_liang/a…

事務的4個特性:

原子性(Atomicity)

  • 原子性是指事務包含的所有操作要麼全部成功,要麼全部失敗回滾,因此事務的操作如果成功就必須要完全應用到資料庫,如果操作失敗則不能對資料庫有任何影響。

一致性(Consistency)

  • 一致性是指事務必須使資料庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之後都必須處於一致性狀態

拿轉賬來說,假設使用者A和使用者B兩者的錢加起來一共是5000,那麼不管A和B之間如何轉賬,轉幾次賬,事務結束後兩個使用者的錢相加起來應該還得是5000,這就是事務的一致性。

隔離性(Isolation)

  • 隔離性是當多個使用者併發訪問資料庫時,比如操作同一張表時,資料庫為每一個使用者開啟的事務,不能被其他事務的操作所干擾,多個併發事務之間要相互隔離

即要達到這麼一種效果:對於任意兩個併發的事務T1和T2,在事務T1看來,T2要麼在T1開始之前就已經結束,要麼在T1結束之後才開始,這樣每個事務都感覺不到有其他事務在併發地執行。

關於事務的隔離性資料庫提供了多種隔離級別,稍後會介紹到。   永續性(Durability)

  • 永續性是指一個事務一旦被提交了,那麼對資料庫中的資料的改變就是永久性的,即便是在資料庫系統遇到故障的情況下也不會丟失提交事務的操作

事務的隔離級別

從理論上來說, 事務應該彼此完全隔離, 以避免併發事務所導致的問題,然而, 那樣會對效能產生極大的影響, 因為事務必須按順序執行, 在實際開發中, 為了提升效能, 事務會以較低的隔離級別執行, 事務的隔離級別可以通過隔離事務屬性指定

  1. ISOLATION_DEFAULT: 這是一個PlatfromTransactionManager預設的隔離級別,使用資料庫預設的事務隔離級別.另外四個與JDBC的隔離級別相對應
  2. ISOLATION_READ_UNCOMMITTED: 這是事務最低的隔離級別,它充許令外一個事務可以看到這個事務未提交的資料。這種隔離級別會產生髒讀,不可重複讀和幻像讀。
  3. ISOLATION_READ_COMMITTED: 保證一個事務修改的資料提交後才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的資料
  4. ISOLATION_REPEATABLE_READ: 這種事務隔離級別可以防止髒讀,不可重複讀。但是可能出現幻像讀。它除了保證一個事務不能讀取另一個事務未提交的資料外,還保證了避免下面的情況產生(不可重複讀)。
  5. ISOLATION_SERIALIZABLE 這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執行。除了防止髒讀,不可重複讀外,還避免了幻像讀。
  • 事務的隔離級別要得到底層資料庫引擎的支援, 而不是應用程式或者框架的支援.
  • Oracle 支援的 2 種事務隔離級別:READ_COMMITED , SERIALIZABLE
  • Mysql 支援 4 中事務隔離級別.

事務回滾屬性

  • 預設情況下只有未檢查異常(RuntimeException和Error型別的異常)會導致事務回滾. 而受檢查異常不會.
  • 事務的回滾規則可以通過@Transactional 註解的 rollbackFor 和 noRollbackFor 屬性來定義,這兩個屬性被宣告為 Class[] 型別的, 因此可以為這兩個屬性指定多個異常類。
  • rollbackFor: 遇到時必須進行回滾
  • noRollbackFor: 一組異常類,遇到時必須不回滾

不推薦使用的手動回滾事務的方法:

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
複製程式碼

Spring 的不同事務傳播行為有哪些,幹什麼用的?

當事務方法被另一個事務方法呼叫時, 必須指定事務應該如何傳播. 例如: 方法可能繼續在現有事務中執行, 也可能開啟一個新事務, 並在自己的事務中執行. 事務的傳播行為可以由傳播屬性指定.

Spring 定義了7 種類傳播行為.(一二兩種最常用)

  1. ==PROPAGATION_REQUIRED==: 如果存在一個事務,則支援當前事務,如果沒有事務則開啟
  2. ==PROPAGATION_REQUIRES_NEW==: 總是開啟一個新的事務,如果一個事務已經存在,則將這個存在的事務掛起
  3. PROPAGATION_SUPPORTS: 如果存在一個事務,支援當前事務。如果沒有事務,則非事務的執行
  4. PROPAGATION_MANDATORY: 如果已經存在一個事務,支援當前事務。如果沒有一個活動的事務,則丟擲異常。
  5. PROPAGATION_NOT_SUPPORTED: 總是非事務地執行,並掛起任何存在的事務。
  6. PROPAGATION_NEVER: 總是非事務地執行,如果存在一個活動事務,則丟擲異常
  7. PROPAGATION_NESTED:如果一個活動的事務存在,則執行在一個巢狀的事務中. 如果沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行

Spring 中用到了那些設計模式?

Spring框架中使用到了大量的設計模式,下面列舉了比較有代表性的:

  • 代理模式—在AOP和remoting中被用的比較多。
  • 單例模式—在spring配置檔案中定義的bean預設為單例模式。
  • 模板方法—用來解決程式碼重複的問題。比如. RestTemplate, JmsTemplate, JpaTemplate。
  • 工廠模式—BeanFactory用來建立物件的例項。
  • 介面卡--spring aop
  • 裝飾器--spring data hashmapper
  • 觀察者-- spring 時間驅動模型
  • 回撥--Spring ResourceLoaderAware回撥介面

工廠模式(Factory Method)

Spring容器就是例項化和管理Bean的工廠

工廠模式隱藏了建立類的細節,返回值必定是介面或者抽象類,而不是具體的某個物件,工廠類根據條件生成不同的子類例項。當得到子類的例項後,就可以呼叫基類中的方法,不必考慮返回的是哪一個子類的例項。

這個很明顯,在各種BeanFactory以及ApplicationContext建立中都用到了;

Spring通過配置檔案,就可以管理所有的bean,而這些bean就是Spring工廠能產生的例項,因此,首先我們在Spring配置檔案中對兩個例項進行配置

單態模式【單例模式】(Singleton)

Spring預設將所有的Bean設定成 單例模式,即對所有的相同id的Bean的請求,都將返回同一個共享的Bean例項。這樣就可以大大降低Java建立物件和銷燬時的系統開銷

使用Spring將Bean設定稱為單例行為,則無需自己完成單例模式。

可以通過singleton=“true|false” 或者 scope=“?”來指定

介面卡(Adapter)

在Spring的Aop中,使用的Advice(通知)來增強被代理類的功能。Spring實現這一AOP功能的原理就使用代理模式(1、JDK動態代理。2、CGLib位元組碼生成技術代理。)對類進行方法級別的切面增強,即,生成被代理類的代理類, 並在代理類的方法前,設定攔截器,通過執行攔截器重的內容增強了代理方法的功能,實現的面向切面程式設計

代理(Proxy)

Spring實現了一種能夠通過額外的方法呼叫完成任務的設計模式 - 代理設計模式,比如JdkDynamicAopProxy和Cglib2AopProxy。

代理設計模式的一個很好的例子是org.springframework.aop.framework.ProxyFactoryBean該工廠根據Spring bean構建AOP代理。該類實現了定義getObject()方法的FactoryBean介面。此方法用於將需求Bean的例項返回給bean factory。在這種情況下,它不是返回的例項,而是AOP代理。在執行代理物件的方法之前,可以通過呼叫補充方法來進一步“修飾”代理物件(其實所謂的靜態代理不過是在裝飾模式上加了個要不要你來幹動作行為而已,而不是裝飾模式什麼也不做就加了件衣服,其他還得由你來全權完成)。

觀察者(Observer)

定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。 spring中Observer模式常用的地方是listener的實現。如ApplicationListener

Spring MVC 的工作原理?

www.cnblogs.com/xiaoxi/p/61…

SpringMVC流程

  1. 使用者傳送請求至前端控制器DispatcherServlet。
  2. DispatcherServlet收到請求呼叫HandlerMapping處理器對映器。
  3. 處理器對映器找到具體的處理器(可以根據xml配置、註解進行查詢),生成處理器物件及處理器攔截器(如果有則生成)一併返回給DispatcherServlet。
  4. DispatcherServlet呼叫HandlerAdapter處理器介面卡。
  5. HandlerAdapter經過適配呼叫具體的處理器(Controller,也叫後端控制器)。
  6. Controller執行完成返回ModelAndView。
  7. HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet。
  8. DispatcherServlet將ModelAndView傳給ViewReslover檢視解析器。
  9. ViewReslover解析後返回具體View。
  10. DispatcherServlet根據View進行渲染檢視(即將模型資料填充至檢視中)。
  11. DispatcherServlet響應使用者。
  • 第一步: 使用者發起請求到前端控制器(DispatcherServlet)
  • 第二步:前端控制器請求處理器對映器(HandlerMappering)去查詢處理器(Handle):通過xml配置或者註解進行查詢
  • 第三步:找到以後處理器對映器(HandlerMappering)像前端控制器返回執行鏈(HandlerExecutionChain)
  • 第四步:前端控制器(DispatcherServlet)呼叫處理器介面卡(HandlerAdapter)去執行處理器(Handler)
  • 第五步:處理器介面卡去執行Handler
  • 第六步:Handler執行完給處理器介面卡返回ModelAndView
  • 第七步:處理器介面卡向前端控制器返回ModelAndView
  • 第八步:前端控制器請求檢視解析器(ViewResolver)去進行檢視解析
  • 第九步:檢視解析器像前端控制器返回View
  • 第十步:前端控制器對檢視進行渲染
  • 第十一步:前端控制器向使用者響應結果

看到這些步驟我相信大家很感覺非常的亂,這是正常的,但是這裡主要是要大家理解springMVC中的幾個元件

前端控制器(DispatcherServlet):接收請求,響應結果,相當於電腦的CPU。

處理器對映器(HandlerMapping):根據URL去查詢處理器

處理器(Handler):(需要程式設計師去寫程式碼處理邏輯的)

處理器介面卡(HandlerAdapter):會把處理器包裝成介面卡,這樣就可以支援多種型別的處理器,類比筆記本的介面卡(介面卡模式的應用)

檢視解析器(ViewResovler):進行檢視解析,多返回的字串,進行處理,可以解析成對應的頁面

Spring如何解決迴圈依賴?

www.importnew.com/17580.html

一、構造器迴圈依賴:表示通過構造器注入構成的迴圈依賴,此依賴是無法解決的,只能丟擲BeanCurrentlyInCreationException異常表示迴圈依賴。

如在建立CircleA類時,構造器需要CircleB類,那將去建立CircleB,在建立CircleB類時又發現需要CircleC類,則又去建立CircleC,最終在建立CircleC時發現又需要CircleA;從而形成一個環,沒辦法建立。

Spring容器將每一個正在建立的Bean 識別符號放在一個“當前建立Bean池”中,Bean識別符號在建立過程中將一直保持在這個池中,因此如果在建立Bean過程中發現自己已經在“當前建立Bean池”裡時將丟擲BeanCurrentlyInCreationException異常表示迴圈依賴;而對於建立完畢的Bean將從“當前建立Bean池”中清除掉

1)首先讓我們看一下配置檔案(chapter3/circleInjectByConstructor.xml):

<bean id="circleA" class="cn.javass.spring.chapter3.bean.CircleA">
<constructor-arg index="0" ref="circleB"/>
</bean>
<bean id="circleB" class="cn.javass.spring.chapter3.bean.CircleB">
<constructor-arg index="0" ref="circleC"/>
</bean>
<bean id="circleC" class="cn.javass.spring.chapter3.bean.CircleC">
<constructor-arg index="0" ref="circleA"/>
</bean>
複製程式碼

2)寫段測試程式碼(cn.javass.spring.chapter3.CircleTest)測試一下吧:

@Test(expected = BeanCurrentlyInCreationException.class)
public void testCircleByConstructor() throws Throwable {
try {
      new ClassPathXmlApplicationContext("chapter3/circleInjectByConstructor.xml");
    }
    catch (Exception e) {
      //因為要在建立circle3時丟擲;
      Throwable e1 = e.getCause().getCause().getCause();
      throw e1;
    }
}
複製程式碼

讓我們分析一下吧:

1、Spring容器建立“circleA” Bean,首先去“當前建立Bean池”查詢是否當前Bean正在建立,如果沒發現,則繼續準備其需要的構造器引數“circleB”,並將“circleA” 識別符號放到“當前建立Bean池”;

2、Spring容器建立“circleB” Bean,首先去“當前建立Bean池”查詢是否當前Bean正在建立,如果沒發現,則繼續準備其需要的構造器引數“circleC”,並將“circleB” 識別符號放到“當前建立Bean池”;

3、Spring容器建立“circleC” Bean,首先去“當前建立Bean池”查詢是否當前Bean正在建立,如果沒發現,則繼續準備其需要的構造器引數“circleA”,並將“circleC” 識別符號放到“當前建立Bean池”;

4、到此為止Spring容器要去建立“circleA”Bean,發現該Bean 識別符號在“當前建立Bean池”中,因為表示迴圈依賴,丟擲BeanCurrentlyInCreationException。

二、setter迴圈依賴:表示通過setter注入方式構成的迴圈依賴。

對於setter注入造成的依賴是通過Spring容器提前暴露剛完成構造器注入但未完成其他步驟(如setter注入)的Bean來完成的,而且只能解決單例作用域的Bean迴圈依賴。

addSingletonFactory(beanName, new ObjectFactory() {
    public Object getObject() throws BeansException {
        return getEarlyBeanReference(beanName, mbd, bean);
    }
});
複製程式碼

具體步驟如下:

1、Spring容器建立單例“circleA” Bean,首先根據無參構造器建立Bean,並暴露一個“ObjectFactory ”用於返回一個提前暴露一個建立中的Bean,並將“circleA” 識別符號放到“當前建立Bean池”;然後進行setter注入“circleB”;

2、Spring容器建立單例“circleB” Bean,首先根據無參構造器建立Bean,並暴露一個“ObjectFactory”用於返回一個提前暴露一個建立中的Bean,並將“circleB” 識別符號放到“當前建立Bean池”,然後進行setter注入“circleC”;

3、Spring容器建立單例“circleC” Bean,首先根據無參構造器建立Bean,並暴露一個“ObjectFactory ”用於返回一個提前暴露一個建立中的Bean,並將“circleC” 識別符號放到“當前建立Bean池”,然後進行setter注入“circleA”;進行注入“circleA”時由於提前暴露了“ObjectFactory”工廠從而使用它返回提前暴露一個建立中的Bean;

4、最後在依賴注入“circleB”和“circleA”,完成setter注入。

Spring 如何保證 Controller 併發的安全?

Spring 多執行緒請求過來呼叫的Controller物件都是一個,而不是一個請求過來就建立一個Controller物件。

併發的安全? 原因就在於Controller物件是單例的,那麼如果不小心在類中定義了類變數,那麼這個類變數是被所有請求共享的,這可能會造成多個請求修改該變數的值,出現與預期結果不符合的異常

那有沒有辦法讓Controller不以單例而以每次請求都重新建立的形式存在呢

答案是當然可以,只需要在類上新增註解@Scope("prototype")即可,這樣每次請求呼叫的類都是重新生成的(每次生成會影響效率)

雖然這樣可以解決問題,但增加了時間成本,總讓人不爽,還有其他方法麼?答案是肯定的!

使用ThreadLocal來儲存類變數,將類變數儲存線上程的變數域中,讓不同的請求隔離開來。

推薦閱讀

Spring 常見的一些面試題整理

相關文章