《今天你面試了嗎》,靚仔一起面試去不嘍!

前言: 本文為《今天你面試了嗎》系列文章的第一篇,採用情景對話的方式還原面試場景,幫助大家梳理常用Java技術棧的知識點,如果喜歡本文章還請轉發鼓勵,如果反響良好。本號 會持續更新本系列文章,如有紕漏還請指出。

今天天氣不錯,我懷著自信的笑容來到某個大廠的研發中心,開啟面試的一天。首先我不是毫無準備的,什麼Java併發,多線程,jvm,分佈式,數據庫都準備的妥妥的,沒想到今天的面試的主題是Spring。不過還好,我也準備了... 門開了,走來一位拿著mac 本,戴眼鏡的年輕的小夥子,跟我差不多大吧。然後他示意我坐下,禮貌的說:“歡迎來我們公司面試,今天我們就聊聊 Spring 吧”

面試開始:

面試官:你說下什麼是Spring?

我:Spring 是一種輕量級開發框架,旨在提高開發人員的開發效率以及系統的可維護性。我們一般說的Spring框架指的是Spring Framework,它是很多模塊的集合,使用這些模塊可以很方便的協助我們開發。這些模塊是:核心容器、數據訪問/集成、Web、AOP(面向切面編程)、工具、消息和測試模塊。比如:Core Container中的Core組件是Spring所有組件的核心,Beans組件和Context組件是實現IOC和依賴注入的基礎,AOP組件用來實現面向切面編程。

面試官:使用Spring框架有什麼好處呢?

我:框架能更讓我們高效的編程以及更方便的維護我們的系統。

1. 輕量:Spring是輕量的,相對其他框架來說。

2. 控制反轉:Spring通過控制反轉實現了鬆散耦合,對象給出他們的依賴,而不是創建或查找依賴的對象們。

3. 面向切面編程(AOP):Spring支持面向切面編程,並且把業務邏輯和系統服務分開。

4. 容器:Spring包含並管理應用中對象的生命週期和配置。

5. MVC框架:Spring的WEB框架是個精心設計的框架,是WEB框架的一個很好的替代品。

6. 事務管理:Spring提供一個持續的事務管理接口,提供聲明式事務和編程式事務。

7. 異常處理:Spring提供方便的API把具體技術相關的異常轉化為一致的unchecked異常。

面試官:你第二點提到了Spring的控制反轉,能解釋下嗎?

我:首先來解釋下控制反轉。控制反轉(Inversion Of Control,縮寫為IOC)是一個重要的面向對象編程的法則來削減程序的耦合問題,也是spring框架的核心。

應用控制反轉,對象在被創建的時候,由一個調控系統內的所有對象的外界實體,將其所依賴的對象的引用,傳遞給它。

也可以說,依賴被注入到對象中。

所以,控制反轉是關於一個對象如何獲取他所依賴的對象的引用,這個責任的反轉。

另外,控制反轉一般分為兩種類型,依賴注入(Dependency Injection,簡稱DI)和依賴查找(Dependency Lookup)。

依賴注入應用比較廣泛。

還有幾個常見的問題:

1. 誰依賴誰-當然是應用程序依賴於IOC容器。

2. 為什麼需要依賴-應用程序需要IOC容器來提供對象需要的外部資源。

3. 誰注入誰-很明顯是IOC容器注入應用程序某個對象,應用程序依賴的對象

4. 注入了什麼-就是注入某個對象所需要的外部資源(包括對象、資源、常量數據)

面試官:那IOC與new對象有什麼區別嗎?

我:這就是正轉與反轉的區別。傳統應用程序是由我們自己在對象中主動控制去直接獲取依賴對象,也就是正轉。而反轉則是容器來幫助我們創建並注入依賴對象。

面試官:好的,那IOC有什麼優缺點嗎?

我:優點:很明顯,實現了組件之間的解耦,提高程序的靈活性和可維護性。缺點:對象生成因為是反射編程,在效率上有些損耗。但相對於IOC提高的維護性和靈活性來說,這點損耗是微不足道的,除非某對象的生成對效率要求特別高。

面試官:Spring管理這麼多對象,肯定需要一個容器吧。你能說下對IOC容器的理解嗎?

