1. 程式人生 > >自己實戰整理面試題--Spring(帶答案,不斷更新)

自己實戰整理面試題--Spring(帶答案,不斷更新)

Spring 的原理?

Spring的核心主要是IOC和AOP;從Spring簡單的來說,是通過對POJO開發的支援,來具體實現的;Spring通過為應用開發提供基於POJO的開發模式,把應用開發和複雜的Java EE服務,實現解耦,並通過提高單元測試的覆蓋率,從而有效的提高整個應用的開發質量。在Spring中,就是由IOC容器以及AOP來具體提供的,這兩個模組,在很大程度上,體現了Spring作為應用開發平臺的核心價值。

Spring的優點?

1.方便解耦,簡化開發

通過Spring提供的IoC容器,我們可以將物件之間的依賴關係交由Spring進行控制,避免硬編碼所造成的過度程式耦合。有了Spring,使用者不必再為單例項模式類、屬性檔案解析等這些很底層的需求編寫程式碼,可以更專注於上層的應用。

2.AOP程式設計的支援

通過Spring提供的AOP功能,方便進行面向切面的程式設計,許多不容易用傳統OOP實現的功能可以通過AOP輕鬆應付。

3.宣告事物的支援

在Spring中,我們可以從單調煩悶的事務管理程式碼中解脫出來,通過宣告式方式靈活地進行事務的管理,提高開發效率和質量。

4.方便程式的測試

可以用非容器依賴的程式設計方式進行幾乎所有的測試工作,在Spring裡,測試不再是昂貴的操作,而是隨手可做的事情。例如:Spring對Junit4支援,可以通過註解方便的測試Spring程式。

5.方便整合各種優秀框架

Spring不排斥各種優秀的開源框架,相反,Spring可以降低各種框架的使用難度,Spring提供了對各種優秀框架(如Struts,Hibernate、Hessian、Quartz)等的直接支援。

6.降低Java EE API的使用難度

Spring對很多難用的Java EE API(如JDBC,JavaMail,遠端呼叫等)提供了一個薄薄的封裝層,通過Spring的簡易封裝,這些Java EE API的使用難度大為降低。

7.Java 原始碼是經典學習範例

Spring的原始碼設計精妙、結構清晰、匠心獨用,處處體現著大師對Java設計模式靈活運用以及對Java技術的高深造詣。Spring框架原始碼無疑是Java技術的最佳實踐範例。如果想在短時間內迅速提高自己的Java技術水平和應用開發水平,學習和研究Spring原始碼將會使你收到意想不到的效果。

低侵入式設計,程式碼汙染極低,Spring並不強制應用完全依賴於Spring,開發者可自由選用Spring框架的部分或全部

Spring如何實現解耦合?

Spring IOC 容器 通過反射拿到物件的例項,使類的功能更加單一化,減少了類與類的依賴關係,So,降低了耦合

IOC核心?

由 Spring IOC 容器來負責物件的生命週期和物件之間的關係

 IOC 優點是什麼?

控制反轉,依賴倒置原則

DI和IOC其實是一個思想,她們的的好處是:如果依賴的類修改了,比如修改了建構函式,如果沒有依賴注入,則需要修改依賴物件呼叫著,如果依賴注入則不需要。

spring IOC的好處是,物件的構建如果依賴非常多的物件,且層次很深,外層在構造物件時很麻煩且不一定知道如何構建這麼多層次的物件。 IOC 幫我們管理物件的建立,只需要在配置檔案裡指定如何構建,每一個物件的配置檔案都在類編寫的時候指定了,所以最外層物件不需要關心深層次物件如何建立的,前人都寫好了。

控制反轉,依賴倒置原則

IOC原理

IOC三種注入方式?

構造器,setter(靈活),介面(侵略性,實現不必要的介面)

Resource體系

Resource,對資源的抽象,它的每一個實現類都代表了一種資源的訪問策略,如ClasspathResource 、 URLResource ,FileSystemResource 等。Spring 利用 ResourceLoader 來進行統一資源載入

BeanFactory?

BeanFactory 是一個非常純粹的 bean 容器,它是 IOC 必備的資料結構,其中 BeanDefinition 是她的基本結構,它內部維護著一個 BeanDefinition map ,並可根據 BeanDefinition 的描述進行 bean 的建立和管理。

