面試官:關於Spring就問這13個


1 Spring核心組件

一句話概括:Spring是一個輕量級、非入侵式的控制反轉(IoC)和麪向切面(AOP)的框架

Spring 版本 JDK版本
1.x 1.3:引入了動態代理機制,AOP 底層就是動態代理。
2.x 1.4:正常升級
3.x 5:引入註解,Spring 3 最低版本是 Java 5 ,從此以後不叫1.x 直接叫x
4.x 6:劃時代意義的版本,開始支持 Spring Boot 1.X
5.x 8:lambda 表達式等功能

PS :目前Java 開發的標配是 Spring5 + Spring Boot 2 + JDK 8

1.1 Spring 簡介

現如今的Java開發又簡稱爲Spring開發,Spring是Java目前第一大框架,它讓現有的技術更容易使用,促進良好的編程習慣,大大簡化應用程序的開發。

因爲你想啊,如果我們想實現某個功能,代碼量一般都是固定的,要麼全自己寫,要麼用已有的優秀框架,而Spring不僅已經給我們提供了各種優秀組件,還提供了良好的代碼組織邏輯跟業務開發流程規範框架,它的主要優點如下:

  1. IOCDI的支持

Spring就是一個大工廠容器,可以將所有對象創建和依賴關係維護,交給Spring管理,Spring工廠是用於生成Bean,並且管理Bean的生命週期,實現高內聚低耦合的設計理念。

  1. AOP編程的支持

Spring提供面向切面編程,可以方便的實現對程序進行權限攔截、運行監控等功能。

  1. 聲明式事務的支持

只需要通過配置就可以完成對事務的管理,而無需手動編程,以前重複的一些JDBC操作,統統不需我們再寫了。

  1. 方便程序的測試

Spring對Junit4提供支持,可以通過註解方便的測試Spring程序。

  1. 粘合劑功能

方便集成各種優秀框架,Spring不排斥各種優秀的開源框架,其內部提供了對各種優秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持。

  1. 降低 JavaEE API的使用難度

Spring 對 JavaEE 開發中非常難用的一些API(JDBC、JavaMail、遠程調用等)都提供了封裝,這些API的提供使得應用難度大大降低。

1.2 Spring組件

Spring框架是分模塊存在,除了最核心的Spring Core Container是必要模塊之外,其他模塊都是可選,大約有20多個模塊。

Spring框架 有很多特性,這些特性由7個定義良好的模塊構成。

  1. Spring Core:Spring核心,它是框架最基礎的部分,提供IOC和依賴注入DI特性。
  2. Spring Context:Spring上下文容器,它是 BeanFactory 功能加強的一個子接口。
  3. Spring Web:它提供Web應用開發的支持。
  4. Spring MVC:它針對Web應用中MVC思想的實現。
  5. Spring DAO:提供對JDBC抽象層,簡化了JDBC編碼,同時,編碼更具有健壯性。
  6. Spring ORM:它支持用於流行的ORM框架的整合,比如:Spring + Hibernate、Spring + iBatis、Spring + JDO的整合等。
  7. Spring AOP:即面向切面編程,它提供了與AOP聯盟兼容的編程實現。

2  IOC 跟 AOP

提到Spring永遠離不開的兩個話題就是 IOCAOP,這是Spring的兩大核心知識點,初學者不要被IOC、AOP、Aspect、Pointcut、Advisor這些術語嚇着了,這些術語都是無聊的人爲了發論文硬造的。

2.1 IOC

Java是個面向對象的編程語言,一般一個應用程序是由一組對象通過相互協作開發出的業務邏輯組成的,那麼如何管理這些對象呢?抽象工廠、工廠方法設計模式可以幫我們創建對象,生成器模式幫我們處理對象間的依賴關係,可是這些又需要我們創建另一些工廠類、生成器類,我們又要而外管理這些類,增加了我們的負擔。如果程序在對象需要的時候,就能自動管理對象的聲明週期,不用我們自己再去管理Bean的聲明週期了,這樣不就實現解耦了麼。

Spring提出了一種思想:由Spring來負責控制對象的生命週期和對象間的關係。所有的類都會在Spring容器中登記,告訴Spring這這個類是什麼,需要什麼,然後Spring會在系統運行到適當的時候,把你要的東西主動給你,同時也把你交給其他需要你的Bean。所有的類的創建、銷燬都由Spring來控制,也就是說控制對象生存週期的不再是引用它的對象,而是Spring。對於某個具體的對象而言,以前是它控制其他對象,現在是所有對象都被spring控制,所以這叫控制反轉(Inversion of Controller),也可以叫依賴注入 DI(Dependency Injection)。

知道大致思想後其實可以如果嘗試 自己實現IOC 的話就會發現核心就是 反射 + XML解析/註解解析

  1. 讀取 XML 獲取 bean 相關信息,類信息、屬性值信息。
  2. 通過 反射機制獲取到目標類的構造函數,調用構造函數,再給對象賦值。