我:首先來解釋下容器:在每個框架中都有個容器的概念,所謂的容器就是將常用的服務封裝起來,然後用戶只需要遵循一定的規則就可以達到統一、靈活、安全、方便和快速的目的。

我:然後IOC容器是具有依賴注入功能的容器,負責實例化、定位、配置應用程序中的對象以及建立這些對象間的依賴。

面試官:那你能說下IOC容器是怎麼工作的嗎?

我:首先說下兩個概念。

1. Bean的概念:

Bean就是由Spring容器初始化、裝配及管理的對象,除此之外,bean就與應用程序中的其他對象沒什麼區別了。

2. 元數據BeanDefinition:

確定如何實例化Bean、管理bean之間的依賴關係以及管理bean,這就需要配置元數據,在spring中由BeanDefinition代表。

我:下面說下工作原理:

準備配置文件:配置文件中聲明Bean定義也就是為Bean配置元數據。

1. 由IOC容器進行解析元數據:

IOC容器的Bean Reader讀取並解析配置文件,根據定義生成BeanDefinition配置元數據對象,IOC容器根據BeanDefinition進行實例化、配置以及組裝Bean。

2. 實例化IOC容器:由客戶端實例化容器,獲取需要的Bean。

下面舉個例子:

<code>@Test  
public void testHelloWorld() {
 //1、讀取配置文件實例化一個IoC容器
 ApplicationContext context = new ClassPathXmlApplicationContext("helloworld.xml");
 //2、從容器中獲取Bean,注意此處完全“面向接口編程,而不是面向實現”

  HelloApi helloApi = context.getBean("hello", HelloApi.class);
  //3、執行業務邏輯
  helloApi.sayHello();
}/<code>

面試官:那你知道BeanFactory和ApplicationContext的區別嗎?

我:

1. BeanFactory是Spring中最基礎的接口。

它負責讀取讀取bean配置文檔,管理bean的加載,實例化,維護bean之間的依賴關係,負責bean的生命週期。

2. ApplicationContext是BeanFactory的子接口,除了提供上述BeanFactory的所有功能外,還提供了更完整的框架功能:如國際化支持,資源訪問,事件傳遞等。

常用的獲取ApplicationContext的方法:

2.1 FileSystemXmlApplicationContext:

從文件系統或者url指定的xml配置文件創建,參數為配置文件名或者文件名數組。

2.2 ClassPathXmlApplicationContext:

從classpath的xml配置文件創建,可以從jar包中讀取配置文件 2.3 WebApplicationContextUtils:

從web應用的根目錄讀取配置文件,需要先在web.xml中配置,可以配置監聽器或者servlet來實現。

1. ApplicationContext的初始化和BeanFactory有一個重大區別:

BeanFactory在初始化容器時,並未實例化Bean,知道第一次訪問某個Bean時才實例化Bean;

而ApplicationContext則在初始化應用上下文時就實例化所有的單例Bean,因此ApplicationContext的初始化時間會比BeanFactory稍長一些。更詳細的可以看看這篇文章:面試題:說說 BeanFactory 和 FactoryBean 的區別

面試官:很好,看來對Spring的IOC容器掌握的不錯。那我們來聊聊Spring的aop,你說下你對Spring aop的瞭解。

我:

Aop(面向切面編程)能夠將那些與業務無關,卻為業務模塊所共同調用的邏輯或責任(例如事務處理、日誌管理、權限控制等)封裝起來,便於減少系統的重複代碼,降低模塊間的耦合度,並有利於未來的擴展性和可維護性。

這裡聚個日誌處理的栗子:

《今天你面試了嗎》,靚仔一起面試去不嘍!

《今天你面試了嗎》,靚仔一起面試去不嘍!

面試官:那你知道Spring aop的原理嗎?

我:Spring aop就是基於動態代理的,如果要代理的對象實現了某個接口,那麼Spring aop會使用jdk proxy,去創建代理對象,而對於沒有實現接口的對象,就無法使用jdk的動態代理,這時Spring aop會使用cglib動態代理,這時候Spring aop會使用cglib生成一個被代理對象的子類作為代理。

關於動態代理的原理可以參考我的這篇文章:12 分鐘搞懂 靜(動)態代理