BeanFacoty (介面)有三個直接子類 (介面)ListableBeanFactoryHierarchicalBeanFactoryAutowireCapableBeanFactoryDefaultListableBeanFactory類 為最終預設實現,它實現了所有介面。

BeanFactory提供的方法及其簡單,僅提供了六種方法供客戶呼叫:

  •   boolean containsBean(String beanName) 判斷工廠中是否包含給定名稱的bean定義,若有則返回true
  •   Object getBean(String) 返回給定名稱註冊的bean例項。根據bean的配置情況,如果是singleton模式將返回一個共享例項,否則將返回一個新建的例項,如果沒有找到指定bean,該方法可能會丟擲異常
  •   Object getBean(String, Class) 返回以給定名稱註冊的bean例項,並轉換為給定class型別
  •   Class getType(String name) 返回給定名稱的bean的Class,如果沒有找到指定的bean例項,則排除NoSuchBeanDefinitionException異常
  •   boolean isSingleton(String) 判斷給定名稱的bean定義是否為單例模式
  •   String[] getAliases(String name) 返回給定bean名稱的所有別名

ApplicationContext體系?

這個就是大名鼎鼎的 Spring 容器,它叫做應用上下文,與我們應用息息相關,她繼承 BeanFactory,所以它是 BeanFactory 的擴充套件升級版,如果BeanFactory 是屌絲的話,那麼 ApplicationContext 則是名副其實的高富帥。由於 ApplicationContext 的結構就決定了它與 BeanFactory 的不同,其主要區別有:

  1. 繼承 MessageSource,提供國際化的標準訪問策略。
  2. 繼承 ApplicationEventPublisher ,提供強大的事件機制。
  3. 擴充套件 ResourceLoader,可以用來載入多個 Resource,可以靈活訪問不同的資源。
  4. 對 Web 應用的支援。

FactoryBean?

IOC初始化流程

IOC 容器的初始化過程分為三步驟:Resource 定位、BeanDefinition 的載入和解析,BeanDefinition 註冊

IOC資源定義,載入過程?

  • Spring 提供了 Resource 和 ResourceLoader 來統一抽象整個資源及其定位。使得資源與資源的定位有了一個更加清晰的界限,並且提供了合適的 Default 類,使得自定義實現更加方便和清晰。
  • DefaultResource 為 Resource 的預設實現,它對 Resource 介面做了一個統一的實現,子類繼承該類後只需要覆蓋相應的方法即可,同時對於自定義的 Resource 我們也是繼承該類。
  • DefaultResourceLoader 同樣也是 ResourceLoader 的預設實現,在自定 ResourceLoader 的時候我們除了可以繼承該類外還可以實現 ProtocolResolver 介面來實現自定資源載入協議。
  • DefaultResourceLoader 每次只能返回單一的資源,所以 Spring 針對這個提供了另外一個介面 ResourcePatternResolver ,該介面提供了根據指定的 locationPattern 返回多個資源的策略。其子類 PathMatchingResourcePatternResolver 是一個集大成者的 ResourceLoader ,因為它即實現了 Resource getResource(String location) 也實現了 Resource[] getResources(String locationPattern)

IOC裝載核心邏輯?

核心邏輯方法 doLoadBeanDefinitions()中主要是做三件事情。

  • 呼叫 getValidationModeForResource() 獲取 xml 檔案的驗證模式(DTD或者XSD)
  • 呼叫 loadDocument() 根據 xml 檔案獲取相應的 Document 例項。
  • 呼叫 registerBeanDefinitions() 註冊 Bean 例項。

獲得Document例項後進行import,bean等標籤及標籤的屬性進行解析,構造BeanDefinition物件

擴充套件 Spring 自定義標籤配置一般需要以下幾個步驟:

  1. 建立一個需要擴充套件的元件
  2. 定義一個 XSD 檔案,用於描述元件內容
  3. 建立一個實現 AbstractSingleBeanDefinitionParser 介面的類,用來解析 XSD 檔案中的定義和元件定義
  4. 建立一個 Handler,繼承 NamespaceHandlerSupport ,用於將元件註冊到 Spring 容器
  5. 編寫 Spring.handlers 和 Spring.schemas 檔案

自定義標籤的解析過程。