如果想自己跟下源碼你會發現IOC的源碼入口是refresh(),裏面包含13個提供不同功能的函數,具體流程較複雜,公衆號回覆 IOC 獲得原圖。

2.2 Context

IOC 容器只是提供一個管理對象的空間而已,如何向容器中放入我們需要容器代爲管理的對象呢?這就涉及到Spring的應用上下文Context

應用上下文Context :

基於 Core 和 Beans,提供了大量的擴展,包括國際化操作(基於 JDK )、資源加載(基於 JDK properties)、數據校驗(Spring 自己封裝的數據校驗機制)、數據綁定(Spring 特有,HTTP 請求中的參數直接映射稱 POJO)、類型轉換,ApplicationContext 接口是 Context 的核心,可以理解爲Bean的上下文或背景信息

可以簡單的理解 應用上下文 是Spring容器的一種抽象化表述,而我們常見的 ApplicationContext 本質上就是一個維護Bean定義以及對象之間協作關係的高級接口。Spring 框架本身就提供了很多個容器的實現,大概分爲兩種類型:

  1. 一種是不常用的 BeanFactory,這是最簡單的容器,只能提供基本的 DI功能。
  2. 另外一種就是繼承了 BeanFactory後派生而來的應用上下文,其抽象接口也就是我們上面提到的的 ApplicationContext,它能提供更多企業級的服務,例如解析配置文本信息等等,這也是應用上下文實例對象最常見的應用場景。有了上下文對象,我們就能向容器註冊需要Spring管理的對象了。對於上下文抽象接口,Spring也爲我們提供了多種類型的容器實現,供我們在不同的應用場景選擇。
  1. AnnotationConfigApplicationContext:從一個或多個基於java的配置類中加載上下文定義,適用於java註解的方式。
  2. ClassPathXmlApplicationContext:從類路徑下的一個或多個xml配置文件中加載上下文定義,適用於xml配置的方式。
  3. FileSystemXmlApplicationContext:從文件系統下的一個或多個xml配置文件中加載上下文定義,也就是說系統盤符中加載xml配置文件。
  4. AnnotationConfigWebApplicationContext:專門爲web應用準備的,適用於註解方式。
  5. XmlWebApplicationContext:從web應用下的一個或多個xml配置文件加載上下文定義,適用於xml配置方式。

工作中通過XML配置或註解 將需要管理的Bean跟Bean之間的協作關係配置好,然後利用應用上下文對象Context加載進Spring容器,容器就能爲你的程序提供你想要的對象管理服務了。比如追蹤下 ClassPathXmlApplicationContext 的底層源碼:可以看到一個XML文件的解析就可以上延8層,可見Spring容器爲了實現IOC進行了全面性的考慮。

2.3 AOP

如果想編碼實現計算器功能,我們的目標是實現加減乘除的運算,可是如何在每種運算前後進行打印日誌跟數字合規的校驗呢。

  1. 日誌記錄數據校驗可重用的功能模塊分離出來,然後在程序的執行的合適的地方動態地植入這些代碼並執行。這樣就簡化了代碼的書寫。
  2. 業務邏輯代碼中沒有參和通用邏輯的代碼,業務模塊更簡潔,只包含核心業務代碼。實現了業務邏輯和通用邏輯的代碼分離,便於維護和升級,降低了業務邏輯和通用邏輯的耦合性。

思路:代碼最終是要加載到內存中實現new出對象,那麼如果我們把可重用的功能提取出來,然後將這些通用功能在內存中通過入的方式實現構造出一個新的目標對象不就OK了麼!

Spring AOP(Aspect Oriented Programming) 恰恰提供從另一個角度來考慮程序結構以完善面向對象編程,如果說依賴注入的目的是讓相互協作的組件保持一種較爲鬆散的耦合狀態的話,AOP則是將遍佈應用各處的功能分離出來形成可重用的組件。在編譯期間、裝載期間或運行期間實現在不修改源代碼的情況下給程序動態添加功能的一種技術。從而實現對業務邏輯的隔離,提高代碼的模塊化能力。

AOP 的核心其實就是動態代理,如果是實現了接口的話就會使用 JDK 動態代理,否則使用 CGLIB 代理,主要應用於處理一些具有橫切性質的系統級服務,如日誌收集、事務管理、安全檢查、緩存、對象池管理等。

Spring主要提供了 Aspect 切面、JoinPoint 連接點、PointCut 切入點、Advice 增強等實現方式,AOP一般有5種環繞方式:

  1. 前置通知 (@Before)
  2. 返回通知 (@AfterReturning)
  3. 異常通知 (@AfterThrowing)
  4. 後置通知 (@After)
  5. 環繞通知 (@Around) :(優先級最高)

PS :多個切面的情況下,可以通過@Order指定先後順序,數字越小,優先級越高。

3  JDK 動態代理和 CGLIB 代理區別

JDK 動態代理 與 CGLib動態代理均是實現Spring AOP的基礎,它們的實現方式有所不同。

3.1 JDK動態代理