面試官:那你知道Spring Aop和AspecJ Aop有什麼區別嗎?

我:Spring AOP屬於運行時增強,而AspectJ是編譯時增強。Spring Aop基於代理,而AspectJ基於字節碼操作。Spring Aop已經集成了AspectJ,AspectJ應該算得上Java生態系統中最完整的AOP框架了。AspectJ相對於Spring Aop功能更加強大,但是Spring AOP相對來說更簡單。如果我們的切面比較少,那麼兩者性能差異不大。但是,當且切面太多的話,最好選擇AspectJ,它比Spring Aop快很多。

面試官:你對Spring中的bean瞭解嗎?都有哪些作用域?

我:Spring中的Bean有五種作用域:

1. singleton:

唯一Bean實例,Spring中的Bean默認都是單例的。

2. prototype:

每次請求都會創建一個新的bean實例。

3. request:

每次HTTP請求都會產生一個新的Bean,該Bean僅在當前HTTP request內有效。

4. session:

每次HTTP請產生一個新的Bean,該Bean僅在當前HTTP session內有效。

5. global-session:

全局session作用域,僅僅在基於portlet的web應用中才有意義,Spring5已經沒有了。

面試官:Spring中的單例Bean的線程安全問題了解嗎?

我:大部分時候我們並沒有在系統中使用多線程。

單例Bean存在線程安全問題,主要是因為當多個線程操作同一個對象的時候,對這個對象的非靜態成員變量的寫操作會存在線程安全問題。

常見的有兩種解決方法:

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

2. 在類中定義一個ThreadLocal成員變量,將需要的可變成員變量保存在Threadlocal中。

面試官:Spring中的Bean的生命週期你瞭解嗎?

我心想,這個過程還挺複雜的,還好來之前小本本記了。

Spring中的Bean從創建到銷燬大概會經過這些:

1. Bean容器找到配置文件中Spring Bean的定義。

2. Bean容器利用Java反射機制創建一個Bean的實例。

3. 如果涉及一些屬性值,利用set()方法設置一些屬性值。

4. 如果Bean實現了BeanNameAware接口,調用setBeanName()方法,傳入Bean的名稱。

5. 如果Bean實現了BeanClassLoaderAware接口,調用setBeanClassLoader()方法,傳入ClassLoader對象的實例。

6. 如果Bean實現了BeanFactoryAware接口,調用setBeanClassLoader()方法,傳入ClassLoader對象的實例。

7. 與上面類似,如果實現了其他*.Aware接口,就調用相應的方法。

8. 如果有和加載這個Bean的Spring容器相關的BeaPostProcessor對象,執行postProcessBeforeInitialization()方法

9. 如果Bean實現了InitializingBean接口,執行afterPropertiesSet()方法

10. 如果Bean在配置文件中的定義包含init-method屬性,執行指定的方法。

11. 如果有和加載這個 Bean的 Spring 容器相關的 BeanPostProcessor 對象,執行postProcessAfterInitialization() 方法

12. 當要銷燬Bean的時候,如果 Bean 實現了 DisposableBean 接口,執行 destroy() 方法。

13. 當要銷燬 Bean 的時候,如果 Bean 在配置文件中的定義包含 destroy-method 屬性,執行指定的方法。

《今天你面試了嗎》,靚仔一起面試去不嘍!

面試官:將一個類聲明為Spring的Bean的註解有哪些你知道嗎?

我:我們一般用@Autowried註解自動裝配Bean,要想把類識別為可用於自動裝配的Bean,採用以下註解可以實現:

@Component:通用的註解,可標註任意類為spring組件。

如果一個Bean不知道屬於哪個層,可以使用@Component註解標註

@Repository:對應持久層即Dao層,主要用於數據庫的操作。

@Service:對應服務層,主要涉及一些複雜的邏輯

@Controller:對應Spring MVC控制層,主要用於接收用戶請求並調用Service層返回數據給前端頁面。

面試官:那@Component和@Bean有什麼區別呢?

我:那我來總結下:

1. 作用對象不同:@Component作用於類,@Bean作用於方法。

2. @Component通常是通過類路徑掃描來自動偵測以及自動裝配到Spring容器中(使用@ComponentScan註解定義要掃描的路徑從中找出識別了需要裝配的類自動裝配到spring的Bean容器中)。