其實整個過程還是較為簡單:首先會載入 handlers 檔案,將其中內容進行一個解析,形成 <namespaceUri,類路徑> 這樣的一個對映,然後根據獲取的 namespaceUri 就可以得到相應的類路徑,對其進行初始化等到相應的 Handler 物件,呼叫 parse() 方法,在該方法中根據標籤的 localName 得到相應的 BeanDefinitionParser 例項物件,呼叫 parse() ,該方法定義在 AbstractBeanDefinitionParser 抽象類中,核心邏輯封裝在其 parseInternal() 中,該方法返回一個 AbstractBeanDefinition 例項物件,其主要是在 AbstractSingleBeanDefinitionParser 中實現,對於自定義的 Parser 類,其需要實現 getBeanClass() 或者 getBeanClassName()doParse()

解析工作分為三步:1、解析預設標籤;2、解析預設標籤後下得自定義標籤;3、註冊解析後的 BeanDefinition。經過前面兩個步驟的解析,這時的 BeanDefinition 已經可以滿足後續的使用要求了,那麼接下來的工作就是將這些 BeanDefinition 進行註冊,也就是完成第三步。

BeanDefinition 解析過程?

首先通過 beanName 註冊 BeanDefinition ,然後再註冊別名 alias。BeanDefinition 的註冊由介面 BeanDefinitionRegistry 定義。

處理過程如下:

  • 首先 BeanDefinition 進行校驗,該校驗也是註冊過程中的最後一次校驗了,主要是對 AbstractBeanDefinition 的 methodOverrides 屬性進行校驗
  • 根據 beanName 從快取中獲取 BeanDefinition,如果快取中存在,則根據 allowBeanDefinitionOverriding 標誌來判斷是否允許覆蓋,如果允許則直接覆蓋,否則丟擲 BeanDefinitionStoreException 異常
  • 若快取中沒有指定 beanName 的 BeanDefinition,則判斷當前階段是否已經開始了 Bean 的建立階段(),如果是,則需要對 beanDefinitionMap 進行加鎖控制併發問題,否則直接設定即可。對於 hasBeanCreationStarted() 方法後續做詳細介紹,這裡不過多闡述。
  • 若快取中存在該 beanName 或者 單利 bean 集合中存在該 beanName,則呼叫 resetBeanDefinition() 重置 BeanDefinition 快取。

其實整段程式碼的核心就在於 this.beanDefinitionMap.put(beanName, beanDefinition); 。BeanDefinition 的快取也不是神奇的東西,就是定義 map ,key 為 beanName,value 為 BeanDefinition。註冊 alias 和註冊 BeanDefinition 的過程差不多

IOC之Bean載入流程

載入 bean 階段:經過容器初始化階段後,應用程式中定義的 bean 資訊已經全部載入到系統中了,當我們顯示或者隱式地呼叫 getBean() 時,則會觸發載入 bean 階段。在這階段,容器會首先檢查所請求的物件是否已經初始化完成了,如果沒有,則會根據註冊的 bean 資訊例項化請求的物件,併為其註冊依賴,然後將其返回給請求方。至此第二個階段也已經完成。

內部呼叫 doGetBean() 方法,其接受四個引數:

  • name:要獲取 bean 的名字
  • requiredType:要獲取 bean 的型別
  • args:建立 bean 時傳遞的引數。這個引數僅限於建立 bean 時使用
  • typeCheckOnly:是否為型別檢查

程式碼超級長:

1.獲取 beanName

final String beanName = transformedBeanName(name);

這裡傳遞的是 name,不一定就是 beanName,可能是 aliasName,也有可能是 FactoryBean,所以這裡需要呼叫 transformedBeanName() 方法對 name 進行一番轉換。

主要處理過程包括兩步:

  1. 去除 FactoryBean 的修飾符。如果 name 以 “&” 為字首,那麼會去掉該 “&”,例如,name = "&studentService",則會是 name = "studentService"
  2. 取指定的 alias 所表示的最終 beanName。主要是一個迴圈獲取 beanName 的過程,例如別名 A 指向名稱為 B 的 bean 則返回 B,若 別名 A 指向別名 B,別名 B 指向名稱為 C 的 bean,則返回 C。

2.從單例 bean 快取中獲取 bean