特點

  1. Interface:對於JDK動態代理,業務類需要一個Interface。
  2. Proxy:Proxy類是動態產生的,這個類在調用 Proxy.newProxyInstance() 方法之後,產生一個Proxy類的實例。實際上,這個Proxy類也是存在的,不僅僅是類的實例,這個Proxy類可以保存在硬盤上。
  3. Method:對於業務委託類的每個方法,現在Proxy類裏面都不用靜態顯示出來。
  4. InvocationHandler:這個類在業務委託類執行時,會先調用invoke方法。invoke方法在執行想要的代理操作,可以實現對業務方法的 再包裝

總結:

  1. JDK動態代理類實現了 InvocationHandler接口,重寫的 invoke方法。
  2. JDK動態代理的基礎是反射機制(method.invoke(對象,參數))Proxy.newProxyInstance()

3.2 CGLib動態代理

特點

  1. 使用字節碼處理框架 ASM,其原理是通過 字節碼技術爲一個類創建子類,並在子類中採用方法攔截的技術攔截所有 父類方法的調用,順勢織入橫切邏輯。
  2. CGLib創建的動態代理對象性能比JDK創建的動態代理對象的性能高不少,但是CGLib在創建代理對象時所花費的時間卻比JDK多得多,所以對於單例的對象,因爲無需頻繁創建對象,用CGLib合適,反之,使用JDK方式要更爲合適一些。同時,由於CGLib由於是採用動態創建子類的方法,對於final方法,無法進行代理。

注意

JDK的動態代理只可以爲接口去完成操作,而 CGlib 它既可以爲沒有實現接口的類去做代理,也可以爲實現接口的類去做代理。

3.3 代碼實現部分

公共代碼

//接口類
public interface FoodService {
    public void makeNoodle();
    public void makeChicken();
}
//實現接口
public class FoodServiceImpl implements FoodService {
    @Override
    public void makeNoodle() {
        System.out.println("make noodle");
    }

    @Override
    public void makeChicken() {
        System.out.println("make Chicken");
    }
}

jdk動態代理代碼

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKProxyFactory implements InvocationHandler
{
 private Object target;

 public JDKProxyFactory(Object target)
 
{
  super();
  this.target = target;
 }

 // 創建代理對象
 public Object createProxy()
 
{
  // 1.得到目標對象的類加載器
  ClassLoader classLoader = target.getClass().getClassLoader();
  // 2.得到目標對象的實現接口
  Class<?>[] interfaces = target.getClass().getInterfaces();
  // 3.第三個參數需要一個實現invocationHandler接口的對象
  Object newProxyInstance = Proxy.newProxyInstance(classLoader, interfaces, this);
  return newProxyInstance;
 }

 // 第一個參數:代理對象.一般不使用;第二個參數:需要增強的方法;第三個參數:方法中的參數
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
 
{
  System.out.println("這是增強方法前......");
  Object invoke = method.invoke(target, args);
  System.out.println("這是增強方法後......");
  return invoke;
 }

 public static void main(String[] args)
 
{
  // 1.創建對象
  FoodServiceImpl foodService = new FoodServiceImpl();
  // 2.創建代理對象
  JDKProxyFactory proxy = new JDKProxyFactory(foodService);
  // 3.調用代理對象的增強方法,得到增強後的對象
  FoodService createProxy = (FoodService) proxy.createProxy();
  createProxy.makeChicken();
 }
}

Cglib動態代理代碼

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CglibProxyFactory implements MethodInterceptor
{
    //得到目標對象
    private Object target;
    //使用構造方法傳遞目標對象
    public CglibProxyFactory(Object target) {
        super();
        this.target = target;
    }
    //創建代理對象
    public Object createProxy(){
        //1.創建Enhancer
        Enhancer enhancer = new Enhancer();
        //2.傳遞目標對象的class
        enhancer.setSuperclass(target.getClass());
        //3.設置回調操作
        enhancer.setCallback(this);
        return enhancer.create();
    }

    //參數一:代理對象;參數二:需要增強的方法;參數三:需要增強方法的參數;參數四:需要增強的方法的代理
 @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("這是增強方法前......");
        Object invoke = methodProxy.invoke(target, args);
        System.out.println("這是增強方法後......");
        return invoke;
    }

    public static void main(String[] args) {
        // 1.創建對象
        FoodServiceImpl foodService = new FoodServiceImpl();
        // 2.創建代理對象
        CglibProxyFactory proxy = new CglibProxyFactory(foodService);
        // 3.調用代理對象的增強方法,得到增強後的對象
        FoodService createProxy = (FoodService) proxy.createProxy();
        createProxy.makeChicken();
    }
}

4. Spring AOP 和 AspectJ AOP區別

4.1 Spring AOP

