面試專欄之spring

原文鏈接:https://www.cnblogs.com/cxhfuujust/p/10883197.html

目錄

1、什麼是 Spring Framework?

2、Spring Framework 中有多少個模塊,它們分別是什麼?

3、使用 Spring 框架能帶來哪些好處?

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

5、什麼是 Spring IoC 容器?

6、什麼是依賴注入?

7、IoC 和 DI 有什麼區別?

8、可以通過多少種方式完成依賴注入?

9、Spring 中有多少種 IoC 容器?

10、請介紹下常用的 BeanFactory 容器?

11、請介紹下常用的 ApplicationContext 容器?

12、列舉一些 IoC 的一些好處?

13、簡述 Spring IoC 的實現機制?

14、Spring 框架中有哪些不同類型的事件?

15、什麼是 Spring Bean ?

16、Spring 有哪些配置方式

17、Spring 支持幾種 Bean Scope ?

18、Spring Bean 在容器的生命週期是什麼樣的?

19、什麼是 Spring 的內部 bean?

20、什麼是 Spring 裝配?

21、解釋什麼叫延遲加載?

22、Spring 框架中的單例 Bean 是線程安全的麼?

23、Spring Bean 怎麼解決循環依賴的問題?

24、什麼是基於註解的容器配置?

25、如何在 Spring 中啓動註解裝配?

26、@Component, @Controller, @Repository, @Service 有何區別?

27、@Required 註解有什麼用?

28、@Autowired 註解有什麼用?

29、@Qualifier 註解有什麼用?

30、Spring AOP

31、什麼是 AOP ?

32、什麼是 Aspect ?

33、什麼是 JoinPoint ?

34、什麼是 PointCut ?

35、關於 JoinPoint 和 PointCut 的區別

36、什麼是 Advice ?

37、什麼是 Target ?

38、AOP 有哪些實現方式?

39、Spring AOP and AspectJ AOP 有什麼區別?

40、什麼是編織(Weaving)?

41、Spring 如何使用 AOP 切面?

42、Spring Transaction

43、什麼是事務?

44、事務的特性指的是?

45、列舉 Spring 支持的事務管理類型?

46、Spring 事務如何和不同的數據持久層框架做集成?

47、爲什麼在 Spring 事務中不能切換數據源?

48、@Transactional 註解有哪些屬性?如何使用?

49、什麼是事務的隔離級別?分成哪些隔離級別?

50、什麼是事務的傳播級別?分成哪些傳播級別?

51、什麼是事務的超時屬性?

52、什麼是事務的只讀屬性?

53、什麼是事務的回滾規則?

54、簡單介紹 TransactionStatus ?

55、使用 Spring 事務有什麼優點?

56、Spring 支持哪些 ORM 框架?

57、在 Spring 框架中如何更有效地使用 JDBC ?

58、Spring 數據數據訪問層有哪些異常?

59、使用 Spring 訪問 Hibernate 的方法有哪些?


1、什麼是 Spring Framework?

Spring 是一個開源應用框架,旨在降低應用程序開發的複雜度。

  • 它是輕量級、鬆散耦合的。

它的輕量級主要是相對於 EJB 。隨着 Spring 的體系越來越龐大,大家被 Spring 的配置搞懵逼了,所以後來出了 Spring Boot 。

  • 它具有分層體系結構,允許用戶選擇組件,同時還爲 J2EE 應用程序開發提供了一個有凝聚力的框架。
  • 它可以集成其他框架,如 Spring MVC、Hibernate、MyBatis 等,所以又稱爲框架的框架( 粘合劑、腳手架 )。

2、Spring Framework 中有多少個模塊,它們分別是什麼?

  1. Spring 核心容器--對應圖中,Core Container 。

該層基本上是 Spring Framework 的核心。它包含以下模塊:

  • Spring Core
  • Spring Bean

           核心容器提供 Spring 框架的基本功能。核心容器的主要組件是 BeanFactory,它是工廠模式的實現。BeanFactory 使用控制反轉 (IOC)模式將應用程序的配置和依賴性規範與實際的應用程序代碼分開。

  • Spring Context

          Spring 上下文是一個配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企業服務,例如 JNDI、EJB、電子郵件、國際化、事件機制、校驗和調度功能。

  • SpEL (Spring Expression Language)

           Spring 表達式語言全稱爲 “Spring Expression Language”,縮寫爲 “SpEL” ,類似於 Struts2 中使用的 OGNL 表達式語言,能在運行時構建複雜表達式、存取對象圖屬性、對象方法調用等等,並且能與 Spring 功能完美整合,如能用來配置 Bean 定義。

 

  1. 數據訪問對應圖中,Data Access

           該層提供與數據庫交互的支持。它包含以下模塊:

  • JDBC (Java DataBase Connectivity)
  • Spring 對 JDBC 的封裝模塊,提供了對關係數據庫的訪問。

  • ORM (Object Relational Mapping)
  •         Spring ORM 模塊,提供了對 hibernate5 和 JPA 的集成。

                hibernate5 是一個 ORM 框架。

                JPA 是一個 Java 持久化 API 。

  •  OXM (Object XML Mappers)

                Spring 提供了一套類似 ORM 的映射機制,用來將 Java 對象和 XML 文件進行映射。這就是 Spring 的對象 XML 映射功能,有            時候也成爲 XML 的序列化和反序列化。

                 用的比較少,胖友瞭解下即可。

  • Transaction
  •         Spring 簡單而強大的事務管理功能,包括聲明式事務和編程式事務。

  •  

  1. Web該層提供了創建 Web 應用程序的支持。它包含以下模塊:
  • WebMVC

        MVC 框架是一個全功能的構建 Web 應用程序的 MVC 實現。通過策略接口,MVC 框架變成爲高度可配置的,MVC 容納了大量視圖技術,其中包括 JSP、Velocity、Tiles、iText 和 POI。

  • WebFlux

         基於 Reactive 庫的響應式的 Web 開發框架

        不瞭解的可以看看 《使用 Spring 5 的 WebFlux 開發反應式 Web 應用

  • WebSocket

          Spring 4.0 的一個最大更新是增加了對 Websocket 的支持。

          Websocket 提供了一個在 Web 應用中實現高效、雙向通訊,需考慮客戶端(瀏覽器)和服務端之間高頻和低延時消息交換的機制。

          一般的應用場景有:在線交易、網頁聊天、遊戲、協作、數據可視化等。

  • Portlet 已經廢棄

 

  1. AOP該層支持面向切面編程。它包含以下模塊:
  • AOP
  • 通過配置管理特性,Spring AOP 模塊直接將面向切面的編程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何對象支持 AOP。

    Spring AOP 模塊爲基於 Spring 的應用程序中的對象提供了事務管理服務。通過使用 Spring AOP,不用依賴 EJB 組件,就可以將聲明性事務管理集成到應用程序中。

  • Aspects:該模塊爲與 AspectJ 的集成提供支持。
  • Instrumentation
  • 該層爲類檢測和類加載器實現提供支持。

  •  

  1. 其它
  • JMS (Java Messaging Service)
  • 提供了一個 JMS 集成框架,簡化了 JMS API 的使用。

    不太瞭解 JMS ,可以看看 《JMS入門》 。

  • Test
  •    該模塊爲使用 JUnit 和 TestNG 進行測試提供支持。

  • Messaging

         該模塊爲 STOMP 提供支持。它還支持註解編程模型,該模型用於從 WebSocket 客戶端路由和處理 STOMP 消息。

    

