1. 程式人生 > 實用技巧 >15個經典的Spring面試常見問題

15個經典的Spring面試常見問題

這篇文章主要是想通過一些問題,加深大家對於 Spring 的理解,所以不會涉及太多的程式碼!這篇文章整理了挺長時間,下面的很多問題我自己在使用 Spring 的過程中也並沒有注意,自己也是臨時查閱了很多資料和書籍補上的。網上也有一些很多關於 Spring 常見問題/面試題整理的文章,我感覺大部分都是互相 copy,而且很多問題也不是很汗,有些回答也存在問題。所以,自己花了一週的業餘時間整理了一下,希望對大家有幫助。

什麼是 Spring 框架?

Spring 是一種輕量級開發框架,旨在提高開發人員的開發效率以及系統的可維護性。Spring 官網:https://spring.io/

我們一般說 Spring 框架指的都是 Spring Framework,它是很多模組的集合,使用這些模組可以很方便地協助我們進行開發。這些模組是:核心容器、資料訪問/整合,、Web、AOP(面向切面程式設計)、工具、訊息和測試模組。比如:Core Container 中的 Core 元件是Spring 所有元件的核心,Beans 元件和 Context 元件是實現IOC和依賴注入的基礎,AOP元件用來實現面向切面程式設計。

Spring 官網列出的 Spring 的 6 個特徵:

  • 核心技術:依賴注入(DI),AOP,事件(events),資源,i18n,驗證,資料繫結,型別轉換,SpEL。
  • 測試:模擬物件,TestContext框架,Spring MVC 測試,WebTestClient。
  • 資料訪問:事務,DAO支援,JDBC,ORM,編組XML。
  • Web支援: Spring MVC和Spring WebFlux Web框架。
  • 整合:遠端處理,JMS,JCA,JMX,電子郵件,任務,排程,快取。
  • 語言:Kotlin,Groovy,動態語言。

列舉一些重要的Spring模組?

下圖對應的是 Spring4.x 版本。目前最新的5.x版本中 Web 模組的 Portlet 元件已經被廢棄掉,同時增加了用於非同步響應式處理的 WebFlux 元件。

  • Spring Core:基礎,可以說 Spring 其他所有的功能都需要依賴於該類庫。主要提供 IOC 依賴注入功能。
  • **Spring Aspects ** : 該模組為與AspectJ的整合提供支援。
  • Spring AOP:提供了面向方面的程式設計實現。
  • Spring JDBC: Java資料庫連線。
  • Spring JMS:Java訊息服務。
  • Spring ORM: 用於支援Hibernate等ORM工具。
  • Spring Web: 為建立Web應用程式提供支援。
  • Spring Test: 提供了對 JUnit 和 TestNG 測試的支援。

談談自己對於 Spring IoC 和 AOP 的理解

IoC

IoC(Inverse of Control:控制反轉)是一種設計思想,就是將原本在程式中手動建立物件的控制權,交由Spring框架來管理。IoC 在其他語言中也有應用,並非 Spirng 特有。IoC 容器是 Spring 用來實現 IoC 的載體, IoC 容器實際上就是個Map(key,value),Map 中存放的是各種物件。

將物件之間的相互依賴關係交給 IOC 容器來管理,並由 IOC 容器完成物件的注入。這樣可以很大程度上簡化應用的開發,把應用從複雜的依賴關係中解放出來。IOC 容器就像是一個工廠一樣,當我們需要建立一個物件的時候,只需要配置好配置檔案/註解即可,完全不用考慮物件是如何被創建出來的。在實際專案中一個 Service 類可能有幾百甚至上千個類作為它的底層,假如我們需要例項化這個 Service,你可能要每次都要搞清這個 Service 所有底層類的建構函式,這可能會把人逼瘋。如果利用 IOC 的話,你只需要配置好,然後在需要的地方引用就行了,這大大增加了專案的可維護性且降低了開發難度。

Spring 時代我們一般通過 XML 檔案來配置 Bean,後來開發人員覺得 XML 檔案來配置不太好,於是 SpringBoot 註解配置就慢慢開始流行起來。

推薦閱讀:https://www.zhihu.com/question/23277575/answer/169698662

Spring IOC的初始化過程:

IOC原始碼閱讀