@Bean註解通常是在標有該註解的方法中定義產生這個bean,@Bean告訴Spring這是某個類的實例,當我需要用它的時候還給我。

3. @Bean註解比@Component註解的自定義性更強,而且很多地方只能通過@Bean註解來註冊Bean,比如第三方庫中的類。

面試官:看來你對Spring的bean掌握的不錯,那你能說下自己對於Spring MVC的瞭解嗎?

我:談到這個問題,不得不說下Model1和Model2這兩個沒有spring MVC的時代。

Model1時代:整個Web應用幾乎都是JSP頁面組成,只用少量的JavaBean來處理數據庫連接,訪問等操作。

這個模式下JSP既是控制層又是表現層。

顯而易見這種模式存在很多問題:

比如講控制層和表現層邏輯混雜在一起,導致代碼重用率極低;

前後端相互依賴,難以進行測試並且開發效率極低。

Model2時代:學過Servlet的朋友應該瞭解“Java Bean(Model)+JSP(VIEW)+Servlet(Controller)”這種開發模式就是早期的JavaWeb開發模式。

Model2模式下還存在很多問題,抽象和封裝程度還遠遠不夠,使用Model2進行開發時不可避免的會重複造輪子。

我想了想,接著說:

MVC是一種設計模式,Spring MVC是一款很優秀的MVC框架。Spring MVC可以幫助我們進行更簡潔的Web層的開發,並且它天生與Spring框架集成。

Spring MVC下我們一般把後端項目分為Service層(處理業務)、Dao層(數據庫操作)、Entity層(實體類)、Controller層(控制層、返回數據給前端)。

我畫個Spring MVC的簡單原理圖:

《今天你面試了嗎》,靚仔一起面試去不嘍!

面試官:你能詳細說下Spring MVC從接受請求到返回數據的整個流程嗎?

(我心想:幸好我還沒忘)我:可以。

這個流程雖然複雜,但是理解起來也不是很難。

1. 客戶端(瀏覽器)發送請求,直接請求到DispatcherServlet。

2. DispatcherServlet根據請求細膩調用HandlerMapping,解析請求對應的Handler。

3. 解析到對應的Handler(也就是Controller)後,開始由HandlerAdapter適配器處理。

4. HandlerAdapter會根據Handler來調用真正的處理器來處理請求,並處理相應的業務邏輯。

5. 處理器處理完業務後,會返回一個ModelAndView對象,Model是返回的數據對象,View是個邏輯上的View。

6. ViewResolver會根據View查找實際的View。

7. DispatcherServlet把返回的Model傳給View(視圖渲染)。

8. 把View返回給請求者(瀏覽器)。

面試官:你知道Spring框架中用到了哪些設計模式嗎?

我:那我來總結一下。

1. 工廠設計模式:Spring使用工廠模式通過BeanFactory、ApplicationContext創建Bean對象。

2. 代理設計模式:Spring AOP功能的實現。

3. 單例設計模式:Spring中的Bean默認都是單例的。

4. 模板方法模式:Spring中jdbcTemplate、hibernateTemplate等以Template結尾的對數據庫操作的類,就是用到了模板模式。

5. 包裝器設計模式:我們的項目需要鏈接多個數據庫,而且不同的客戶在每次訪問中根據需要會去訪問不同的數據庫。

這種模式讓我們可以根據客戶需求都太切換不同的數據源。

6. 觀察者模式:Spring事件驅動模型就是觀察者模式很經典的一個應用。

7. 適配器模式:Spring AOP的增強或通知使用到了適配器模式。

SpringMVC中也是用到了適配器模式適配Controller。

面試官:你使用過Spring的事務嗎?是怎麼用的?

我:當然用過。Spring管理事務有兩種方式:

1. 編程式事務:在代碼中硬編碼(不推薦使用)

2. 聲明式事務:在配置文件中配置,聲明式事務又分為兩種:

基於XML的方式和基於註解的方式(推薦使用) 在項目中使用Spring的事務只需要在你需要事務的方法上加上@Transaction註解,那麼這個方法就加上了事務,如果遇到異常,整個方法中的數據修改的邏輯都會被回滾掉,避免造成數據的不一致性。