3、使用 Spring 框架能帶來哪些好處?

下面列舉了一些使用 Spring 框架帶來的主要好處:

  • DI :Dependency Injection(DI) 方法,使得構造器和 JavaBean、properties 文件中的依賴關係一目瞭然。
  • 輕量級:與 EJB 容器相比較,IoC 容器更加趨向於輕量級。這樣一來 IoC 容器在有限的內存和 CPU 資源的情況下,進行應用程序的開發和發佈就變得十分有利。
  • 面向切面編程(AOP): Spring 支持面向切面編程,同時把應用的業務邏輯與系統的服務分離開來。
  • 集成主流框架:Spring 並沒有閉門造車,Spring 集成了已有的技術棧,比如 ORM 框架、Logging 日期框架、J2EE、Quartz 和 JDK Timer ,以及其他視圖技術。
  • 模塊化:Spring 框架是按照模塊的形式來組織的。由包和類的命名,就可以看出其所屬的模塊,開發者僅僅需要選用他們需要的模塊即可。
  • 便捷的測試:要 測試一項用Spring開發的應用程序 十分簡單,因爲測試相關的環境代碼都已經囊括在框架中了。更加簡單的是,利用 JavaBean 形式的 POJO 類,可以很方便的利用依賴注入來寫入測試數據。
  • Web 框架:Spring 的 Web 框架亦是一個精心設計的 Web MVC 框架,爲開發者們在 Web 框架的選擇上提供了一個除了主流框架比如 Struts 、過度設計的、不流行 Web 框架的以外的有力選項。
  • 事務管理:Spring 提供了一個便捷的事務管理接口,適用於小型的本地事物處理(比如在單 DB 的環境下)和複雜的共同事物處理(比如利用 JTA 的複雜 DB 環境)。
  • 異常處理:Spring 提供一個方便的 API ,將特定技術的異常(由JDBC, Hibernate, 或 JDO 拋出)轉化爲一致的、Unchecked 異常。

當然,Spring 代碼優點的同時,一定會帶來相應的缺點:

  • 每個框架都有的問題,調試階段不直觀,後期的 bug 對應階段,不容易判斷問題所在。要花一定的時間去理解它。
  • 把很多 JavaEE 的東西封裝了,在滿足快速開發高質量程序的同時,隱藏了實現細節。

    這就導致,很多工程師,離開了 Spring 之後,就不知道怎麼工作了。從 Java 工程師,變成了 Spring 工程師。對於有追求的我們,還是需要知其然,知其所以然。

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

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

  • 代理模式 — 在 AOP 和 remoting 中被用的比較多。
  • 單例模式 — 在 Spring 配置文件中定義的 Bean 默認爲單例模式。
  • 模板方法 — 用來解決代碼重複的問題。比如 RestTemplate、JmsTemplate、JdbcTemplate 。
  • 前端控制器 — Spring提供了 DispatcherServlet 來對請求進行分發。
  • 視圖幫助(View Helper) — Spring 提供了一系列的 JSP 標籤,高效宏來輔助將分散的代碼整合在視圖裏。
  • 依賴注入 — 貫穿於 BeanFactory / ApplicationContext 接口的核心理念。
  • 工廠模式 — BeanFactory 用來創建對象的實例。

當然,感興趣的胖友,覺得不過癮,可以看看幾篇文章:

 

5、什麼是 Spring IoC 容器?

注意,正確的拼寫是 IoC 。

Spring 框架的核心是 Spring IoC 容器。容器創建 Bean 對象,將它們裝配在一起,配置它們並管理它們的完整生命週期。

  • Spring 容器使用依賴注入來管理組成應用程序的 Bean 對象。
  • 容器通過讀取提供的配置元數據 Bean Definition 來接收對象進行實例化,配置和組裝的指令。
  • 該配置元數據 Bean Definition 可以通過 XML,Java 註解或 Java Config 代碼提供。

Spring IoC

6、什麼是依賴注入?

在依賴注入中,你不必主動、手動創建對象,但必須描述如何創建它們。

  • 你不是直接在代碼中將組件和服務連接在一起,而是描述配置文件中哪些組件需要哪些服務。
  • 然後,再由 IoC 容器將它們裝配在一起。

另外,依賴注入的英文縮寫是 Dependency Injection ,簡稱 DI 。

7、IoC 和 DI 有什麼區別?

下面引用知乎上的一個討論:《IoC 和DI 有什麼區別?》

IoC 是個更寬泛的概念,DI 是更具體的。引用鄭燁的一篇博客,引用鄭燁的一篇博客,我眼中的Spring

Dependency Injection
原來,它叫 IoC 。
Martin Flower 發話了是個框架都有 IoC ,這不足以新生容器反轉的“如何定位插件的具體實現”,於是,它有了個新名字,Dependency Injection 。

其實,它就是一種將調用者與被調用者分離的思想,Uncle Bob 管它叫DIP(Dependency Inversion Principle),並把它歸入OO設計原則。
同 Spring 相比,它更早進入我的大腦。一切都是那麼朦朧,直至 Spring 出現。

慢慢的,我知道了它還分爲三種:

  • Interface Injection(type 1)
  • Setter Injection(type 2)
  • Constructor Injection(type 3)。

Martin Flower那篇爲它更名的大作讓我心目關於它的一切趨於完整。

在 Spring 中,它是一切的基礎。Spring 的種種優勢隨之而來。
於我而言,它爲我帶來更多的是思維方式的轉變,恐怕以後我再也無法寫出那種一大塊的全功能程序了。

8、可以通過多少種方式完成依賴注入?

通常,依賴注入可以通過三種方式完成,即:

上面一個問題的三種方式的英文,下面是三種方式的中文。

  • 接口注入
  • 構造函數注入
  • setter 注入

目前,在 Spring Framework 中,僅使用構造函數 setter 注入這兩種方式。

那麼這兩種方式各有什麼優缺點呢?胖友可以簡單閱讀 《Spring兩種依賴注入方式的比較》,不用太較真。綜述來說:

構造函數注入 setter 注入
沒有部分注入 有部分注入
不會覆蓋 setter 屬性 會覆蓋 setter 屬性
任意修改都會創建一個新實例 任意修改不會創建一個新實例
適用於設置很多屬性 適用於設置少量屬性
  • 實際場景下,setting 注入使用的更多。

9、Spring 中有多少種 IoC 容器?

Spring 提供了兩種( 不是“個” ) IoC 容器,分別是 BeanFactory、ApplicationContext 。

BeanFactory

BeanFactory 在 spring-beans 項目提供。

BeanFactory ,就像一個包含 Bean 集合的工廠類。它會在客戶端要求時實例化 Bean 對象。

ApplicationContext

ApplicationContext 在 spring-context 項目提供。

ApplicationContext 接口擴展了 BeanFactory 接口,它在 BeanFactory 基礎上提供了一些額外的功能。內置如下功能:

  • MessageSource :管理 message ,實現國際化等功能。
  • ApplicationEventPublisher :事件發佈。
  • ResourcePatternResolver :多資源加載。
  • EnvironmentCapable :系統 Environment(profile + Properties)相關。
  • Lifecycle :管理生命週期。
  • Closable :關閉,釋放資源
  • InitializingBean:自定義初始化。
  • BeanNameAware:設置 beanName 的 Aware 接口。

另外,ApplicationContext 會自動初始化非懶加載的 Bean 對象們。