AOP

AOP(Aspect-Oriented Programming:面向切面程式設計)能夠將那些與業務無關,卻為業務模組所共同呼叫的邏輯或責任(例如事務處理、日誌管理、許可權控制等)封裝起來,便於減少系統的重複程式碼,降低模組間的耦合度,並有利於未來的可拓展性和可維護性。

Spring AOP就是基於動態代理的,如果要代理的物件,實現了某個介面,那麼Spring AOP會使用JDK Proxy,去建立代理物件,而對於沒有實現介面的物件,就無法使用 JDK Proxy 去進行代理了,這時候Spring AOP會使用Cglib,這時候Spring AOP會使用Cglib生成一個被代理物件的子類來作為代理,如下圖所示:

當然你也可以使用 AspectJ ,Spring AOP 已經集成了AspectJ ,AspectJ 應該算的上是 Java 生態系統中最完整的 AOP 框架了。

使用 AOP 之後我們可以把一些通用功能抽象出來,在需要用到的地方直接使用即可,這樣大大簡化了程式碼量。我們需要增加新功能時也方便,這樣也提高了系統擴充套件性。日誌功能、事務管理等等場景都用到了 AOP 。

Spring AOP 和 AspectJ AOP 有什麼區別?

Spring AOP 屬於執行時增強,而 AspectJ 是編譯時增強。Spring AOP 基於代理(Proxying),而 AspectJ 基於位元組碼操作(Bytecode Manipulation)。

Spring AOP 已經集成了 AspectJ ,AspectJ 應該算的上是 Java 生態系統中最完整的 AOP 框架了。AspectJ 相比於 Spring AOP 功能更加強大,但是 Spring AOP 相對來說更簡單,

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

Spring 中的 bean 的作用域有哪些?

  • singleton : 唯一 bean 例項,Spring 中的 bean 預設都是單例的。
  • prototype : 每次請求都會建立一個新的 bean 例項。
  • request : 每一次HTTP請求都會產生一個新的bean,該bean僅在當前HTTP request內有效。
  • session : 每一次HTTP請求都會產生一個新的 bean,該bean僅在當前 HTTP session 內有效。
  • global-session: 全域性session作用域,僅僅在基於portlet的web應用中才有意義,Spring5已經沒有了。Portlet是能夠生成語義程式碼(例如:HTML)片段的小型Java Web外掛。它們基於portlet容器,可以像servlet一樣處理HTTP請求。但是,與 servlet 不同,每個 portlet 都有不同的會話

Spring 中的單例 bean 的執行緒安全問題了解嗎?

大部分時候我們並沒有在系統中使用多執行緒,所以很少有人會關注這個問題。單例 bean 存線上程問題,主要是因為當多個執行緒操作同一個物件的時候,對這個物件的非靜態成員變數的寫操作會存線上程安全問題。

常見的有兩種解決辦法:

  1. 在Bean物件中儘量避免定義可變的成員變數(不太現實)。

  2. 在類中定義一個ThreadLocal成員變數,將需要的可變成員變數儲存在 ThreadLocal 中(推薦的一種方式)。

Spring 中的 bean 生命週期?

這部分網上有很多文章都講到了,下面的內容整理自:https://yemengying.com/2016/07/14/spring-bean-life-cycle/,除了這篇文章,再推薦一篇很不錯的文章 :https://www.cnblogs.com/zrtqsk/p/3735273.html

  • Bean 容器找到配置檔案中 Spring Bean 的定義。
  • Bean 容器利用 Java Reflection API 建立一個Bean的例項。
  • 如果涉及到一些屬性值 利用set()方法設定一些屬性值。
  • 如果 Bean 實現了BeanNameAware介面,呼叫setBeanName()方法,傳入Bean的名字。
  • 如果 Bean 實現了BeanClassLoaderAware介面,呼叫setBeanClassLoader()方法,傳入ClassLoader物件的例項。
  • 如果Bean實現了BeanFactoryAware介面,呼叫setBeanClassLoader()方法,傳入ClassLoader物件的例項。
  • 與上面的類似,如果實現了其他*.Aware介面,就呼叫相應的方法。
  • 如果有和載入這個 Bean 的 Spring 容器相關的BeanPostProcessor物件,執行postProcessBeforeInitialization()方法
  • 如果Bean實現了InitializingBean介面,執行afterPropertiesSet()方法。
  • 如果 Bean 在配置檔案中的定義包含 init-method 屬性,執行指定的方法。
  • 如果有和載入這個 Bean的 Spring 容器相關的BeanPostProcessor物件,執行postProcessAfterInitialization()方法
  • 當要銷燬 Bean 的時候,如果 Bean 實現了DisposableBean介面,執行destroy()方法。
  • 當要銷燬 Bean 的時候,如果 Bean 在配置檔案中的定義包含 destroy-method 屬性,執行指定的方法。