單例模式的 bean 在整個過程中只會被建立一次,第一次建立後會將該 bean 載入到快取中,後面在獲取 bean 就會直接從單例快取中獲取。如果從快取中得到了 bean,則需要呼叫 getObjectForBeanInstance() 對 bean 進行例項化處理,因為快取中記錄的是最原始的 bean 狀態,我們得到的不一定是我們最終想要的 bean。

從單例快取中獲取Bean:

首先從 singletonObjects 中獲取,若為空且當前 bean 正在建立中,則從 earlySingletonObjects 中獲取,若為空且允許提前建立則從 singletonFactories 中獲取相應的 ObjectFactory ,若不為空,則呼叫其 getObject() 建立 bean,然後將其加入到 earlySingletonObjects,然後從 singletonFactories 刪除。總體邏輯就是根據 beanName 依次檢測這三個 Map,若為空,從下一個,否則返回。這三個 Map 存放的都有各自的功能,如下:

  • singletonObjects :存放的是單例 bean,對應關係為 bean name --> bean instance
  • earlySingletonObjects:存放的是早期的 bean,對應關係也是 bean name --> bean instance。它與 singletonObjects 區別在於 earlySingletonObjects 中存放的 bean 不一定是完整的,從上面過程中我們可以瞭解,bean 在建立過程中就已經加入到 earlySingletonObjects 中了,所以當在 bean 的建立過程中就可以通過 getBean() 方法獲取。這個 Map 也是解決迴圈依賴的關鍵所在。
  • singletonFactories:存放的是 ObjectFactory,可以理解為建立單例 bean 的 factory,對應關係是 bean name --> ObjectFactory

從快取中獲取的 bean 是最原始的 bean 並不一定使我們最終想要的 bean,呼叫 getObjectForBeanInstance() 進行處理,該方法的定義為獲取給定 bean 例項的物件,該物件要麼是 bean 例項本身,要麼就是 FactoryBean 建立的物件。

getObjectForBeanInstance() 主要是返回給定的 bean 例項物件,當然該例項物件為非 FactoryBean 型別,對於 FactoryBean 型別的 bean,則是委託 getObjectFromFactoryBean() 從 FactoryBean 獲取 bean 例項物件。

主要流程如下:

  • 若為單例且單例 bean 快取中存在 beanName,則進行後續處理(跳轉到下一步),否則則從 FactoryBean 中獲取 bean 例項物件,如果接受後置處理,則呼叫 postProcessObjectFromFactoryBean() 進行後置處理。
  • 首先獲取鎖(其實我們在前面篇幅中發現了大量的同步鎖,鎖住的物件都是 this.singletonObjects, 主要是因為在單例模式中必須要保證全域性唯一),然後從 factoryBeanObjectCache 快取中獲取例項物件 object,若 object 為空,則呼叫 doGetObjectFromFactoryBean() 方法從 FactoryBean 獲取物件,其實內部就是呼叫 FactoryBean.getObject()
  • 如果需要後續處理,則進行進一步處理,步驟如下:
    • 若該 bean 處於建立中(isSingletonCurrentlyInCreation),則返回非處理物件,而不是儲存它
    • 呼叫 beforeSingletonCreation() 進行建立之前的處理。預設實現將該 bean 標誌為當前建立的。
    • 呼叫 postProcessObjectFromFactoryBean() 對從 FactoryBean 獲取的 bean 例項物件進行後置處理,預設實現是按照原樣直接返回,具體實現是在 AbstractAutowireCapableBeanFactory 中實現的,當然子類也可以重寫它,比如應用後置處理
    • 呼叫 afterSingletonCreation() 進行建立 bean 之後的處理,預設實現是將該 bean 標記為不再在建立中。
  • 最後加入到 FactoryBeans 快取中。

3.原型模式依賴檢查與 parentBeanFactory

Spring 只處理單例模式下得迴圈依賴,對於原型模式的迴圈依賴直接丟擲異常。主要原因還是在於 Spring 解決迴圈依賴的策略有關。對於單例模式 Spring 在建立 bean 的時候並不是等 bean 完全建立完成後才會將 bean 新增至快取中,而是不等 bean 建立完成就會將建立 bean 的 ObjectFactory 提早加入到快取中,這樣一旦下一個 bean 建立的時候需要依賴 bean 時則直接使用 ObjectFactroy。但是原型模式我們知道是沒法使用快取的,所以 Spring 對原型模式的迴圈依賴處理策略則是不處理。

