《今天面試了嗎》-Spring

前言

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

面試環節

  • 面試官:你說下什麼是spring?java

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

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

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

  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對象有什麼區別嗎面試

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

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

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

  • 面試官:spring管理這麼多對象,確定須要一個容器吧。你能說下對IOC容器的理解嗎?編程

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

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

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

  • 我:首先說下兩個概念。

  1. Bean的概念:Bean就是由Spring容器初始化、裝配及管理的對象,除此以外,bean就與應用程序中的其餘對象沒什麼區別了。
  2. 元數據BeanDefinition:肯定如何實例化Bean、管理bean之間的依賴關係以及管理bean,這就須要配置元數據,在spring中由BeanDefinition表明。
  • 我:下面說下工做原理:
  1. 準備配置文件:配置文件中聲明Bean定義也就是爲Bean配置元數據。
  2. 由IOC容器進行解析元數據:IOC容器的Bean Reader讀取並解析配置文件,根據定義生成BeanDefinition配置元數據對象,IOC容器根據BeanDefinition進行實例化、配置以及組裝Bean。
  3. 實例化IOC容器:由客戶端實例化容器,獲取須要的Bean。
    下面舉個例子:
@Test  
       public void testHelloWorld() {  
             //一、讀取配置文件實例化一個IoC容器  
             ApplicationContext context = new ClassPathXmlApplicationContext("helloworld.xml");  
             //二、從容器中獲取Bean,注意此處徹底「面向接口編程,而不是面向實現」  
              HelloApi helloApi = context.getBean("hello", HelloApi.class);  
              //三、執行業務邏輯  
              helloApi.sayHello();  
       }
複製代碼
  • 面試官:那你知道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來實現。
  3. ApplicationContext的初始化和BeanFactory有一個重大區別:BeanFactory在初始化容器時,並未實例化Bean,知道第一次訪問某個Bean時才實例化Bean;而ApplicationContext則在初始化應用上下文時就實例化全部的單例Bean,所以ApplicationContext的初始化時間會比BeanFactory稍長一些。
  • 面試官:很好,看來對spring的IOC容器掌握的不錯。那咱們來聊聊spring的aop,你說下你對spring aop的瞭解。
  • 我:Aop(面向切面編程)可以將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任(例如事務處理、日誌管理、權限控制等)封裝起來,便於減小系統的重複代碼,下降模塊間的耦合度,並有利於將來的擴展性和可維護性。這裏聚個日誌處理的栗子:
日誌處理方式 實現方式 優缺點
硬代碼編寫
處理代碼相同,代碼強耦合
抽離方法,代碼複用
手動插入代碼,代碼強耦合
aop
橫向的功能抽離出來造成一個獨立的模塊,低耦合
  • 面試官:那你知道spring aop的原理嗎?

  • 我:spring aop就是基於動態代理的,若是要代理的對象實現了某個接口,那麼spring aop會使用jdk proxy,去建立代理對象,而對於沒有實現接口的對象,就沒法使用jdk的動態代理,這時spring aop會使用cglib動態代理,這時候spring aop會使用cglib生成一個被代理對象的子類做爲代理。

  • 我:關於動態代理的原理能夠參考個人這篇文章:juejin.im/post/5cea01…

  • 面試官:那你知道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,採用如下註解能夠實現:
  1. @Component:通用的註解,可標註任意類爲spring組件。若是一個Bean不知道屬於哪一個層,可使用@Component註解標註
  2. @Repository:對應持久層即Dao層,主要用於數據庫的操做。
  3. @Service:對應服務層,主要涉及一些複雜的邏輯
  4. @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的時代。
  1. Model1時代:整個Web應用幾乎都是JSP頁面組成,只用少許的JavaBean來處理數據庫鏈接,訪問等操做。這個模式下JSP既是控制層又是表現層。顯而易見這種模式存在不少問題:好比講控制層和表現層邏輯混雜在一塊兒,致使代碼重用率極低;先後端相互依賴,難以進行測試而且開發效率極低。
  2. 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. 緣由1:是不是數據庫引擎設置不對形成的。好比咱們經常使用的mysql,引擎MyISAM,是不支持事務操做的,須要改爲InnoDB才能支持。
  2. 緣由2:入口的方法必須是public,不然事務不起做用(這一點由Spring的AOP特性決定)private方法,final方法和static方法不能添加事務,加了也不生效。
  3. 緣由3:Spring事務管理默認只對出現運行時異常(kava.lang.RuntimeException及其子類)進行回滾(至於Spring爲何這麼設計:由於Spring認爲Checked異常屬於業務,程序員應該給出解決方案而不該該直接扔給框架)。
  4. 緣由4:@EnableTransactionManagement // 啓註解事務管理,等同於xml配置方式的 <tx:annotation-driven />。沒有使用該註解開啓事務。
  5. 緣由5:請確認你的類是否被代理了。(由於Spring的事務實現原理是AOP,只有經過代理對象調用方法才能被攔截,事務才能生效)。
  6. 緣由6:請確保你的業務和事務入口在同一個線程裏,不然事務也是不生效的,好比下面的代碼:
@Transactional
@Override
public void save(User user1, User user2) {
    new Thread(() -> {
          saveError(user1, user2);
          System.out.println(1 / 0);
    }).start();
}

複製代碼
  1. 緣由7:同一個類中一個無事務的方法調用另外一個有事務的方法,事務是不會起做用的。例以下面的代碼:

  • 面試官:看來你對spring框架的重點知識掌握的還不錯,基礎很紮實,今天咱們就先聊到這裏,但願明天你能表現的更好。
  • 我微笑着說:(暗自慶幸:經過了一關是一關)嗯,我會好好準備的。
相關文章
相關標籤/搜索