詳細的內容,感興趣的胖友,可以看看 《【死磕 Spring】—— ApplicationContext 相關接口架構分析》 一文。源碼之前無祕密。簡單總結下 BeanFactory 與 ApplicationContext 兩者的差異:

艿艿:可能很多胖友沒看過源碼,所以會比較難。

BeanFactory ApplicationContext
它使用懶加載 它使用即時加載
它使用語法顯式提供資源對象 它自己創建和管理資源對象
不支持國際化 支持國際化
不支持基於依賴的註解 支持基於依賴的註解

另外,BeanFactory 也被稱爲低級容器,而 ApplicationContext 被稱爲高級容器。

10、請介紹下常用的 BeanFactory 容器?

BeanFactory 最常用的是 XmlBeanFactory 。它可以根據 XML 文件中定義的內容,創建相應的 Bean。

11、請介紹下常用的 ApplicationContext 容器?

以下是三種較常見的 ApplicationContext 實現方式:

  • 1、ClassPathXmlApplicationContext :從 ClassPath 的 XML 配置文件中讀取上下文,並生成上下文定義。應用程序上下文從程序環境變量中取得。示例代碼如下:

    ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);
    
  • 2、FileSystemXmlApplicationContext :由文件系統中的XML配置文件讀取上下文。示例代碼如下:

    ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);
    
  • 3、XmlWebApplicationContext :由 Web 應用的XML文件讀取上下文。例如我們在 Spring MVC 使用的情況。

      4、當然,目前我們更多的是使用 Spring Boot 爲主,所以使用的是第四種 ApplicationContext 容器,ConfigServletWebServerApplicationContext 。

12、列舉一些 IoC 的一些好處?

  • 它將最小化應用程序中的代碼量。
  • 它以最小的影響和最少的侵入機制促進鬆耦合。
  • 它支持即時的實例化和延遲加載 Bean 對象。
  • 它將使您的應用程序易於測試,因爲它不需要單元測試用例中的任何單例或 JNDI 查找機制。

13、簡述 Spring IoC 的實現機制?

簡單來說,Spring 中的 IoC 的實現原理,就是工廠模式加反射機制。代碼如下:

interface Fruit {

     public abstract void eat();
     
}
class Apple implements Fruit {

    public void eat(){
        System.out.println("Apple");
    }
    
}
class Orange implements Fruit {
    public void eat(){
        System.out.println("Orange");
    }
}

class Factory {

    public static Fruit getInstance(String className) {
        Fruit f = null;
        try {
            f = (Fruit) Class.forName(className).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return f;
    }
    
}

class Client {

    public static void main(String[] args) {
        Fruit f = Factory.getInstance("io.github.dunwu.spring.Apple");
        if(f != null){
            f.eat();
        }
    }
    
}
  • Fruit 接口,有 Apple 和 Orange 兩個實現類。
  • Factory 工廠,通過反射機制,創建 className 對應的 Fruit 對象。
  • Client 通過 Factory 工廠,獲得對應的 Fruit 對象。
  • 實際情況下,Spring IoC 比這個複雜很多很多,例如單例 Bean 對象,Bean 的屬性注入,相互依賴的 Bean 的處理,以及等等。

在基友 《面試問爛的 Spring IoC 過程》 的文章中,把 Spring IoC 相關的內容,講的非常不錯。

14、Spring 框架中有哪些不同類型的事件?

Spring 的 ApplicationContext 提供了支持事件和代碼中監聽器的功能。

我們可以創建 Bean 用來監聽在 ApplicationContext 中發佈的事件。如果一個 Bean 實現了 ApplicationListener 接口,當一個ApplicationEvent 被髮布以後,Bean 會自動被通知。示例代碼如下:

public class AllApplicationEventListener implements ApplicationListener<ApplicationEvent> {  
    
    @Override  
    public void onApplicationEvent(ApplicationEvent applicationEvent) {  
        // process event  
    }
    
}

Spring 提供了以下五種標準的事件:

  1. 上下文更新事件(ContextRefreshedEvent):該事件會在ApplicationContext 被初始化或者更新時發佈。也可以在調用ConfigurableApplicationContext 接口中的 #refresh() 方法時被觸發。
  2. 上下文開始事件(ContextStartedEvent):當容器調用ConfigurableApplicationContext 的 #start() 方法開始/重新開始容器時觸發該事件。
  3. 上下文停止事件(ContextStoppedEvent):當容器調用 ConfigurableApplicationContext 的 #stop() 方法停止容器時觸發該事件。
  4. 上下文關閉事件(ContextClosedEvent):當ApplicationContext 被關閉時觸發該事件。容器被關閉時,其管理的所有單例 Bean 都被銷燬。
  5. 請求處理事件(RequestHandledEvent):在 We b應用中,當一個HTTP 請求(request)結束觸發該事件。

除了上面介紹的事件以外,還可以通過擴展 ApplicationEvent 類來開發自定義的事件。

① 示例自定義的事件的類,代碼如下:

public class CustomApplicationEvent extends ApplicationEvent{  

    public CustomApplicationEvent(Object source, final String msg) {  
        super(source);
    }  

}

② 爲了監聽這個事件,還需要創建一個監聽器。示例代碼如下:

public class CustomEventListener implements ApplicationListener<CustomApplicationEvent> {

    @Override  
    public void onApplicationEvent(CustomApplicationEvent applicationEvent) {  
        // handle event  
    }
    
}

③ 之後通過 ApplicationContext 接口的 #publishEvent(Object event) 方法,來發布自定義事件。示例代碼如下:

// 創建 CustomApplicationEvent 事件
CustomApplicationEvent customEvent = new CustomApplicationEvent(applicationContext, "Test message");
// 發佈事件
applicationContext.publishEvent(customEvent);

15、什麼是 Spring Bean ?

  • Bean 由 Spring IoC 容器實例化,配置,裝配和管理。
  • Bean 是基於用戶提供給 IoC 容器的配置元數據 Bean Definition 創建。

這個問題,可以在回過頭看 「什麼是 Spring IoC 容器?」 問題,相互對照。

16、Spring 有哪些配置方式

單純從 Spring Framework 提供的方式,一共有三種:

  • 1、XML 配置文件。

    Bean 所需的依賴項和服務在 XML 格式的配置文件中指定。這些配置文件通常包含許多 bean 定義和特定於應用程序的配置選項。它們通常以 bean 標籤開頭。例如:

    <bean id="studentBean" class="org.edureka.firstSpring.StudentBean">
        <property name="name" value="Edureka"></property>
    </bean>
    
  • 2、註解配置。

    您可以通過在相關的類,方法或字段聲明上使用註解,將 Bean 配置爲組件類本身,而不是使用 XML 來描述 Bean 裝配。默認情況下,Spring 容器中未打開註解裝配。因此,您需要在使用它之前在 Spring 配置文件中啓用它。例如:

    <beans>
    <context:annotation-config/>
    <!-- bean definitions go here -->
    </beans>
    
  • 3、Java Config 配置。

    Spring 的 Java 配置是通過使用 @Bean 和 @Configuration 來實現。

    • @Bean 註解扮演與 <bean /> 元素相同的角色。
    • @Configuration 類允許通過簡單地調用同一個類中的其他 @Bean 方法來定義 Bean 間依賴關係。
    • 例如:

      @Configuration
      public class StudentConfig {
          
          @Bean
          public StudentBean myStudent() {
              return new StudentBean();
          }
          
      }
      

目前主要使用 Java Config 配置爲主。當然,三種配置方式是可以混合使用的。例如說:

  • Dubbo 服務的配置,艿艿喜歡使用 XML 。
  • Spring MVC 請求的配置,艿艿喜歡使用 @RequestMapping 註解。
  • Spring MVC 攔截器的配置,艿艿喜歡 Java Config 配置。

另外,現在已經是 Spring Boot 的天下,所以更加是 Java Config 配置爲主。

17、Spring 支持幾種 Bean Scope ?

艿艿,這個是一個比較小衆的題目,簡單瞭解即可。

Spring Bean 支持 5 種 Scope ,分別如下:

  • Singleton - 每個 Spring IoC 容器僅有一個單 Bean 實例。默認
  • Prototype - 每次請求都會產生一個新的實例。
  • Request - 每一次 HTTP 請求都會產生一個新的 Bean 實例,並且該 Bean 僅在當前 HTTP 請求內有效。
  • Session - 每一個的 Session 都會產生一個新的 Bean 實例,同時該 Bean 僅在當前 HTTP Session 內有效。
  • Application - 每一個 Web Application 都會產生一個新的 Bean ,同時該 Bean 僅在當前 Web Application 內有效。

另外,網絡上很多文章說有 Global-session 級別,它是 Portlet 模塊獨有,目前已經廢棄,在 Spring5 中是找不到的。

僅當用戶使用支持 Web 的 ApplicationContext 時,最後三個纔可用。

再補充一點,開發者是可以自定義 Bean Scope ,具體可參見 《Spring(10)—— Bean 作用範圍(二)—— 自定義 Scope》 。

不錯呢,還是那句話,這個題目簡單瞭解下即可,實際常用的只有 Singleton 和 Prototype 兩種級別,甚至說,只有 Singleton 級別。😈

18、Spring Bean 在容器的生命週期是什麼樣的?

艿艿說:這是一個比較高級的 Spring 的面試題,非常常見,並且答對比較加分。當然,如果實際真正弄懂,需要對 Spring Bean 的源碼,有比較好的理解,所以 《精盡 Spring 源碼》 系列,該讀還是讀吧。

艿艿:要注意下面每段話,艿艿進行加粗的地方。

Spring Bean 的初始化流程如下:

  • 實例化 Bean 對象

    • Spring 容器根據配置中的 Bean Definition(定義)中實例化 Bean 對象。

      Bean Definition 可以通過 XML,Java 註解或 Java Config 代碼提供。

    • Spring 使用依賴注入填充所有屬性,如 Bean 中所定義的配置。

  • Aware 相關的屬性,注入到 Bean 對象
    • 如果 Bean 實現 BeanNameAware 接口,則工廠通過傳遞 Bean 的 beanName 來調用 #setBeanName(String name) 方法。
    • 如果 Bean 實現 BeanFactoryAware 接口,工廠通過傳遞自身的實例來調用 #setBeanFactory(BeanFactory beanFactory) 方法。
  • 調用相應的方法,進一步初始化 Bean 對象
    • 如果存在與 Bean 關聯的任何 BeanPostProcessor 們,則調用 #preProcessBeforeInitialization(Object bean, String beanName) 方法。
    • 如果 Bean 實現 InitializingBean 接口,則會調用 #afterPropertiesSet() 方法。
    • 如果爲 Bean 指定了 init 方法(例如 <bean /> 的 init-method 屬性),那麼將調用該方法。
    • 如果存在與 Bean 關聯的任何 BeanPostProcessor 們,則將調用 #postProcessAfterInitialization(Object bean, String beanName) 方法。

Spring Bean 的銷燬流程如下:

  • 如果 Bean 實現 DisposableBean 接口,當 spring 容器關閉時,會調用 #destroy() 方法。
  • 如果爲 bean 指定了 destroy 方法(例如 <bean /> 的 destroy-method 屬性),那麼將調用該方法。

整體如下圖:流程圖

無意中,艿艿又翻到一張有趣的整體圖,如下圖:

流程圖

19、什麼是 Spring 的內部 bean?

只有將 Bean 僅用作另一個 Bean 的屬性時,才能將 Bean 聲明爲內部 Bean。

  • 爲了定義 Bean,Spring 提供基於 XML 的配置元數據在 <property>或 <constructor-arg> 中提供了 <bean>元素的使用。
  • 內部 Bean 總是匿名的,並且它們總是作爲原型 Prototype 。

例如,假設我們有一個 Student 類,其中引用了 Person 類。這裏我們將只創建一個 Person 類實例並在 Student 中使用它。示例代碼如下:

// Student.java

public class Student {

    private Person person;
    
    // ... Setters and Getters
}

// Person.java

public class Person {

    private String name;
    private String address;
    
    // ... Setters and Getters
}
<!-- bean.xml -->

<bean id=“StudentBean" class="com.edureka.Student">
    <property name="person">
        <!--This is inner bean -->
        <bean class="com.edureka.Person">
            <property name="name" value=“Scott"></property>
            <property name="address" value=“Bangalore"></property>
        </bean>
    </property>
</bean>

20、什麼是 Spring 裝配?

當 Bean 在 Spring 容器中組合在一起時,它被稱爲裝配或 Bean 裝配。Spring 容器需要知道需要什麼 Bean 以及容器應該如何使用依賴注入來將 Bean 綁定在一起,同時裝配 Bean 。

裝配,和上文提到的 DI 依賴注入,實際是一個東西。

自動裝配有哪些方式?

Spring 容器能夠自動裝配 Bean 。也就是說,可以通過檢查 BeanFactory 的內容讓 Spring 自動解析 Bean 的協作者。

自動裝配的不同模式:

  • no - 這是默認設置,表示沒有自動裝配。應使用顯式 Bean 引用進行裝配。
  • byName - 它根據 Bean 的名稱注入對象依賴項。它匹配並裝配其屬性與 XML 文件中由相同名稱定義的 Bean 。
  • 【最常用】byType - 它根據類型注入對象依賴項。如果屬性的類型與 XML 文件中的一個 Bean 類型匹配,則匹配並裝配屬性。
  • 構造函數 - 它通過調用類的構造函數來注入依賴項。它有大量的參數。
  • autodetect - 首先容器嘗試通過構造函數使用 autowire 裝配,如果不能,則嘗試通過 byType 自動裝配。

自動裝配有什麼侷限?

艿艿:這個題目,瞭解下即可,也不是很準確。

  • 覆蓋的可能性 - 您始終可以使用 <constructor-arg> 和 <property> 設置指定依賴項,這將覆蓋自動裝配。
  • 基本元數據類型 - 簡單屬性(如原數據類型,字符串和類)無法自動裝配。

    這種,嚴格來說,也不能稱爲侷限。因爲可以通過配置文件來解決。

  • 令人困惑的性質 - 總是喜歡使用明確的裝配,因爲自動裝配不太精確。

21、解釋什麼叫延遲加載?

默認情況下,容器啓動之後會將所有作用域爲單例的 Bean 都創建好,但是有的業務場景我們並不需要它提前都創建好。此時,我們可以在Bean 中設置 lzay-init = "true" 。

  • 這樣,當容器啓動之後,作用域爲單例的 Bean ,就不在創建。
  • 而是在獲得該 Bean 時,才真正在創建加載。

22、Spring 框架中的單例 Bean 是線程安全的麼?

Spring 框架並沒有對單例 Bean 進行任何多線程的封裝處理。