圖示:

與之比較類似的中文版本:

說說自己對於 Spring MVC 瞭解?

談到這個問題,我們不得不提提之前 Model1 和 Model2 這兩個沒有 Spring MVC 的時代。

  • Model1 時代: 很多學 Java 後端比較晚的朋友可能並沒有接觸過 Model1 模式下的 JavaWeb 應用開發。在 Model1 模式下,整個 Web 應用幾乎全部用 JSP 頁面組成,只用少量的 JavaBean 來處理資料庫連線、訪問等操作。這個模式下 JSP 即是控制層又是表現層。顯而易見,這種模式存在很多問題。比如①將控制邏輯和表現邏輯混雜在一起,導致程式碼重用率極低;②前端和後端相互依賴,難以進行測試並且開發效率極低;
  • Model2 時代:學過 Servlet 並做過相關 Demo 的朋友應該瞭解“Java Bean(Model)+ JSP(View,)+Servlet(Controller) ”這種開發模式,這就是早期的 JavaWeb MVC 開發模式。Model:系統涉及的資料,也就是 dao 和 bean。View:展示模型中的資料,只是用來展示。Controller:處理使用者請求都發送給 ,返回資料給 JSP 並展示給使用者。

Model2 模式下還存在很多問題,Model2的抽象和封裝程度還遠遠不夠,使用Model2進行開發時不可避免地會重複造輪子,這就大大降低了程式的可維護性和複用性。於是很多JavaWeb開發相關的 MVC 框架營運而生比如Struts2,但是 Struts2 比較笨重。隨著 Spring 輕量級開發框架的流行,Spring 生態圈出現了 Spring MVC 框架, Spring MVC 是當前最優秀的 MVC 框架。相比於 Struts2 , Spring MVC 使用更加簡單和方便,開發效率更高,並且 Spring MVC 執行速度更快。

MVC 是一種設計模式,Spring MVC 是一款很優秀的 MVC 框架。Spring MVC 可以幫助我們進行更簡潔的Web層的開發,並且它天生與 Spring 框架整合。Spring MVC 下我們一般把後端專案分為 Service層(處理業務)、Dao層(資料庫操作)、Entity層(實體類)、Controller層(控制層,返回資料給前臺頁面)。

Spring MVC 的簡單原理圖如下:

SpringMVC 工作原理了解嗎?

原理如下圖所示:

上圖的一個筆誤的小問題:Spring MVC 的入口函式也就是前端控制器DispatcherServlet的作用是接收請求,響應結果。

流程說明(重要):

  1. 客戶端(瀏覽器)傳送請求,直接請求到DispatcherServlet
  2. DispatcherServlet根據請求資訊呼叫HandlerMapping,解析請求對應的Handler
  3. 解析到對應的Handler(也就是我們平常說的Controller控制器)後,開始由HandlerAdapter介面卡處理。
  4. HandlerAdapter會根據Handler來呼叫真正的處理器開處理請求,並處理相應的業務邏輯。
  5. 處理器處理完業務後,會返回一個ModelAndView物件,Model是返回的資料物件,View是個邏輯上的View
  6. ViewResolver會根據邏輯View查詢實際的View
  7. DispaterServlet把返回的Model傳給View(檢視渲染)。
  8. View返回給請求者(瀏覽器)

Spring 框架中用到了哪些設計模式?