如果容器快取中沒有相對應的 BeanDefinition 則會嘗試從父類工廠(parentBeanFactory)中載入,然後再去遞迴呼叫 getBean()

檢測邏輯和單例模式一樣,一個“集合”存放著正在建立的 bean,從該集合中進行判斷即可,只不過單例模式的“集合”為 Set ,而原型模式的則是 ThreadLocal 

委託 parentBeanFactory 的 getBean() 進行處理,只不過在獲取之前對 name 進行簡單的處理,主要是想獲取原始的 beanName ,因為最開始對name處理了,這塊再還原。

 有依賴 bean 的話,那麼在初始化該 bean 時是需要先初始化它所依賴的 bean。

  1. 檢測。若當前 bean 在建立,則丟擲 BeanCurrentlyInCreationException 異常。
  2. 如果 beanDefinitionMap 中不存在 beanName 的 BeanDefinition(即在 Spring bean 初始化過程中沒有載入),則嘗試從 parentBeanFactory 中載入。
  3. 判斷是否為型別檢查。
  4. 從 mergedBeanDefinitions 中獲取 beanName 對應的 RootBeanDefinition,如果這個 BeanDefinition 是子 Bean 的話,則會合並父類的相關屬性。
  5. 依賴處理。

3. 依賴處理

整體主要是分為三個部分:

各scope的bean建立:

singleton:Spring預設

getSingleton()方法做了一部分準備和預處理步驟,真正獲取單例 bean 的方法其實是由 singletonFactory.getObject() 這部分實現,而 singletonFactory 由回撥方法產生。那麼這個方法做了哪些準備呢?

再次檢查快取是否已經載入過,如果已經載入了則直接返回,否則開始載入過程。

呼叫 beforeSingletonCreation() 記錄載入單例 bean 之前的載入狀態,即前置處理。

呼叫引數傳遞的 ObjectFactory 的 getObject() 例項化 bean。

呼叫 afterSingletonCreation() 進行載入單例後的後置處理。

將結果記錄並加入值快取中,同時刪除載入 bean 過程中所記錄的一些輔助狀態。

載入了單例 bean 後,呼叫 getObjectForBeanInstance() 從 bean 例項中獲取物件。

原型模式prototype:

  1. 分析從快取中獲取單例 bean,以及對 bean 的例項中獲取物件
  2. 如果從單例快取中獲取 bean,Spring 是怎麼載入的呢?所以第二部分是分析 bean 載入,以及 bean 的依賴處理
  3. bean 已經載入了,依賴也處理完畢了,第三部分則分析各個作用域的 bean 初始化過程。 

直接建立一個新的例項就可以了。過程如下:

其他作用域:

核心流程和原型模式一樣,只不過獲取 bean 例項是由 scope.get() 實現.

  1. 呼叫 beforeSingletonCreation() 記錄載入原型模式 bean 之前的載入狀態,即前置處理。
  2. 呼叫 createBean() 建立一個 bean 例項物件。
  3. 呼叫 afterSingletonCreation() 進行載入原型模式 bean 後的後置處理。
  4. 呼叫 getObjectForBeanInstance() 從 bean 例項中獲取物件。

Spring AOP的實現原理和場景?
 

aop的底層實現,動態代理是如何動態,假如有100個物件,如何動態的為這100個物件代理
spring的注入配置bean的方式

Spring bean的作用域和生命週期

https://blog.csdn.net/qq_32575047/article/details/78997488

xml 中配置的 init、destroy 方法怎麼可以做到呼叫具體的方法?

SpringMVC、動態代理、反射、事務隔離級別;

web.xml的配置
spring的監聽器。
Autowire註解
用過spring的執行緒池還是java的執行緒池?


springmvc的流程
spring boot的啟動過程,一個bean xml檔案,如何讀取,讀取以後,是如何建立這個物件的。
springMVC原理
Spring Boot比Spring做了哪些改進? Spring 5比Spring4做了哪些改進;
如何自定義一個Spring Boot Starter?

Spring RestTemplate 的具體實現
專案用的是 SpringBoot ,你能說下 Spring Boot 與 Spring 的區別嗎?
SpringBoot 的自動配置是怎麼做的?

Spring Cloud 有了解多少?

Spring Boot除了自動配置,相比傳統的 Spring 有什麼其他的區別?