Spring AOP 屬於運行時增強,主要具有如下特點:

  1. 基於動態代理來實現,默認如果使用接口的,用JDK提供的動態代理實現,如果是方法則使用CGLIB實現
  2. Spring AOP 需要依賴 IOC 容器來管理,並且只能作用於Spring容器,使用純Java代碼實現
  3. 在性能上,由於Spring AOP是基於 動態代理來實現的,在容器啓動時需要生成代理實例,在方法調用上也會增加棧的深度,使得Spring AOP的性能不如AspectJ的那麼好。
  4. Spring AOP致力於解決企業級開發中最普遍的AOP(方法織入)。

4.2 AspectJ

AspectJ 是一個易用的功能強大的AOP框架,屬於編譯時增強,  可以單獨使用,也可以整合到其它框架中,是 AOP 編程的完全解決方案。AspectJ需要用到單獨的編譯器ajc。

AspectJ屬於靜態織入,通過修改代碼來實現,在實際運行之前就完成了織入,所以說它生成的類是沒有額外運行時開銷的,一般有如下幾個織入的時機:

  1. 編譯期織入(Compile-time weaving):如類 A 使用 AspectJ 添加了一個屬性,類 B 引用了它,這個場景就需要編譯期的時候就進行織入,否則沒法編譯類 B。
  2. 編譯後織入(Post-compile weaving):也就是已經生成了 .class 文件,或已經打成 jar 包了,這種情況我們需要增強處理的話,就要用到編譯後織入。
  3. 類加載後織入(Load-time weaving):指的是在加載類的時候進行織入,要實現這個時期的織入,有幾種常見的方法

4.3 對比

Spring AOP AspectJ
在純Java中實現 用Java編程語言擴展實現
編譯器javac 一般需要ajc
只可運行時織入 支持編譯時、編譯後、加載時織入
僅支持方法級編織 可編織字段、方法、構造函數、靜態初始值等
只可在spring管理的Bean上實現 可在所有域對象實現
僅支持方法執行切入點 支持所有切入點
比AspectJ 慢很多 速度比AOP快很多
易學習使用 比AOP更復雜
代理由目標對象創建,切面應用在代理上 執行程序前,各方面直接織入代碼中

5.  BeanFactory 和 FactoryBean

5.1 BeanFactory

  1. BeanFactory 以 Factory 結尾,表示它是一個工廠類(接口),BeanFacotry 是 Spring 中比較原始的Factory。
  2. BeanFactory 無法支持 Spring 的許多插件,如AOP功能、Web應用等。 ApplicationContext 接口由BeanFactory接口派生而來,提供了國際化訪問、事件傳播等多個功能。
  3. BeanFactory 是 IOC 容器的核心,負責生產和管理 Bean 對象。

5.2 FactoryBean

  1. FactoryBean 以 Bean 結尾,表示它是一個Bean。
  2. FactoryBean 是工廠類接口,用戶可以通過實現該接口定製實例化 Bean 的邏輯。FactoryBean 接口對於 Spring 框架來說佔用重要的地位,Spring自身就提供了70多個FactoryBean的實現。
  3. 當在IOC容器中的Bean實現了 FactoryBean 後,通過getBean(String BeanName)獲取到的 Bean 對象並不是 FactoryBean 的實現類對象,而是這個實現類中的 getObject() 方法返回的對象。要想獲取FactoryBean的實現類,就要 getBean(String &BeanName),在BeanName之前加上 &

6. Spring生命週期

Spring IOC 初始化跟銷燬 Bean 的過程大致分爲Bean定義、Bean初始化、Bean的生存期 跟 Bean的銷燬4個部分。

如果僅僅是實例化跟依賴注入當然簡單,問題是如果我們要完成自定義的要求,Spring提供了一系列接口跟配置來完成 Bean 的初始化過程,看下整個IOC容器初始化Bean的流程。

一般情況下我們自定義Bean的初始化跟銷燬方法下面三種:

  1. 通過 XML 或者 @Bean配置

通過xml或者@Bean(initMethod="init", destroyMethod="destory")來實現。

  1. 使用 JSR250 規則定義的(java規範)兩個註解來實現
  1. @PostConstruct: 在Bean創建完成,且屬於賦值完成後進行初始化,屬於JDK規範的註解。
  2. @PreDestroy: 在bean將被移除之前進行通知,在容器銷燬之前進行清理工作。
  3. 提示:JSR是由JDK提供的一組規範。
  1. 通過繼承實現類方法
  1. 實現 InitializingBean接口的 afterPropertiesSet()方法,當 beanFactory創建好對象,且把bean所有 屬性設置好之後會調這個方法,相當於初始化方法。
  2. 實現 DisposableBeandestory()方法,當bean銷燬時會把單實例bean進行銷燬
  3. 對於 實例的bean,可以正常調用初始化和銷燬方法。對於 實例的bean,容器只負責調用時候初始化,但不會管理bean, 容器關閉時不會調用銷燬方法。

7. Spring中的設計模式