關於下面一些設計模式的詳細介紹,可以看筆主前段時間的原創文章《面試官:“談談Spring中都用到了那些設計模式?”。》

  • 工廠設計模式: Spring使用工廠模式通過BeanFactoryApplicationContext建立 bean 物件。
  • 代理設計模式: Spring AOP 功能的實現。
  • 單例設計模式: Spring 中的 Bean 預設都是單例的。
  • 模板方法模式: Spring 中jdbcTemplatehibernateTemplate等以 Template 結尾的對資料庫操作的類,它們就使用到了模板模式。
  • 包裝器設計模式: 我們的專案需要連線多個數據庫,而且不同的客戶在每次訪問中根據需要會去訪問不同的資料庫。這種模式讓我們可以根據客戶的需求能夠動態切換不同的資料來源。
  • 觀察者模式:Spring 事件驅動模型就是觀察者模式很經典的一個應用。
  • 介面卡模式:Spring AOP 的增強或通知(Advice)使用到了介面卡模式、spring MVC 中也是用到了介面卡模式適配Controller

@Component 和 @Bean 的區別是什麼?

  1. 作用物件不同:@Component註解作用於類,而@Bean註解作用於方法。
  2. @Component通常是通過類路徑掃描來自動偵測以及自動裝配到Spring容器中(我們可以使用@ComponentScan註解定義要掃描的路徑從中找出標識了需要裝配的類自動裝配到 Spring 的 bean 容器中)。@Bean註解通常是我們在標有該註解的方法中定義產生這個 bean,@Bean告訴了Spring這是某個類的示例,當我需要用它的時候還給我。
  3. @Bean註解比Component註解的自定義性更強,而且很多地方我們只能通過@Bean註解來註冊bean。比如當我們引用第三方庫中的類需要裝配到Spring容器時,則只能通過@Bean來實現。

@Bean註解使用示例:

@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

上面的程式碼相當於下面的 xml 配置

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
  • 1
  • 2
  • 3

下面這個例子是通過@Component無法實現的。

@Bean
public OneService getService(status) {
    case (status)  {
        when 1:
                return new serviceImpl1();
        when 2:
                return new serviceImpl2();
        when 3:
                return new serviceImpl3();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

將一個類宣告為Spring的 bean 的註解有哪些?

我們一般使用@Autowired註解自動裝配 bean,要想把類標識成可用於@Autowired註解自動裝配的 bean 的類,採用以下註解可實現:

  • @Component:通用的註解,可標註任意類為Spring元件。如果一個Bean不知道屬於拿個層,可以使用@Component註解標註。
  • @Repository: 對應持久層即 Dao 層,主要用於資料庫相關操作。
  • @Service: 對應服務層,主要涉及一些複雜的邏輯,需要用到 Dao層。
  • @Controller: 對應 Spring MVC 控制層,主要使用者接受使用者請求並呼叫 Service 層返回資料給前端頁面。

Spring 管理事務的方式有幾種?

  1. 程式設計式事務,在程式碼中硬編碼。(不推薦使用)
  2. 宣告式事務,在配置檔案中配置(推薦使用)

宣告式事務又分為兩種:

  1. 基於XML的宣告式事務
  2. 基於註解的宣告式事務

Spring 事務中的隔離級別有哪幾種?

TransactionDefinition 介面中定義了五個表示隔離級別的常量:

  • TransactionDefinition.ISOLATION_DEFAULT:使用後端資料庫預設的隔離級別,Mysql 預設採用的 REPEATABLE_READ隔離級別 Oracle 預設採用的 READ_COMMITTED隔離級別.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:最低的隔離級別,允許讀取尚未提交的資料變更,可能會導致髒讀、幻讀或不可重複讀
  • TransactionDefinition.ISOLATION_READ_COMMITTED:允許讀取併發事務已經提交的資料,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:對同一欄位的多次讀取結果都是一致的,除非資料是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程式的效能。通常情況下也不會用到該級別。

Spring 事務中哪幾種事務傳播行為?

支援當前事務的情況:

  • TransactionDefinition.PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續執行。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則丟擲異常。(mandatory:強制性)

不支援當前事務的情況:

  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:建立一個新的事務,如果當前存在事務,則把當前事務掛起。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式執行,如果當前存在事務,則把當前事務掛起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則丟擲異常。

其他情況:

  • TransactionDefinition.PROPAGATION_NESTED:如果當前存在事務,則建立一個事務作為當前事務的巢狀事務來執行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。