  • 關於單例 Bean 的線程安全和併發問題,需要開發者自行去搞定。
  • 並且,單例的線程安全問題,也不是 Spring 應該去關心的。Spring 應該做的是,提供根據配置,創建單例 Bean 或多例 Bean 的功能。

當然,但實際上,大部分的 Spring Bean 並沒有可變的狀態(比如Serview 類和 DAO 類),所以在某種程度上說 Spring 的單例 Bean 是線程安全的。

如果你的 Bean 有多種狀態的話,就需要自行保證線程安全。最淺顯的解決辦法,就是將多態 Bean 的作用域( Scope )由 Singleton 變更爲 Prototype 。

23、Spring Bean 怎麼解決循環依賴的問題?

這是個比較複雜的問題,建議看下 《【死磕 Spring】—— IoC 之加載 Bean:創建 Bean(五)之循環依賴處理》

 

24、什麼是基於註解的容器配置?

不使用 XML 來描述 Bean 裝配,開發人員通過在相關的類,方法或字段聲明上使用註解將配置移動到組件類本身。它可以作爲 XML 設置的替代方案。例如:

Spring 的 Java 配置是通過使用 @Bean 和 @Configuration 來實現。

  • @Bean 註解,扮演與 <bean /> 元素相同的角色。
  • @Configuration 註解的類,允許通過簡單地調用同一個類中的其他 @Bean 方法來定義 Bean 間依賴關係。

示例如下:

@Configuration
public class StudentConfig {

    @Bean
    public StudentBean myStudent() {
        return new StudentBean();
    }

}

25、如何在 Spring 中啓動註解裝配?

默認情況下,Spring 容器中未打開註解裝配。因此,要使用基於註解裝配,我們必須通過配置 <context:annotation-config /> 元素在 Spring 配置文件中啓用它。

當然,如果是使用 Spring Boot ,默認情況下已經開啓。

26、@Component, @Controller, @Repository, @Service 有何區別?

  • @Component :它將 Java 類標記爲 Bean 。它是任何 Spring 管理組件的通用構造型。
  • @Controller :它將一個類標記爲 Spring Web MVC 控制器。
  • @Service :此註解是組件註解的特化。它不會對 @Component 註解提供任何其他行爲。您可以在服務層類中使用 @Service 而不是 @Component ,因爲它以更好的方式指定了意圖。
  • @Repository :這個註解是具有類似用途和功能的 @Component 註解的特化。它爲 DAO 提供了額外的好處。它將 DAO 導入 IoC 容器,並使未經檢查的異常有資格轉換爲 Spring DataAccessException 。

27、@Required 註解有什麼用?

@Required 註解,應用於 Bean 屬性 setter 方法。

  • 此註解僅指示必須在配置時使用 Bean 定義中的顯式屬性值或使用自動裝配填充受影響的 Bean 屬性。
  • 如果尚未填充受影響的 Bean 屬性,則容器將拋出 BeanInitializationException 異常。

示例代碼如下:

public class Employee {

    private String name;

    @Required
    public void setName(String name){
        this.name=name;
    }

    public string getName(){
        return name;
    }

}

28、@Autowired 註解有什麼用?

@Autowired 註解,可以更準確地控制應該在何處以及如何進行自動裝配。

  • 此註解用於在 setter 方法,構造函數,具有任意名稱或多個參數的屬性或方法上自動裝配 Bean。
  • 默認情況下,它是類型驅動的注入。

示例代碼如下:

public class EmpAccount {
    
    @Autowired
    private Employee emp;
    
}

29、@Qualifier 註解有什麼用?

當你創建多個相同類型的 Bean ,並希望僅使用屬性裝配其中一個 Bean 時,您可以使用 @Qualifier 註解和 @Autowired 通過指定 ID 應該裝配哪個確切的 Bean 來消除歧義。

例如,應用中有兩個類型爲 Employee 的 Bean ID 爲 "emp1" 和 "emp2" ,此處,我們希望 EmployeeAccount Bean 注入 "emp1" 對應的 Bean 對象。代碼如下:

public class EmployeeAccount {

    @Autowired
    @Qualifier(emp1)
    private Employee emp;

}
 

30、Spring AOP

Spring AOP 的面試題中,大多數都是概念題,主要是對切面的理解。概念點主要有:

  • AOP
  • Aspect
  • JoinPoint
  • PointCut
  • Advice
  • Target
  • AOP Proxy
  • Weaving
  • 在閱讀完 「Spring AOP」 的面試題後,在回過頭思考下這些概念點,到底理解了多少。注意,不是背,理解!

非常推薦閱讀如下兩篇文章:

31、什麼是 AOP ?

AOP(Aspect-Oriented Programming),即面向切面編程, 它與 OOP( Object-Oriented Programming, 面向對象編程) 相輔相成, 提供了與 OOP 不同的抽象軟件結構的視角。

  • 在 OOP 中,以類( Class )作爲基本單元
  • 在 AOP 中,以切面( Aspect )作爲基本單元。

32、什麼是 Aspect ?

Aspect 由 PointCut 和 Advice 組成。

  • 它既包含了橫切邏輯的定義,也包括了連接點的定義。
  • Spring AOP 就是負責實施切面的框架,它將切面所定義的橫切邏輯編織到切面所指定的連接點中。

AOP 的工作重心在於如何將增強編織目標對象的連接點上, 這裏包含兩個工作:

  1. 如何通過 PointCut 和 Advice 定位到特定的 JoinPoint 上。
  2. 如何在 Advice 中編寫切面代碼。

可以簡單地認爲, 使用 @Aspect 註解的類就是切面

流程圖

33、什麼是 JoinPoint ?

JoinPoint ,切點,程序運行中的一些時間點, 例如:

  • 一個方法的執行。
  • 或者是一個異常的處理。

在 Spring AOP 中,JoinPoint 總是方法的執行點。

34、什麼是 PointCut ?

PointCut ,匹配 JoinPoint 的謂詞(a predicate that matches join points)。

簡單來說,PointCut 是匹配 JoinPoint 的條件。

  • Advice 是和特定的 PointCut 關聯的,並且在 PointCut 相匹配的 JoinPoint 中執行。即 Advice => PointCut => JoinPoint 。
  • 在 Spring 中, 所有的方法都可以認爲是 JoinPoint ,但是我們並不希望在所有的方法上都添加 Advice 。而 PointCut 的作用,就是提供一組規則(使用 AspectJ PointCut expression language 來描述) 來匹配 JoinPoint ,給滿足規則的 JoinPoint 添加 Advice 。

是不是覺得有點繞,實際場景下,其實也不會弄的這麼清楚~~

35、關於 JoinPoint 和 PointCut 的區別

JoinPoint 和 PointCut 本質上就是兩個不同緯度上的東西。

  • 在 Spring AOP 中,所有的方法執行都是 JoinPoint 。而 PointCut 是一個描述信息,它修飾的是 JoinPoint ,通過 PointCut ,我們就可以確定哪些 JoinPoint 可以被織入 Advice 。
  • Advice 是在 JoinPoint 上執行的,而 PointCut 規定了哪些 JoinPoint 可以執行哪些 Advice 。

或者,我們在換一種說法:

  1. 首先,Advice 通過 PointCut 查詢需要被織入的 JoinPoint 。
  2. 然後,Advice 在查詢到 JoinPoint 上執行邏輯。

36、什麼是 Advice ?

Advice ,通知。

  • 特定 JoinPoint 處的 Aspect 所採取的動作稱爲 Advice 。
  • Spring AOP 使用一個 Advice 作爲攔截器,在 JoinPoint “周圍”維護一系列的攔截器。

有哪些類型的 Advice?

  • Before - 這些類型的 Advice 在 JoinPoint 方法之前執行,並使用 @Before 註解標記進行配置。
  • After Returning - 這些類型的 Advice 在連接點方法正常執行後執行,並使用 @AfterReturning 註解標記進行配置。
  • After Throwing - 這些類型的 Advice 僅在 JoinPoint 方法通過拋出異常退出並使用 @AfterThrowing 註解標記配置時執行。
  • After Finally - 這些類型的 Advice 在連接點方法之後執行,無論方法退出是正常還是異常返回,並使用 @After 註解標記進行配置。
  • Around - 這些類型的 Advice 在連接點之前和之後執行,並使用 @Around 註解標記進行配置。

看起來,是不是和攔截器的執行時間,有幾分相似。實際上,用於攔截效果的各種實現,大體都是類似的。

37、什麼是 Target ?

Target ,織入 Advice 的目標對象。目標對象也被稱爲 Advised Object 。

  • 因爲 Spring AOP 使用運行時代理的方式來實現 Aspect ,因此 Advised Object 總是一個代理對象(Proxied Object) 。
  • 注意, Advised Object 指的不是原來的對象,而是織入 Advice 後所產生的代理對象。
  • Advice + Target Object = Advised Object = Proxy 。

38、AOP 有哪些實現方式?

實現 AOP 的技術,主要分爲兩大類:

  • ① 靜態代理 - 指使用 AOP 框架提供的命令進行編譯,從而在編譯階段就可生成 AOP 代理類,因此也稱爲編譯時增強。

    • 編譯時編織(特殊編譯器實現)
    • 類加載時編織(特殊的類加載器實現)。

      例如,SkyWalking 基於 Java Agent 機制,配置上 ByteBuddy 庫,實現類加載時編織時增強,從而實現鏈路追蹤的透明埋點。

      感興趣的,可以看看 《SkyWalking 源碼分析之 JavaAgent 工具 ByteBuddy 的應用》 。

  • ② 動態代理 - 在運行時在內存中“臨時”生成 AOP 動態代理類,因此也被稱爲運行時增強。目前 Spring 中使用了兩種動態代理庫:

    • JDK 動態代理
    • CGLIB

那麼 Spring 什麼時候使用 JDK 動態代理,什麼時候使用 CGLIB 呢?

// From 《Spring 源碼深度解析》P172
// Spring AOP 部分使用 JDK 動態代理或者 CGLIB 來爲目標對象創建代理。(建議儘量使用 JDK 的動態代理)
// 如果被代理的目標對象實現了至少一個接口,則會使用 JDK 動態代理。所有該目標類型實現的接口都將被代理。
// 若該目標對象沒有實現任何接口,則創建一個 CGLIB 代理。
// 如果你希望強制使用 CGLIB 代理,(例如希望代理目標對象的所有方法,而不只是實現自接口的方法)那也可以。但是需要考慮以下兩個方法:
//      1> 無法通知(advise) Final 方法,因爲它們不能被覆蓋。
//      2> 你需要將 CGLIB 二進制髮型包放在 classpath 下面。
// 爲什麼 Spring 默認使用 JDK 的動態代理呢?筆者猜測原因如下:
//      1> 使用 JDK 原生支持,減少三方依賴
//      2> JDK8 開始後,JDK 代理的性能差距 CGLIB 的性能不會太多。可參見:https://www.cnblogs.com/haiq/p/4304615.html

或者,我們來換一個解答答案:

Spring AOP 中的動態代理主要有兩種方式,

  • JDK 動態代理

    JDK 動態代理通過反射來接收被代理的類,並且要求被代理的類必須實現一個接口。JDK動態代理的核心是 InvocationHandler 接口和 Proxy 類。

  • CGLIB 動態代理

    如果目標類沒有實現接口,那麼 Spring AOP 會選擇使用 CGLIB 來動態代理目標類。當然,Spring 也支持配置,強制使用 CGLIB 動態代理。
    CGLIB(Code Generation Library),是一個代碼生成的類庫,可以在運行時動態的生成某個類的子類,注意,CGLIB 是通過繼承的方式做的動態代理,因此如果某個類被標記爲 final ,那麼它是無法使用 CGLIB 做動態代理的。

39、Spring AOP and AspectJ AOP 有什麼區別?

  • 代理方式不同
    • Spring AOP 基於動態代理方式實現。
    • AspectJ AOP 基於靜態代理方式實現。
  • PointCut 支持力度不同
    • Spring AOP 僅支持方法級別的 PointCut 。
    • AspectJ AOP 提供了完全的 AOP 支持,它還支持屬性級別的 PointCut 。

40、什麼是編織(Weaving)?

Weaving ,編織。

  • 爲了創建一個 Advice 對象而鏈接一個 Aspect 和其它應用類型或對象,稱爲編織(Weaving)。
  • 在 Spring AOP 中,編織在運行時執行,即動態代理。請參考下圖:Proxy

41、Spring 如何使用 AOP 切面?

在 Spring AOP 中,有兩種方式配置 AOP 切面:

  • 基於 XML 方式的切面實現。
  • 基於 註解 方式的切面實現。

目前,主流喜歡使用 註解 方式。可以看看 《徹底征服 Spring AOP 之實戰篇》 。

42、Spring Transaction

非常推薦閱讀如下文章:

43、什麼是事務?

事務就是對一系列的數據庫操作(比如插入多條數據)進行統一的提交或回滾操作,如果插入成功,那麼一起成功,如果中間有一條出現異常,那麼回滾之前的所有操作。

這樣可以防止出現髒數據,防止數據庫數據出現問題。

44、事務的特性指的是?

指的是 ACID ,如下圖所示:

事務的特性

  1. 原子性 Atomicity :一個事務(transaction)中的所有操作,或者全部完成,或者全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被恢復(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。即,事務不可分割、不可約簡。
  2. 一致性 Consistency :在事務開始之前和事務結束以後,數據庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設約束觸發器)、級聯回滾等。
  3. 隔離性 Isolation :數據庫允許多個併發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致數據的不一致。事務隔離分爲不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和串行化(Serializable)。
  4. 持久性 Durability :事務處理結束後,對數據的修改就是永久的,即便系統故障也不會丟失。

45、列舉 Spring 支持的事務管理類型?

目前 Spring 提供兩種類型的事務管理:

  • 聲明式事務:通過使用註解或基於 XML 的配置事務,從而事務管理與業務代碼分離。
  • 編程式事務:通過編碼的方式實現事務管理,需要在代碼中顯式的調用事務的獲得、提交、回滾。它爲您提供極大的靈活性,但維護起來非常困難。

實際場景下,我們一般使用 Spring Boot + 註解的聲明式事務。具體的示例,胖友可以看看 《Spring Boot 事務註解詳解》 。

另外,也推薦看看 《Spring 事務管理 - 編程式事務、聲明式事務》 一文。

46、Spring 事務如何和不同的數據持久層框架做集成?

① 首先,我們先明確下,這裏數據持久層框架,指的是 Spring JDBC、Hibernate、Spring JPA、MyBatis 等等。

② 然後,Spring 事務的管理,是通過 org.springframework.transaction.PlatformTransactionManager 進行管理,定義如下:

// PlatformTransactionManager.java
public interface PlatformTransactionManager {

    // 根據事務定義 TransactionDefinition ,獲得 TransactionStatus 。 
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

    // 根據情況,提交事務
    void commit(TransactionStatus status) throws TransactionException;

    // 根據情況,回滾事務
    void rollback(TransactionStatus status) throws TransactionException;

}
  • PlatformTransactionManager 是負責事務管理的接口,一共有三個接口方法,分別負責事務的獲得、提交、回滾。
  • #getTransaction(TransactionDefinition definition) 方法,根據事務定義 TransactionDefinition ,獲得 TransactionStatus 。
    • 爲什麼不是創建事務呢?因爲如果當前如果已經有事務,則不會進行創建,一般來說會跟當前線程進行綁定。如果不存在事務,則進行創建。
    • 爲什麼返回的是 TransactionStatus 對象?在 TransactionStatus 中,不僅僅包含事務屬性,還包含事務的其它信息,例如是否只讀、是否爲新創建的事務等等。下面,也會詳細解析 TransactionStatus 。
    • 事務 TransactionDefinition 是什麼?下面,也會詳細解析 TransactionStatus 。
  • #commit(TransactionStatus status) 方法,根據 TransactionStatus 情況,提交事務。
    • 爲什麼根據 TransactionStatus 情況,進行提交?例如說,帶@Transactional 註解的的 A 方法,會調用 @Transactional 註解的的 B 方法。
      • 在 B 方法結束調用後,會執行 PlatformTransactionManager#commit(TransactionStatus status) 方法,此處事務是不能、也不會提交的。
      • 而是在 A 方法結束調用後,執行 PlatformTransactionManager#commit(TransactionStatus status) 方法,提交事務。
  • #rollback(TransactionStatus status) 方法,根據 TransactionStatus 情況,回滾事務。
    • 爲什麼根據 TransactionStatus 情況,進行回滾?原因同 #commit(TransactionStatus status) 方法。

③ 再之後,PlatformTransactionManager 有抽象子類 org.springframework.transaction.support.AbstractPlatformTransactionManager ,基於 模板方法模式 ,實現事務整體邏輯的骨架,而抽象 #doCommit(DefaultTransactionStatus status)#doRollback(DefaultTransactionStatus status) 等等方法,交由子類類來實現。

前方高能,即將進入關鍵的 ④ 步驟。

④ 最後,不同的數據持久層框架,會有其對應的 PlatformTransactionManager 實現類,如下圖所示:事務的特性

  • 所有的實現類,都基於 AbstractPlatformTransactionManager 這個骨架類。
  • HibernateTransactionManager ,和 Hibernate5 的事務管理做集成。
  • DataSourceTransactionManager ,和 JDBC 的事務管理做集成。所以,它也適用於 MyBatis、Spring JDBC 等等。
  • JpaTransactionManager ,和 JPA 的事務管理做集成。

如下,是一個比較常見的 XML 方式來配置的事務管理器,使用的是 DataSourceTransactionManager 。代碼如下:

<!-- 事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 數據源 -->
    <property name="dataSource" ref="dataSource" />
</bean>
  • 正如上文所說,它適用於 MyBatis、Spring JDBC 等等。

 是不是很有趣,更多詳細的解析,可見如下幾篇文章:

47、爲什麼在 Spring 事務中不能切換數據源?

做過 Spring 多數據源的胖友,都會有個慘痛的經歷,爲什麼在開啓事務的 Service 層的方法中,無法切換數據源呢?因爲,在 Spring 的事務管理中,所使用的數據庫連接會和當前線程所綁定,即使我們設置了另外一個數據源,使用的還是當前的數據源連接。

另外,多個數據源且需要事務的場景,本身會帶來多事務一致性的問題,暫時沒有特別好的解決方案。

所以一般一個應用,推薦除非了讀寫分離所帶來的多數據源,其它情況下,建議只有一個數據源。並且,隨着微服務日益身形,一個服務對應一個 DB 是比較常見的架構選擇。

48、@Transactional 註解有哪些屬性?如何使用?

@Transactional 註解的屬性如下:

屬性 類型 描述
value String 可選的限定描述符,指定使用的事務管理器
propagation enum: Propagation 可選的事務傳播行爲設置
isolation enum: Isolation 可選的事務隔離級別設置
readOnly boolean 讀寫或只讀事務,默認讀寫
timeout int (in seconds granularity) 事務超時時間設置
rollbackFor Class對象數組,必須繼承自Throwable 導致事務回滾的異常類數組
rollbackForClassName 類名數組,必須繼承自Throwable 導致事務回滾的異常類名字數組
noRollbackFor Class對象數組,必須繼承自Throwable 不會導致事務回滾的異常類數組
noRollbackForClassName 類名數組,必須繼承自Throwable 不會導致事務回滾的異常類名字數組
  • 一般情況下,我們直接使用 @Transactional 的所有屬性默認值即可。

具體用法如下:

  • @Transactional 可以作用於接口、接口方法、類以及類方法上。當作用於類上時,該類的所有 public 方法將都具有該類型的事務屬性,同時,我們也可以在方法級別使用該標註來覆蓋類級別的定義。
  • 雖然 @Transactional 註解可以作用於接口、接口方法、類以及類方法上,但是 Spring 建議不要在接口或者接口方法上使用該註解,因爲這只有在使用基於接口的代理時它纔會生效。另外, @Transactional 註解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protectedprivate 或者默認可見性的方法上使用 @Transactional 註解,這將被忽略,也不會拋出任何異常。這一點,非常需要注意。

下面,我們來簡單說下源碼相關的東西。

@Transactional 註解的屬性,會解析成 org.springframework.transaction.TransactionDefinition 對象,即事務定義。TransactionDefinition 代碼如下:

public interface TransactionDefinition {
   int getPropagationBehavior(); // 事務的傳播行爲
   int getIsolationLevel(); // 事務的隔離級別
   int getTimeout(); // 事務的超時時間
   boolean isReadOnly(); // 事務是否只讀
   @Nullable
   String getName(); // 事務的名字
}
  • @Transactional 註解的 rollbackForrollbackForClassNamenoRollbackFornoRollbackForClassName 屬性貌似沒體現出來?它們體現在 TransactionDefinition 的實現類 RuleBasedTransactionAttribute 中。
  • #getPropagationBehavior() 方法,返回事務的傳播行爲,該值是個枚舉,在下面來說。
  • #getIsolationLevel() 方法,返回事務的隔離級別,該值是個枚舉,在下面來說。

49、什麼是事務的隔離級別?分成哪些隔離級別?

關於這個問題,涉及的內容會比較多,胖友直接看如下兩篇文章:

另外,有一點非常重要,不同數據庫對四個隔離級別的支持和實現略有不同。因爲我們目前互聯網主要使用 MySQL 爲主,所以至少要搞懂 MySQL 對隔離級別的支持和實現情況。

在 TransactionDefinition 接口中,定義了“四種”的隔離級別枚舉。代碼如下:

// TransactionDefinition.java

/**
 * 【Spring 獨有】使用後端數據庫默認的隔離級別
 *
 * MySQL 默認採用的 REPEATABLE_READ隔離級別
 * Oracle 默認採用的 READ_COMMITTED隔離級別
 */
int ISOLATION_DEFAULT = -1;

/**
 * 最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致髒讀、幻讀或不可重複讀
 */
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;

/**
 * 允許讀取併發事務已經提交的數據,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生
 */
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
/**
 * 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生。
 */
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
/**
 * 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。
 *
 * 但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。
 */
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

50、什麼是事務的傳播級別?分成哪些傳播級別?

事務的傳播行爲,指的是當前帶有事務配置的方法,需要怎麼處理事務。

  • 例如:方法可能繼續在現有事務中運行,也可能開啓一個新事務,並在自己的事務中運行。
  • 有一點需要注意,事務的傳播級別,並不是數據庫事務規範中的名詞,而是 Spring 自身所定義的。通過事務的傳播級別,Spring 才知道如何處理事務,是創建一個新事務呢,還是繼續使用當前的事務。

在 TransactionDefinition 接口中,定義了三類七種傳播級別。代碼如下:

// TransactionDefinition.java

// ========== 支持當前事務的情況 ========== 

/**
 * 如果當前存在事務,則使用該事務。
 * 如果當前沒有事務,則創建一個新的事務。
 */
int PROPAGATION_REQUIRED = 0;
/**
 * 如果當前存在事務,則使用該事務。
 * 如果當前沒有事務,則以非事務的方式繼續運行。
 */
int PROPAGATION_SUPPORTS = 1;
/**
 * 如果當前存在事務,則使用該事務。
 * 如果當前沒有事務,則拋出異常。
 */
int PROPAGATION_MANDATORY = 2;

// ========== 不支持當前事務的情況 ========== 

/**
 * 創建一個新的事務。
 * 如果當前存在事務,則把當前事務掛起。
 */
int PROPAGATION_REQUIRES_NEW = 3;
/**
 * 以非事務方式運行。
 * 如果當前存在事務,則把當前事務掛起。
 */
int PROPAGATION_NOT_SUPPORTED = 4;
/**
 * 以非事務方式運行。
 * 如果當前存在事務,則拋出異常。
 */
int PROPAGATION_NEVER = 5;

// ========== 其他情況 ========== 

/**
 * 如果當前存在事務,則創建一個事務作爲當前事務的嵌套事務來運行。
 * 如果當前沒有事務,則等價於 {@link TransactionDefinition#PROPAGATION_REQUIRED}
 */
int PROPAGATION_NESTED = 6;
  • 分類之後,其實還是比較好記的。當然,絕大數場景,我們只用 PROPAGATION_REQUIRED 傳播級別。
  • 這裏需要指出的是,前面的六種事務傳播行爲是 Spring 從 EJB 中引入的,他們共享相同的概念。而 PROPAGATION_NESTED 是 Spring 所特有的。
    • 以 PROPAGATION_NESTED 啓動的事務內嵌於外部事務中(如果存在外部事務的話),此時,內嵌事務並不是一個獨立的事務,它依賴於外部事務的存在,只有通過外部的事務提交,才能引起內部事務的提交,嵌套的子事務不能單獨提交。如果熟悉 JDBC 中的保存點(SavePoint)的概念,那嵌套事務就很容易理解了,其實嵌套的子事務就是保存點的一個應用,一個事務中可以包括多個保存點,每一個嵌套子事務。另外,外部事務的回滾也會導致嵌套子事務的回滾。
    •  當然,雖然上面 PROPAGATION_NESTED 文字很長,實際我們基本沒用過。或者說,去掉基本,我們根本沒用過。

51、什麼是事務的超時屬性?

所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。

在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。

當然,這個屬性,貌似我們基本也沒用過。

52、什麼是事務的只讀屬性?

事務的只讀屬性是指,對事務性資源進行只讀操作或者是讀寫操作。

  • 所謂事務性資源就是指那些被事務管理的資源,比如數據源、JMS 資源,以及自定義的事務性資源等等。
  • 如果確定只對事務性資源進行只讀操作,那麼我們可以將事務標誌爲只讀的,以提高事務處理的性能。感興趣的可以看看 《不使用事務和使用只讀事務的區別 》 。

在 TransactionDefinition 中以 boolean 類型來表示該事務是否只讀。

53、什麼是事務的回滾規則?

回滾規則,定義了哪些異常會導致事務回滾而哪些不會。

  • 默認情況下,事務只有遇到運行期異常時纔會回滾,而在遇到檢查型異常時不會回滾(這一行爲與EJB的回滾行爲是一致的)。
  • 但是你可以聲明事務在遇到特定的檢查型異常時像遇到運行期異常那樣回滾。同樣,你還可以聲明事務遇到特定的異常不回滾,即使這些異常是運行期異常。

注意,事務的回滾規則,並不是數據庫事務規範中的名詞,而是 Spring 自身所定義的。

54、簡單介紹 TransactionStatus ?

TransactionStatus 接口,記錄事務的狀態,不僅僅包含事務本身,還包含事務的其它信息。代碼如下:

// TransactionStatus.java
public interface TransactionStatus extends SavepointManager, Flushable {

    /**
    * 是否是新創建的事務
    */
    boolean isNewTransaction();

    /**
    * 是否有 Savepoint
    *
    * 在 {@link TransactionDefinition#PROPAGATION_NESTED} 傳播級別使用。
    */
    boolean hasSavepoint();

    /**
    * 設置爲只回滾
    */
    void setRollbackOnly();
    /**
    * 是否爲只回滾
    */
    boolean isRollbackOnly();

    /**
    * 執行 flush 操作
    */
    @Override
    void flush();

    /**
    * 是否事務已經完成
    */
    boolean isCompleted();

}
  • 爲什麼沒有事務對象呢?在 TransactionStatus 的實現類 DefaultTransactionStatus 中,有個 Object transaction 屬性,表示事務對象。
  • #isNewTransaction() 方法,表示是否是新創建的事務。有什麼用呢?答案結合 「Spring 事務如何和不同的數據持久層框架做集成?」 問題,我們對 #commit(TransactionStatus status) 方法的解釋。通過該方法,我們可以判斷,當前事務是否當前方法所創建的,只有創建事務的方法,才能且應該真正的提交事務。

55、使用 Spring 事務有什麼優點?

  1. 通過 PlatformTransactionManager ,爲不同的數據層持久框架提供統一的 API ,無需關心到底是原生 JDBC、Spring JDBC、JPA、Hibernate 還是 MyBatis 。
  2. 通過使用聲明式事務,使業務代碼和事務管理的邏輯分離,更加清晰。

56、Spring 支持哪些 ORM 框架?

  • Hibernate
  • JPA
  • MyBatis
  • JDO
  • OJB

可能會有胖友說,不是應該還有 Spring JDBC 嗎。注意,Spring JDBC 不是 ORM 框架。

57、在 Spring 框架中如何更有效地使用 JDBC ?

Spring 提供了 Spring JDBC 框架,方便我們使用 JDBC 。

對於開發者,只需要使用 JdbcTemplate 類,它提供了很多便利的方法解決諸如把數據庫數據轉變成基本數據類型或對象,執行寫好的或可調用的數據庫操作語句,提供自定義的數據錯誤處理。

沒有使用過的胖友,可以看看 《Spring JDBC 訪問關係型數據庫》 文章。

58、Spring 數據數據訪問層有哪些異常?

通過使用 Spring 數據數據訪問層,它統一了各個數據持久層框架的不同異常,統一進行提供 org.springframework.dao.DataAccessException 異常及其子類。如下圖所示:

流程圖

59、使用 Spring 訪問 Hibernate 的方法有哪些?

我們可以通過兩種方式使用 Spring 訪問 Hibernate:

  • 使用 Hibernate 模板和回調進行控制反轉。
  • 擴展 HibernateDAOSupport 並應用 AOP 攔截器節點。

具體可參考 《一起來學 SpringBoot 2.x | 第六篇:整合 Spring Data JPA》 。

當然,我們可以再來看一道 《JPA 規範與 ORM 框架之間的關係是怎樣的呢?》 。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章