Spring 框架中廣泛使用了不同類型的設計模式,下面我們來看看到底有哪些設計模式?

  1. 工廠設計模式 : Spring 使用工廠模式通過 BeanFactory、ApplicationContext 創建 bean 對象。
  2. 代理設計模式 : Spring AOP 功能的實現。
  3. 單例設計模式 : Spring 中的 Bean 默認都是單例的。
  4. 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 結尾的對數據庫操作的類,它們就使用到了模板模式。
  5. 包裝器設計模式 : 我們的項目需要連接多個數據庫,而且不同的客戶在每次訪問中根據需要會去訪問不同的數據庫。這種模式讓我們可以根據客戶的需求能夠動態切換不同的數據源。
  6. 觀察者模式: Spring 事件驅動模型就是觀察者模式很經典的一個應用。
  7. 適配器模式 :Spring AOP 的增強或通知(Advice)使用到了適配器模式、spring MVC 中也是用到了適配器模式適配Controller。

8. Spring循環依賴

8.1 簡說循環依賴


Spring循環依賴
:說白了就是一個或多個對象實例之間存在直接或間接的依賴關係,這種依賴關係構成了構成一個環形調用。發生循環依賴的兩個前提條件是:

  1. 出現循環依賴的Bean必須要是單例( singleton),如果依賴 prototype則完全不會有此需求。
  2. 依賴注入的方式不能全是構造器注入的方式,只能解決setter方法的循環依賴,這是錯誤的。

假設AB之間相互依賴,通過嘗試不同的注入方式注入後可的如下結論:

依賴情況 依賴注入方式 問題解決
AB循環依賴 均採用setter方法注入
AB循環依賴 均採用屬性自動注入
AB循環依賴 均採用構造器注入
AB循環依賴 A中注入B的方式爲setter方法,B中注入A的方式爲構造器
AB循環依賴 B中注入A的方式爲setter方法,A中注入B的方式爲構造器

PS:第四種可以而第五種不可以的原因是 Spring在創建Bean時默認會根據自然排序進行創建,所以A會先於B進行創建。

8.2 循環依賴通俗說

Spring通過三級緩存解決了循環依賴。

  1. 一級緩存 : Map<String,Object> singletonObjects,單例池,用於保存實例化、注入、初始化完成的bean實例
  2. 二級緩存 : Map<String,Object> earlySingletonObjects,早期曝光對象,用於保存實例化完成的bean實例
  3. 三級緩存 : Map<String,ObjectFactory<?>> singletonFactories,早期曝光對象工廠,用於保存bean創建工廠,以便於後面擴展有機會創建代理對象。

當A、B兩個類發生循環引用時,在A完成實例化後,就使用實例化後的對象去創建一個對象工廠,並添加到三級緩存中,如果A被AOP代理,那麼通過這個工廠獲取到的就是A代理後的對象,如果A沒有被AOP代理,那麼這個工廠獲取到的就是A實例化的對象。當A進行屬性注入時,會去創建B,同時B又依賴了A,所以創建B的同時又會去調用getBean(a)來獲取需要的依賴,此時的getBean(a)會從緩存中獲取:

  1. 第一步,先獲取到三級緩存中的工廠。
  2. 第二步,調用對象工工廠的getObject方法來獲取到對應的對象,得到這個對象後將其注入到B中。緊接着B會走完它的生命週期流程,包括初始化、後置處理器等。

當B創建完後,會將B再注入到A中,此時A再完成它的整個生命週期。至此循環依賴結束!

8.2 三級緩存意義何在?


先跟蹤下源碼(如上圖),跟蹤過程中注意區別下有AOP的依賴沒有AOP的依賴兩種情況,跟蹤後你會發現三級緩存的功能是隻有真正發生循環依賴的時候,纔去提前生成代理對象,否則只會創建一個工廠並將其放入到三級緩存中,但是不會去通過這個工廠去真正創建對象。至於提速這一說法,還是拉閘吧。


如上圖所示,如果使用二級緩存解決循環依賴,意味着所有Bean在實例化後就要完成AOP代理,這樣違背了Spring設計的原則,Spring在設計之初就是通過AnnotationAwareAspectJAutoProxyCreator這個後置處理器來在Bean生命週期的最後一步來完成AOP代理,而不是在實例化後就立馬進行AOP代理。

9. Spring事務

Spring 事務的本質其實就是數據庫對事務的支持,沒有數據庫的事務支持,spring是無法提供事務功能的。Spring只提供統一事務管理接口,具體實現都是由各數據庫自己實現,數據庫事務的提交和回滾是通過binlog或者undolog實現的,具體流程在MySQL中講過了。Spring會在事務開始時,根據當前環境中設置的隔離級別,調整數據庫隔離級別,由此保持一致。

9.1  Spring事務的種類

Spring 支持編程式事務管理和聲明式事務管理兩種方式:

  1. 編程式事務

編程式事務管理使用TransactionTemplate。

  1. 聲明式事務
  1. 聲明式事務管理建立在AOP之上的。其本質是通過AOP功能,對方法前後進行攔截,將事務處理的功能編織到攔截的方法中,也就是在目標方法開始之前啓動一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。
  2. 優點是不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明或通過@Transactional註解的方式,便可以將事務規則應用到業務邏輯中,減少業務代碼的污染。唯一不足地方是,最細粒度只能作用到方法級別,無法做到像編程式事務那樣可以作用到代碼塊級別。

9.2 Spring的事務傳播機制

spring事務的傳播機制說的是,當多個事務同時存在的時候,spring如何處理這些事務的行爲。事務傳播機制實際上是使用簡單的ThreadLocal實現的,所以,如果調用的方法是在新線程調用的,事務傳播實際上是會失效的。

  1. propagation_requierd:如果當前沒有事務,就新建一個事務,如果已存在一個事務中,加入到這個事務中,這是最常見的選擇,也是默認模式,它適合於絕大多數情況。
  2. propagation_supports:支持當前事務,如果沒有當前事務,就以非事務方法執行。
  3. propagation_mandatory:使用當前事務,如果沒有當前事務,就拋出異常。
  4. propagation_required_new:新建事務,如果當前存在事務,把當前事務掛起。
  5. propagation_not_supported:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
  6. propagation_never:以非事務方式執行操作,如果當前事務存在則拋出異常。
  7. propagation_nested:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與propagation_required類似的操作

9.2 Spring的事務隔離級別

TransactionDefinition接口中定義了五個表示隔離級別的常量,這裏其實關鍵還是要看MySQL的隔離級別:

  1. ISOLATION_DEFAULT:使用後端數據庫默認的隔離界別,MySQL默認可重複讀,Oracle默認讀已提交。
  2. ISOLATION_READ_UNCOMMITTED:讀未提交。
  3. ISOLATION_READ_COMMITTED:讀已提交。
  4. ISOLATION_REPEATABLE_READ:可重複讀。
  5. ISOLATION_SERIALIZABLE:串行化。

10. Spring MVC

10.1 什麼是 MVC ?

MVC模式中M是指業務模型,V是指用戶界面,C則是控制器,使用MVC的目的是將 M 和 V 的實現代碼分離,開發中一般將應用程序分爲 Controller、Model、View 三層,Controller 接收客戶端請求,調用 Model 生成業務數據,傳遞給 View,View最終展示前端結果。

Spring MVC 就是對上述這套流程的封裝,屏蔽了很多底層代碼,開放出接口,讓開發者可以更加輕鬆、便捷地完成基於 MVC 模式的 Web 開發。

10.2 Spring 跟 Spring MVC關係

最開始只有Spring,只提供IOC跟AOP核心功能,後來出了亂七八糟的比如MVC、Security、Boot 等。原來的Spring 就變成了現在的Spring Core,MVC指的是Web的MVC框架。

  1. Spring MVC 就是一個MVC框架,其實大範圍上來說屬於Spring,Spring MVC是一個類似於Struts的MVC模式的WEB開發框架,Spring MVC是基於 Spring 功能之上添加的 Web 框架,Spring 跟 SpringMVC可以理解爲父子容器的關係,想用 Spring MVC 必須先依賴Spring。
  2. Spring MVC 是控制層,用來接收前臺傳值,調用service層和持久層,返回數據再通過 Spring MVC把數據返回前臺

10.3 Spring MVC 的核心組件

  1. DispatcherServlet:前置控制器,是整個流程控制的 核心,控制其他組件的執行,進行統一調度,降低組件之間的耦合性,相當於總指揮。
  2. Handler:處理器,完成具體的業務邏輯,相當於 Servlet 或 Action。
  3. HandlerMapping:DispatcherServlet 接收到請求之後,通過 HandlerMapping 將不同的請求映射到不同的 Handler。
  4. HandlerInterceptor:處理器攔截器,是一個接口,如果需要完成一些攔截處理,可以實現該接口。
  5. HandlerExecutionChain:處理器執行鏈,包括兩部分內容:Handler 和 HandlerInterceptor(系統會有一個默認的 HandlerInterceptor,如果需要額外設置攔截,可以添加攔截器)。
  6. HandlerAdapter:處理器適配器,Handler 執行業務方法之前,需要進行一系列的操作,包括表單數據的驗證、數據類型的轉換、將表單數據封裝到 JavaBean 等,這些操作都是由 HandlerApater 來完成,開發者只需將注意力集中業務邏輯的處理上,DispatcherServlet 通過 HandlerAdapter 執行不同的 Handler。
  7. ModelAndView:裝載了模型數據和視圖信息,作爲 Handler 的處理結果,返回給 DispatcherServlet。
  8. ViewResolver:視圖解析器,DispatcheServlet 通過它將邏輯視圖解析爲物理視圖,最終將渲染結果響應給客戶端。

10.4 Spring MVC 的工作流程

  1. DispatcherServlet 表示前置控制器,是整個SpringMVC的控制中心。用戶發出請求,接收請求並攔截請求。
  2. HandlerMapping 爲處理器映射。DispatcherServlet調用 HandlerMapping,HandlerMapping根據請求url查找Handler。
  3. HandlerExecution 表示具體的Handler,其主要作用是根據url查找控制器,如上url被查找控制器爲:hello。
  4. HandlerExecution 將解析後的信息傳遞給 DispatcherServlet,如解析控制器映射等。
  5. HandlerAdapter 表示處理器適配器,其按照特定的規則去執行Handler。
  6. Handler 讓具體的 Controller 執行。
  7. Controller 將具體的執行信息返回給 HandlerAdapter,如ModelAndView。
  8. HandlerAdapte r將視圖邏輯名或模型傳遞給 DispatcherServlet。
  9. DispatcherServlet 調用視圖解析器(ViewResolver)來解析 HandlerAdapter 傳遞的邏輯視圖名。
  10. 視圖解析器將解析的邏輯視圖名傳給 DispatcherServlet。
  11. DispatcherServlet 根據視圖解析器解析的視圖結果,調用具體的視圖。
  12. 最終視圖呈現給用戶。

Spring MVC 雖然整體流程複雜,但是實際開發中很簡單,大部分的組件不需要開發者創建跟管理,只需要通過配置文件的方式完成配置即可,真正需要開發者進行處理的只有 HandlerViewModle

但是隨着前後端分離跟微服務的發展,一包走天下的開發模式其實用的不是很多了,大部分情況下是 SpringBoot + Vue

11. Spring Boot

11.1 Spring Boot簡介

Spring Boot 基於 Spring 開發,Spirng Boot 本身並不提供 Spring 框架的核心特性以及擴展功能,只是用於快速、敏捷地開發新一代基於 Spring 框架的應用程序。它並不是用來替代 Spring 的解決方案,而是和 Spring 框架緊密結合用於提升 Spring 開發者體驗的工具。Spring Boot 以約定大於配置核心思想開展工作,相比Spring具有如下優勢:

  1. Spring Boot 可以建立獨立的Spring應用程序。
  2. Spring Boot 內嵌瞭如Tomcat,Jetty和Undertow這樣的容器,也就是說可以直接跑起來,用不着再做部署工作了。
  3. Spring Boot 無需再像Spring那樣搞一堆繁瑣的xml文件的配置。
  4. Spring Boot 可以自動配置(核心)Spring。SpringBoot將原有的XML配置改爲Java配置,將bean注入改爲使用註解注入的方式(@Autowire),並將多個xml、properties配置濃縮在一個appliaction.yml配置文件中。
  5. Spring Boot 提供了一些現有的功能,如量度工具,表單數據驗證以及一些外部配置這樣的一些第三方功能。
  6. Spring Boot 整合常用依賴(開發庫,例如spring-webmvc、jackson-json、validation-api和tomcat等),提供的POM可以簡化Maven的配置。當我們引入核心依賴時,SpringBoot會自引入其他依賴。

11.2 SpringBoot 注意點

  1. SpringBoot 抽離

將所有的功能場景都抽取出來,做成一個個的starter,spring-boot-starter-xxx 就是spring-boot的場景啓動器。只需要在項目中引入這些starter即可,所有相關的依賴都會導入進來 。

  1. 自動配置原理
  1. SpringBoot在啓動的時候從類路徑下的 META-INF/spring.factories 中獲取 EnableAutoConfiguration 指定的值
  2. 我們看我們需要的功能有沒有在SpringBoot默認寫好的自動配置類 xxxxAutoConfigurartion 當中。
  3. 我們再來看這個自動配置類中到底配置了哪些組件;(只要我們要用的組件存在在其中,我們就不需要再手動配置了)。
  4. 給容器中自動配置類添加組件的時候,會從 xxxxProperties 類中獲取某些屬性。我們只需要在配置文件中指定這些屬性的值即可。
  1. 各種組件的整合

比如整合MyBatis、Redis、Swagger、Security、Shrio、Druid等,百度教程即可。

11.3  Springboot啓動原理的底層

SpringApplication這個類主要做了以下四件事情:

  1. 推斷應用的類型是普通的項目還是Web項目
  2. 查找並加載所有可用初始化器 , 設置到initializers屬性中
  3. 找出所有的應用程序監聽器,設置到listeners屬性中
  4. 推斷並設置main方法的定義類,找到運行的主類

SpringBoot啓動大致流程如下(源網侵刪):


11.3 架構演進


  1. 單體應用

傳統項目把所有的業務功能在一個項目中,這種單體架構結構邏輯比較簡單,對於小型項目來說很實用。但隨着業務量的增多,邏輯越來越複雜,項目會逐漸變得非常龐大,邏輯也會變得混亂,給維護和開發造成比較大的困難。

  1. 垂直架構

把原來比較大的單體項目根據業務邏輯拆分成多個小的單體項目,比如把物流系統、客戶關係系統從原來的電子商城系統中抽離出來,構建成兩個小的項目。這種架構雖然解決了原來單體項目過大的問題,但也帶來了數據冗餘、耦合性大的問題。

  1. SOA架構

面向服務架構(Service Oriented Architecture),它在垂直架構的基礎上,把原來項目中的公共組件抽離出來做成形成服務,爲各個系統提供服務。服務層即抽取出的公共組件。系統層的多個小型系統通過ESB企業服務總線(它是項目與服務之間通信的橋樑)以及Webservice調用服務,完成各自的業務邏輯。但是SOA架構抽取的粒度比較粗糙,系統與服務之間的耦合性很大,系統與服務界限不夠清晰,給開發和維護造成一定的困難。

  1. 微服務架構

微服務架構對服務的抽取粒度更小,把系統中的服務層完全隔離出來。遵循單一原則,系統層和服務層的界限清晰,各個系統通過服務網關調用所需微服務。微服務之間通過RESTful等輕量級協議傳輸,相比ESB更輕量。但這種架構的開發成本比較高(因爲新增了容錯、分佈式事務等要求),對團隊的要求比較大,所以不適合小項目、小團隊。

  1. 框架演變

從一個複雜應用場景衍生一種規範框架,用戶只需要進行各種配置而不需要自己去實現它,這時候強大的配置功能成了優點。發展到一定程度之後,人們根據實際生產應用情況,選取其中實用功能和設計精華,重構出一些輕量級的框架。之後爲了提高開發效率,嫌棄原先的各類配置過於麻煩,於是開始提倡約定大於配置的方案,進而衍生出一些一站式的解決方案。

12. Spring Cloud

微服務的定義 :

  1. 2014 年 Martin Fowler 提出的一種新的架構形式。微服務架構是一種 架構模式,提倡將單一應用程序劃分成一組小的服務,服務之間相互協調,互相配合,爲用戶提供最終價值。每個服務運行在其獨立的進程中,服務與服務之間採用輕量級的通信機制(如HTTP或Dubbo)互相協作,每個服務都圍繞着具體的業務進行構建,並且能夠被獨立的部署到生產環境中,另外,應儘量避免統一的,集中式的服務管理機制,對具體的一個服務而言,應根據業務上下文,選擇合適的語言、工具(如Maven)對其進行構建。
  2. 微服務化的核心就是將傳統的一站式應用,根據業務拆分成一個一個的服務,徹底地去耦合,每一個微服務提供單個業務功能的服務,一個服務做一件事情,從技術角度看就是一種小而獨立的處理過程,類似進程的概念,能夠自行單獨啓動或銷燬,擁有自己獨立的數據庫。

微服務時代核心問題(問題根本:網絡不可靠):

  1. 服務很多,客戶端怎麼訪問,如何提供對外網關?
  2. 這麼多服務,服務之間如何通信? HTTP還是RPC?
  3. 這麼多服務,如何治理? 服務的註冊跟發現。
  4. 服務掛了怎麼辦?熔斷機制。

主流微服務框架:

  1. Spring Cloud Netflix
  2. Spring Cloud Alibaba
  3. Spring +  Dubbo  +  ZooKeeper

關於 SpringCloud Netflix 前面詳細寫過,在此不再重複。

13.  常用註解

感覺Spring這塊沒啥特別好寫的不知道爲啥,可能跟自己用的少也有關吧,最後來幾個簡單註解收尾,一般有Spring核心註解、SpringBoot註解、SpringCloud註解、任務執行跟調度註解、測試註解、JPA註解、SpringMVC跟REST註解等等,這裏只羅列下幾個核心註解(全部註解公衆號回覆註解):

  1. @Component : 可以配置CommandLineRunner使用,當一個組件不好歸屬到下面類的時候會用該註解標註, @Controller@Service@Repository 屬於 Component的細化。
  2. @Autowired :自動導入依賴的Bean,默認byType,完成屬性,方法的組裝,可以對類成員變量,方法,構造函數進行標註,加上(required=false)時找不到也不報錯
  3. @Import : 跟@Bean類似,更靈活的導入其他配置類。 ImportSelectorImportBeanDefinitionRegistrar
  4. @Bean : 等價xml中配置的bean, 用在方法上哦,來生產出一個bean,然後交給Spring管理
  5. @Value : 可用在字段,構造器參數跟方法參數,指定一個默認值,支持 #{} 跟 ${} 兩個方式。一般將SpringbBoot中的application.properties 配置的屬性值賦值給變量。
  6. @Configuration : 等價於Spring的XML配置文件,使用Java代碼可以檢查類型安全。如果有些時候必須用到xml配置文件,可通過@Configuration 類作爲項目的配置主類,使用@ImportResource註解加載xml 文件
  7. @Qualifier : 該註解通常跟@Autowired一起使用,當想對注入的過程做更多的控制,@Qualifier可幫助配置,比如兩個以上相同類型的Bean時 Spring無法抉擇,用到此註解

14. 參考

MVC 常見面試題:https://blog.csdn.net/a745233700/article/details/80963758

Spring 常見面試題:https://thinkwon.blog.csdn.net/article/details/104397516

本文分享自微信公衆號 - Java識堂(erlieStar)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

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