面試官:那Spring的事務有哪幾種隔離級別?

我:TransactionDefinition接口中定義了五個隔離級別的常量:

1. ISOLATION_DEFAULT:

使用後端數據庫默認的隔離級別(一般用這個就好了),MySQL默認採用的是REPEATABLE_READ隔離級別,Oracle默認採用的是READ_COMMITTED隔離級別。

2. ISOLATION_READ_UNCOMMITTED:

最低的隔離級別,允許讀取尚未提交的數據,可能導致髒讀、幻讀或不可重複讀。

3. ISOLATION_READ_COMMITTED:

允許讀取併發事務以及提交的數據,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生。

4. ISOLATION_REPEATABLE_READ:

對同一字段的多次讀取結果都是一致的,除非數據是被事務自己修改的,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生。

5. ISOLATION_SERIALIZABLE:

最高的隔離級別,所有事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀和幻讀。

但是這將嚴重影響程序性能,通常也不會用到。

面試官:那你知道Spring事務有哪幾種事務傳播行為嗎?

(面試官露出神秘的一笑)

(我心想:Spring事務中這裡的坑踩的最多,怎麼會不清楚呢)

在TransactionDefinition中定義了7種事務傳播行為:

支持當前事務的情況

1. PROPAGATION_REQUIRED:

如果當前存在事務,則加入該事務;

如果當前沒有事務,則創建一個新的事務。

2. PROPAGATION_SUPPORTS:

如果當前存在事務,則加入該事務;

如果當前沒有事務,則以非事務的方式繼續運行。

3. PROPAGATION_MANDATORY:

如果當前存在事務,則加入該事務;

如果當前沒有事務,則拋出異常(mandatory:強制) 不支持當前事務的情況


4. PROPAGATION_REQUIRES_NEW:

創建一個新的事務,如果當前存在事務,則把當前事務掛起。

5. PROPAGATION_NOT_SUPPORTED:

以非事務的方式運行,如果當前存在事務,則把當前事務掛起。

6. PROPAGATION_NEVER:

以非事務的方式運行,如果當前存在事務,則拋出異常。

其他情況

7. PROPAGATION_NESTED:

如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;

如果當前沒有事務,則該取值等價於PROPAGATION_REQUIRED。

我:

一篇有價值的博客,列舉了Spring事務不生效的幾大原因

https://blog.csdn.net/f641385712/article/details/80445933。

這裡我列舉一下:

原因1:是否是數據庫引擎設置不對造成的。

比如我們常用的mysql,引擎MyISAM,是不支持事務操作的,需要改成InnoDB才能支持。

原因2:入口的方法必須是public,否則事務不起作用(這一點由Spring的AOP特性決定)private方法,final方法和static方法不能添加事務,加了也不生效。

原因3:Spring事務管理默認只對出現運行時異常(kava.lang.RuntimeException及其子類)進行回滾(至於Spring為什麼這麼設計:

因為Spring認為Checked異常屬於業務,程序員應該給出解決方案而不應該直接扔給框架)。

原因4:@EnableTransactionManagement // 啟註解事務管理,等同於xml配置方式的 <annotation-driven>。

沒有使用該註解開啟事務。

原因5:請確認你的類是否被代理了。

(因為Spring的事務實現原理是AOP,只有通過代理對象調用方法才能被攔截,事務才能生效)。

原因6:請確保你的業務和事務入口在同一個線程裡,否則事務也是不生效的,比如下面的代碼:

<code>@Transactional
@Override
public void save(User user1, User user2) {
    new Thread(() -> {
          saveError(user1, user2);
          System.out.println(1 / 0);
    }).start();
}/<code>

原因7:同一個類中一個無事務的方法調用另一個有事務的方法,事務是不會起作用的。

例如下面的代碼:

《今天你面試了嗎》,靚仔一起面試去不嘍!

面試官:

看來你對Spring框架的重點知識掌握的還不錯,基礎很紮實,今天我們就先聊到這裡,希望明天你能表現的更好。

我微笑著說:(暗自慶幸:通過了一關是一關)嗯,我會好好準備的。

https://juejin.im/post/5e6d993cf265da575b1bd4af


分享到:


相關文章: