激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 深入淺出Spring架構設計

深入淺出Spring架構設計

2022-01-12 23:03三太子敖丙 Java教程

為什么需要Spring? 什么是Spring?對于這樣的問題,大部分人都是處于一種朦朦朧朧的狀態,說的出來,但又不是完全說的出來,今天我們就以架構設計的角度嘗試解開Spring的神秘面紗。

深入淺出Spring架構設計

前言

為什么需要Spring? 什么是Spring?

對于這樣的問題,大部分人都是處于一種朦朦朧朧的狀態,說的出來,但又不是完全說的出來,今天我們就以架構設計的角度嘗試解開Spring的神秘面紗。

本篇文章以由淺入深的方式進行介紹,大家不必驚慌,我可以保證,只要你會編程就能看懂。

本篇文章基于Spring 5.2.8,閱讀時長大概需要20分鐘

案例

我們先來看一個案例:有一個小伙,有一輛吉利車, 平常就開吉利車上班

深入淺出Spring架構設計

代碼實現:

  1. public class GeelyCar { 
  2.  
  3.     public void run(){ 
  4.         System.out.println("geely running"); 
  5.     } 
  1. public class Boy { 
  2.   // 依賴GeelyCar 
  3.     private final GeelyCar geelyCar = new GeelyCar(); 
  4.  
  5.     public void drive(){ 
  6.         geelyCar.run(); 
  7.     } 

有一天,小伙賺錢了,又買了輛紅旗,想開新車。

簡單,把依賴換成HongQiCar

深入淺出Spring架構設計

代碼實現:

  1. public class HongQiCar { 
  2.  
  3.     public void run(){ 
  4.         System.out.println("hongqi running"); 
  5.     } 
  1. public class Boy { 
  2.     // 修改依賴為HongQiCar 
  3.     private final HongQiCar hongQiCar = new HongQiCar(); 
  4.  
  5.     public void drive(){ 
  6.         hongQiCar.run(); 
  7.     } 

新車開膩了,又想換回老車,這時候,就會出現一個問題:這個代碼一直在改來改去

很顯然,這個案例違背了我們的依賴倒置原則(DIP):程序不應依賴于實現,而應依賴于抽象

優化后

現在我們對代碼進行如下優化:

深入淺出Spring架構設計

Boy依賴于Car接口,而之前的GeelyCar與HongQiCar為Car接口實現

代碼實現:

定義出Car接口

  1. public interface Car { 
  2.  
  3.     void run(); 

將之前的GeelyCar與HongQiCar改為Car的實現類

  1. public class GeelyCar implements Car { 
  2.  
  3.     @Override 
  4.     public void run(){ 
  5.         System.out.println("geely running"); 
  6.     } 

HongQiCar相同

Person此時依賴的為Car接口

  1. public class Boy { 
  2.   // 依賴于接口 
  3.     private final Car car; 
  4.    
  5.     public Person(Car car){ 
  6.         this.car = car; 
  7.     } 
  8.  
  9.     public void drive(){ 
  10.         car.run(); 
  11.     } 

此時小伙想換什么車開,就傳入什么參數即可,代碼不再發生變化。

局限性

以上案例改造后看起來確實沒有什么毛病了,但還是存在一定的局限性,如果此時增加新的場景:

有一天小伙喝酒了沒法開車,需要找個代駕。代駕并不關心他給哪個小伙開車,也不關心開的是什么車,小伙就突然成了個抽象,這時代碼又要進行改動了,代駕依賴小伙的代碼可能會長這個樣子:

  1. private final Boy boy = new YoungBoy(new HongQiCar()); 

隨著系統的復雜度增加,這樣的問題就會越來越多,越來越難以維護,那么我們應當如何解決這個問題呢?

思考

首先,我們可以肯定:使用依賴倒置原則是沒有問題的,它在一定程度上解決了我們的問題。

我們覺得出問題的地方是在傳入參數的過程:程序需要什么我們就傳入什么,一但系統中出現多重依賴的類關系,這個傳入的參數就會變得極其復雜。

或許我們可以把思路反轉一下:我們有什么,程序就用什么!

當我們只實現HongQiCar和YoungBoy時,代駕就使用的是開著HongQiCar的YoungBoy!

當我們只實現GeelyCar和OldBoy時,代駕自然而然就改變成了開著GeelyCar的OldBoy!

而如何反轉,就是Spring所解決的一大難題。

Spring介紹

Spring是一個一站式輕量級重量級的開發框架,目的是為了解決企業級應用開發的復雜性,它為開發Java應用程序提供全面的基礎架構支持,讓Java開發者不再需要關心類與類之間的依賴關系,可以專注的開發應用程序(crud)。

Spring為企業級開發提供給了豐富的功能,而這些功能的底層都依賴于它的兩個核心特性:依賴注入(DI)和面向切面編程(AOP)。

Spring的核心概念

IoC容器

IoC的全稱為Inversion of Control,意為控制反轉,IoC也被稱為依賴性注入(DI),這是一個通過依賴注入對象的過程:對象僅通過構造函數、工廠方法,或者在對象實例化在其上設置的屬性來定義其依賴關系(即與它們組合的其他對象),然后容器在創建bean時注入這些需要的依賴。這個過程從根本上說是Bean本身通過使用直接構建類或諸如服務定位模式的機制,來控制其依賴關系的實例化或位置的逆過程(因此被稱為控制反轉)。

依賴倒置原則是IoC的設計原理,依賴注入是IoC的實現方式。

容器

在Spring中,我們可以使用XML、Java注解或Java代碼的方式來編寫配置信息,而通過配置信息,獲取有關實例化、配置和組裝對象的說明,進行實例化、配置和組裝應用對象的稱為容器。

一般情況下,我們只需要添加幾個注解,這樣容器進行創建和初始化后,我們就可以得到一個可配置的,可執行的系統或應用程序。

深入淺出Spring架構設計

Bean

在Spring中,由Spring IOC容器進行實例化—>組裝管理—>構成程序骨架的對象稱為Bean。Bean就是應用程序中眾多對象之一。

以上三點串起來就是:Spring內部是一個放置Bean的IoC容器,通過依賴注入的方式處理Bean之間的依賴關系。

AOP

面向切面編程(Aspect-oriented Programming),是相對面向對象編程(OOP)的一種功能補充,OOP面向的主要對象是類,而AOP則是切面。在處理日志、安全管理、事務管理等方面有非常重要的作用。AOP是Spring框架重要的組件,雖然IOC容器沒有依賴AOP,但是AOP提供了非常強大的功能,用來對IOC做補充。

AOP可以讓我們在不修改原有代碼的情況下,對我們的業務功能進行增強:將一段功能切入到我們指定的位置,如在方法的調用鏈之間打印日志。

Spring的優點

1、Spring通過DI、AOP來簡化企業級Java開發

2、Spring的低侵入式設計,讓代碼的污染極低

3、Spring的IoC容器降低了業務對象之間的復雜性,讓組件之間互相解耦

4、Spring的AOP支持允許將一些通用任務如安全、事務、日志等進行集中式處理,從而提高了更好的復用性

5、Spring的高度開放性,并不強制應用完全依賴于Spring,開發者可自由選用Spring框架的部分或全部

6、Spring的高度擴展性,讓開發者可以輕易的讓自己的框架在Spring上進行集成

7、Spring的生態極其完整,集成了各種優秀的框架,讓開發者可以輕易的使用它們

我們可以沒有Java,但是不能沒有Spring~

用Spring改造案例

我們現在已經認識了什么是Spring,現在就嘗試使用Spring對案例進行改造一下

原來的結構沒有變化,只需在GeelyCar或HongQiCar上增加@Component注解,Boy在使用時加上@Autowired注解

深入淺出Spring架構設計

代碼樣式:

  1. @Componentpublic class GeelyCar implements Car { @Override public void run() {  System.out.println("geely car running"); }} 

HongQiCar相同

在Spring中,當類標識了@Component注解后就表示這是一個Bean,可以被IoC容器所管理

  1. @Componentpublic class Boy {  // 使用Autowired注解表示car需要進行依賴注入 @Autowired private Car car; public void driver(){  car.run(); }} 

我們之前所說的:我們實現什么,程序就使用什么,在這里就等同于我們在哪個類上標識了Component注解,哪個類就會是一個Bean,Spring就會使用它注入Boy的屬性Car中

所以當我們給GeelyCar標識Component注解時,Boy開的車就是GeelyCar,當我們給HongQiCar標識Component注解時,Boy開的車就是HongQiCar

當然,我們不可以在GeelyCar和HongQiCar上同時標識Component注解,因為這樣Spring就不知道用哪個Car進行注入了——Spring也有選擇困難癥(or 一boy不能開倆車?)

使用Spring啟動程序

  1. // 告訴Spring從哪個包下掃描Bean,不寫就是當前包路徑@ComponentScan(basePackages = "com.my.spring.test.demo")public class Main { public static void main(String[] args) {  // 將Main(配置信息)傳入到ApplicationContext(IoC容器)中  ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);  // 從(IoC容器)中獲取到我們的boy  Boy boy = (Boy) context.getBean("boy");  // 開車  boy.driver(); }} 

這里就可以把我們剛剛介紹Spring的知識進行解讀了

把具有ComponentScan注解(配置信息)的Main類,傳給AnnotationConfigApplicationContext(IoC容器)進行初始化,就等于:IoC容器通過獲取配置信息進行實例化、管理和組裝Bean。

而如何進行依賴注入則是在IoC容器內部完成的,這也是本文要討論的重點

思考

我們通過一個改造案例完整的認識了Spring的基本功能,也對之前的概念有了一個具象化的體驗,而我們還并不知道Spring的依賴注入這一內部動作是如何完成的,所謂知其然更要知其所以然,結合我們的現有知識,以及對Spring的理解,大膽猜想推測一下吧(這是很重要的能力哦)

其實猜測就是指:如果讓我們自己實現,我們會如何實現這個過程?

首先,我們要清楚我們需要做的事情是什么:掃描指定包下面的類,進行實例化,并根據依賴關系組合好。

步驟分解:

掃描指定包下面的類 -> 如果這個類標識了Component注解(是個Bean) -> 把這個類的信息存起來

進行實例化 -> 遍歷存好的類信息 -> 通過反射把這些類進行實例化

根據依賴關系組合 -> 解析類信息 -> 判斷類中是否有需要進行依賴注入的字段 -> 對字段進行注入

深入淺出Spring架構設計

方案實現

我們現在已經有了一個看起來像是那么一回事的解決方案,現在就嘗試把這個方案實現出來

定義注解

首先我們需要定義出需要用到的注解:ComponentScan,Component,Autowired

  1. @Target(ElementType.TYPE) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface ComponentScan { 
  4.  
  5.     String basePackages() default ""
  1. @Target(ElementType.TYPE) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface Component { 
  4.  
  5.     String value() default ""
  1. @Target(ElementType.FIELD) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface Autowired { 

掃描指定包下面的類

掃描指定包下的所有類,聽起來好像一時摸不著頭腦,其實它等同于另一個問題:如何遍歷文件目錄?

那么存放類信息應該用什么呢?我們看看上面例子中getBean的方法,是不是像Map中的通過key獲取value?而Map中還有很多實現,但線程安全的卻只有一個,那就是ConcurrentHashMap(別跟我說HashTable)

定義存放類信息的map

  1. private final Map<String, Class<?>> classMap = new ConcurrentHashMap<>(16); 

具體流程,下面同樣附上代碼實現:

深入淺出Spring架構設計

代碼實現,可以與流程圖結合觀看:

掃描類信息

  1. private void scan(Class<?> configClass) { 
  2.   // 解析配置類,獲取到掃描包路徑 
  3.   String basePackages = this.getBasePackages(configClass); 
  4.   // 使用掃描包路徑進行文件遍歷操作 
  5.   this.doScan(basePackages); 
  1. private String getBasePackages(Class<?> configClass) { 
  2.   // 從ComponentScan注解中獲取掃描包路徑 
  3.   ComponentScan componentScan = configClass.getAnnotation(ComponentScan.class); 
  4.   return componentScan.basePackages(); 
  1. private void doScan(String basePackages) { 
  2.   // 獲取資源信息 
  3.   URI resource = this.getResource(basePackages); 
  4.  
  5.   File dir = new File(resource.getPath()); 
  6.   for (File file : dir.listFiles()) { 
  7.     if (file.isDirectory()) { 
  8.       // 遞歸掃描 
  9.       doScan(basePackages + "." + file.getName()); 
  10.     } 
  11.     else { 
  12.       // com.my.spring.example + . + Boy.class -> com.my.spring.example.Boy 
  13.       String className = basePackages + "." + file.getName().replace(".class"""); 
  14.       // 將class存放到classMap中 
  15.       this.registerClass(className); 
  16.     } 
  17.   } 
  1. private void registerClass(String className){ 
  2.   try { 
  3.     // 加載類信息 
  4.     Class<?> clazz = classLoader.loadClass(className); 
  5.     // 判斷是否標識Component注解 
  6.     if(clazz.isAnnotationPresent(Component.class)){ 
  7.       // 生成beanName com.my.spring.example.Boy -> boy 
  8.       String beanName = this.generateBeanName(clazz); 
  9.       // car: com.my.spring.example.Car 
  10.       classMap.put(beanName, clazz); 
  11.     } 
  12.   } catch (ClassNotFoundException ignore) {} 

實例化

現在已經把所有適合的類都解析好了,接下來就是實例化的過程了

定義存放Bean的Map

  1. private final Map<String, Object> beanMap = new ConcurrentHashMap<>(16); 

具體流程,下面同樣給出代碼實現:

深入淺出Spring架構設計

代碼實現,可以與流程圖結合觀看:

遍歷classMap進行實例化Bean

  1. public void instantiateBean() { 
  2.   for (String beanName : classMap.keySet()) { 
  3.     getBean(beanName); 
  4.   } 
  1. public Object getBean(String beanName){ 
  2.   // 先從緩存中獲取 
  3.   Object bean = beanMap.get(beanName); 
  4.   if(bean != null){ 
  5.     return bean; 
  6.   } 
  7.   return this.createBean(beanName); 
  1. private Object createBean(String beanName){ 
  2.   Class<?> clazz = classMap.get(beanName); 
  3.   try { 
  4.     // 創建bean 
  5.     Object bean = this.doCreateBean(clazz); 
  6.     // 將bean存到容器中 
  7.     beanMap.put(beanName, bean); 
  8.     return bean; 
  9.   } catch (IllegalAccessException e) { 
  10.     throw new RuntimeException(e); 
  11.   } 
  1. private Object doCreateBean(Class<?> clazz) throws IllegalAccessException {  // 實例化bean  Object bean = this.newInstance(clazz);  // 填充字段,將字段設值  this.populateBean(bean, clazz);  return bean;} 
  1. private Object newInstance(Class<?> clazz){  try {    // 這里只支持默認構造器    return clazz.getDeclaredConstructor().newInstance();  } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {    throw new RuntimeException(e);  }} 
  1. private void populateBean(Object bean, Class<?> clazz) throws IllegalAccessException {  // 解析class信息,判斷類中是否有需要進行依賴注入的字段  final Field[] fields = clazz.getDeclaredFields();  for (Field field : fields) {    Autowired autowired = field.getAnnotation(Autowired.class);    if(autowired != null){      // 獲取bean      Object value = this.resolveBean(field.getType());      field.setAccessible(true);      field.set(bean, value);    }  }} 
  1. private Object resolveBean(Class<?> clazz){  // 先判斷clazz是否為一個接口,是則判斷classMap中是否存在子類  if(clazz.isInterface()){    // 暫時只支持classMap只有一個子類的情況    for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) {      if (clazz.isAssignableFrom(entry.getValue())) {        return getBean(entry.getValue());      }    }    throw new RuntimeException("找不到可以進行依賴注入的bean");  }else {    return getBean(clazz);  }} 
  1. public Object getBean(Class<?> clazz){ 
  2.   // 生成bean的名稱 
  3.   String beanName = this.generateBeanName(clazz); 
  4.   // 此處對應最開始的getBean方法 
  5.   return this.getBean(beanName); 

組合

兩個核心方法已經寫好了,接下把它們組合起來,我把它們實現在自定義的ApplicationContext類中,構造方法如下:

  1. public ApplicationContext(Class<?> configClass) { 
  2.   // 1.掃描配置信息中指定包下的類 
  3.   this.scan(configClass); 
  4.   // 2.實例化掃描到的類 
  5.   this.instantiateBean(); 

UML類圖:

深入淺出Spring架構設計

測試

代碼結構與案例相同,這里展示一下我們自己的Spring是否可以正常運行

深入淺出Spring架構設計

運行正常,中國人不騙中國人

源碼會在文末給出

回顧

現在,我們已經根據設想的方案進行了實現,運行的情況也達到了預期的效果。但如果仔細研究一下,再結合我們平常使用Spring的場景,就會發現這一份代碼的不少問題:

1、無法支持構造器注入,當然也沒有支持方法注入,這是屬于功能上的缺失。

2、加載類信息的問題,加載類時我們使用的是classLoader.loadClass的方式,雖然這避免了類的初始化(可千萬別用Class.forName的方式),但還是不可避免的把類元信息加載到了元空間中,當我們掃描包下有不需要的類時,這就浪費了我們的內存。

3、無法解決bean之間的循環依賴,比如有一個A對象依賴了B對象, B對象又依賴了A對象,這個時候我們再來看看代碼邏輯,就會發現此時會陷入死循環。

4、擴展性很差,我們把所有的功能都寫在一個類里,當想要完善功能(比如以上3個問題)時,就需要頻繁修改這個類,這個類也會變得越來越臃腫,別說迭代新功能,維護都會令人頭疼。

優化方案

對于前三個問題都類似于功能上的問題,功能嘛,改一改就好了。

我們需要著重關注的是第四個問題,一款框架想要變得優秀,那么它的迭代能力一定要好,這樣功能才能變得豐富,而迭代能力的影響因素有很多,其中之一就是它的擴展性。

那么應該如何提高我們的方案的擴展性呢,六大設計原則給了我們很好的指導作用。

在方案中,ApplicationContext做了很多事情, 主要可以分為兩大塊

1、掃描指定包下的類

2、實例化Bean

借助單一職責原則的思想:一個類只做一種事,一個方法只做一件事。

我們把掃描指定包下的類這件事單獨使用一個處理器進行處理,因為掃描配置是從配置類而來,那我們就叫他配置類處理器:ConfigurationCalssProcessor

深入淺出Spring架構設計

實例化Bean這件事情也同樣如此,實例化Bean又分為了兩件事:實例化和依賴注入

實例化Bean就是相當于一個生產Bean的過程,我們就把這件事使用一個工廠類進行處理,它就叫做:BeanFactory,既然是在生產Bean,那就需要原料(Class),所以我們把classMap和beanMap都定義到這里

深入淺出Spring架構設計

而依賴注入的過程,其實就是在處理Autowired注解,那它就叫做: AutowiredAnnotationBeanProcessor

深入淺出Spring架構設計

我們還在知道,在Spring中,不僅僅只有這種使用方式,還有xml,mvc,SpringBoot的方式,所以我們將ApplicationContext進行抽象,只實現主干流程,原來的注解方式交由AnnotationApplicationContext實現。

深入淺出Spring架構設計

借助依賴倒置原則:程序應當依賴于抽象

在未來,類信息不僅僅可以從類信息來,也可以從配置文件而來,所以我們將ConfigurationCalssProcessor抽象

深入淺出Spring架構設計

而依賴注入的方式不一定非得是用Autowried注解標識,也可以是別的注解標識,比如Resource,所以我們將AutowiredAnnotationBeanProcessor抽象

深入淺出Spring架構設計

Bean的類型也可以有很多,可以是單例的,可以使多例的,也可以是個工廠Bean,所以我們將BeanFactory抽象

深入淺出Spring架構設計

現在,我們借助兩大設計原則對我們的方案進行了優化,相比于之前可謂是”脫胎換骨“。

Spring的設計

在上一步,我們實現了自己的方案,并基于一些設想進行了擴展性優化,現在,我們就來認識一下實際上Spring的設計

那么,在Spring中又是由哪些"角色"構成的呢?

深入淺出Spring架構設計

1、Bean: Spring作為一個IoC容器,最重要的當然是Bean咯

2、BeanFactory: 生產與管理Bean的工廠

3、BeanDefinition: Bean的定義,也就是我們方案中的Class,Spring對它進行了封裝

4、BeanDefinitionRegistry: 類似于Bean與BeanFactory的關系,BeanDefinitionRegistry用于管理BeanDefinition

5、BeanDefinitionRegistryPostProcessor: 用于在解析配置類時的處理器,類似于我們方案中的ClassProcessor

6、BeanFactoryPostProcessor: BeanDefinitionRegistryPostProcessor父類,讓我們可以再解析配置類之后進行后置處理

7、BeanPostProcessor: Bean的后置處理器,用于在生產Bean的過程中進行一些處理,比如依賴注入,類似我們的AutowiredAnnotationBeanProcessor

8、ApplicationContext: 如果說以上的角色都是在工廠中生產Bean的工人,那么ApplicationContext就是我們Spring的門面,ApplicationContext與BeanFactory是一種組合的關系,所以它完全擴展了BeanFactory的功能,并在其基礎上添加了更多特定于企業的功能,比如我們熟知的ApplicationListener(事件監聽器)

以上說的類似其實有一些本末倒置了,因為實際上應該是我們方案中的實現類似于Spring中的實現,這樣說只是為了讓大家更好的理解

我們在經歷了自己方案的設計與優化后,對這些角色其實是非常容易理解的

接下來,我們就一個一個的詳細了解一下

BeanFactory

BeanFactory是Spring中的一個頂級接口,它定義了獲取Bean的方式,Spring中還有另一個接口叫SingletonBeanRegistry,它定義的是操作單例Bean的方式,這里我將這兩個放在一起進行介紹,因為它們大體相同,SingletonBeanRegistry的注釋上也寫了可以與BeanFactory接口一起實現,方便統一管理。

BeanFactory

深入淺出Spring架構設計

1、ListableBeanFactory:接口,定義了獲取Bean/BeanDefinition列表相關的方法,如getBeansOfType(Class type)

2、AutowireCapableBeanFactory:接口,定義了Bean生命周期相關的方法,如創建bean, 依賴注入,初始化

3、AbstractBeanFactory:抽象類,基本上實現了所有有關Bean操作的方法,定義了Bean生命周期相關的抽象方法

4、AbstractAutowireCapableBeanFactory:抽象類,繼承了AbstractBeanFactory,實現了Bean生命周期相關的內容,雖然是個抽象類,但它沒有抽象方法

5、DefaultListableBeanFactory:繼承與實現以上所有類和接口,是為Spring中最底層的BeanFactory, 自身實現了ListableBeanFactory接口

6、ApplicationContext:也是一個接口,我們會在下面有專門對它的介紹

SingletonBeanRegistry

深入淺出Spring架構設計

1、DefaultSingletonBeanRegistry: 定義了Bean的緩存池,類似于我們的BeanMap,實現了有關單例的操作,比如getSingleton(面試常問的三級緩存就在這里)

2、FactoryBeanRegistrySupport:提供了對FactoryBean的支持,比如從FactoryBean中獲取Bean

BeanDefinition

BeanDefinition其實也是個接口(想不到吧),這里定義了許多和類信息相關的操作方法,方便在生產Bean的時候直接使用,比如getBeanClassName

它的大概結構如下(這里舉例RootBeanDefinition子類):

深入淺出Spring架構設計

里面的各種屬性想必大家也絕不陌生

同樣的,它也有許多實現類:

深入淺出Spring架構設計

1、AnnotatedGenericBeanDefinition:解析配置類與解析Import注解帶入的類時,就會使用它進行封裝

2、ScannedGenericBeanDefinition:封裝通過@ComponentScan掃描包所得到的類信息

3、ConfigurationClassBeanDefinition:封裝通過@Bean注解所得到的類信息

4、RootBeanDefinition:ConfigurationClassBeanDefinition父類,一般在Spring內部使用,將其他的BeanDefition轉化成該類

BeanDefinitionRegistry

定義了與BeanDefiniton相關的操作,如registerBeanDefinition,getBeanDefinition,在BeanFactory中,實現類就是DefaultListableBeanFactory

BeanDefinitionRegistryPostProcessor

插話:講到這里,有沒有發現Spring的命名極其規范,Spring團隊曾言Spring中的類名都是反復推敲才確認的,真是名副其實呀,所以看Spring源碼真的是一件很舒服的事情,看看類名方法名就能猜出它們的功能了。

該接口只定義了一個功能:處理BeanDefinitonRegistry,也就是解析配置類中的Import、Component、ComponentScan等注解進行相應的處理,處理完畢后將這些類注冊成對應的BeanDefinition

在Spring內部中,只有一個實現:ConfigurationClassPostProcessor

BeanFactoryPostProcessor

所謂BeanFactory的后置處理器,它定義了在解析完配置類后可以調用的處理邏輯,類似于一個插槽,如果我們想在配置類解析完后做點什么,就可以實現該接口。

在Spring內部中,同樣只有ConfigurationClassPostProcessor實現了它:用于專門處理加了Configuration注解的類

這里串場一個小問題,如知以下代碼:

  1. @Configuraiton 
  2. public class MyConfiguration{ 
  3.   @Bean 
  4.   public Car car(){ 
  5.       return new Car(wheel()); 
  6.   } 
  7.   @Bean 
  8.   public Wheel wheel(){ 
  9.       return new Wheel(); 
  10.   } 

問:Wheel對象在Spring啟動時,被new了幾次?為什么?

BeanPostProcessor

江湖翻譯:Bean的后置處理器

該后置處理器貫穿了Bean的生命周期整個過程,在Bean的創建過程中,一共被調用了9次,至于哪9次我們下次再來探究,以下介紹它的實現類以及作用

深入淺出Spring架構設計

1、AutowiredAnnotationBeanPostProcessor:用于推斷構造器進行實例化,以及處理Autowired和Value注解

2、CommonAnnotationBeanPostProcessor:處理Java規范中的注解,如Resource、PostConstruct

3、ApplicationListenerDetector: 在Bean的初始化后使用,將實現了ApplicationListener接口的bean添加到事件監聽器列表中

4、ApplicationContextAwareProcessor:用于回調實現了Aware接口的Bean

5、ImportAwareBeanPostProcessor: 用于回調實現了ImportAware接口的Bean

ApplicationContext

ApplicationContext作為Spring的核心,以門面模式隔離了BeanFactory,以模板方法模式定義了Spring啟動流程的骨架,又以策略模式調用了各式各樣的Processor......實在是錯綜復雜又精妙絕倫!

它的實現類如下:

深入淺出Spring架構設計

1、ConfigurableApplicationContext:接口,定義了配置與生命周期相關操作,如refresh

2、AbstractApplicationContext: 抽象類,實現了refresh方法,refresh方法作為Spring核心中的核心,可以說整個Spring皆在refresh之中,所有子類都通過refresh方法啟動,在調用該方法之后,將實例化所有單例

3、AnnotationConfigApplicationContext: 在啟動時使用相關的注解讀取器與掃描器,往Spring容器中注冊需要用的處理器,而后在refresh方法在被主流程調用即可

4、AnnotationConfigWebApplicationContext:實現loadBeanDefinitions方法,以期在refresh流程中被調用,從而加載BeanDefintion

5、ClassPathXmlApplicationContext: 同上

從子類的情況可以看出,子類的不同之處在于如何加載BeanDefiniton, AnnotationConfigApplicationContext是通過配置類處理器(ConfigurationClassPostProcessor)加載的,而AnnotationConfigWebApplicationContext與ClassPathXmlApplicationContext則是通過自己實現loadBeanDefinitions方法,其他流程則完全一致

Spring的流程

以上,我們已經清楚了Spring中的主要角色以及作用,現在我們嘗試把它們組合起來,構建一個Spring的啟動流程

同樣以我們常用的AnnotationConfigApplicationContext為例

深入淺出Spring架構設計

圖中只畫出了Spring中的部分大概流程,詳細內容我們會在后面的章節展開

小結

所謂萬事開頭難,本文初衷就是能讓大家以由淺入深的方式認識Spring,初步建立Spring的認知體系,明白Spring的內部架構,對Spring的認知不再浮于表面。

現在頭已經開了,相信后面內容的學習也將水到渠來。

本篇文章既講是Spring的架構設計,也希望能成為我們以后復習Spring整體內容時使用的手冊。

最后,看完文章之后,相信對以下面試常問的問題回答起來也是輕而易舉

1、什么是BeanDefinition?

2、BeanFactory與ApplicationContext的關系?

3、后置處理器的分類與作用?

4、Spring的主要流程是怎么樣的?

如果小伙伴覺得沒辦法很好回答上來的話就再看看文章,或者在評論區留下自己的見解吧

好啦,我是敖丙,你知道的越多,你不知道的越多,我們下期見。

原文鏈接:https://mp.weixin.qq.com/s/wXkgudY0ThIRLFxUmohJog

延伸 · 閱讀

精彩推薦
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
主站蜘蛛池模板: 极品一级片 | 成人小视频在线播放 | 国产交换3p国产精品 | 精品午夜影院 | 国产剧情在线观看一区二区 | 99精品视频免费看 | 欧美a黄| 91九色网址 | 国产一区二区三区精品在线观看 | 亚洲狠狠入 | 亚洲精品日韩色噜噜久久五月 | 青青草免费观看完整版高清 | 99久久久精品 | 成人18在线 | 亚洲国产网站 | 精品一区二区三区日本 | 成年免费网站 | 国产午夜精品视频免费不卡69堂 | 免费看成人av | 一级做a爱性色毛片免费1 | 日韩视频在线一区二区三区 | 日本a v免费观看 | 国产一区二区三区视频免费 | 色播av在线 | 日韩字幕 | 午夜视频中文字幕 | 亚洲热线99精品视频 | 极品大长腿啪啪高潮露脸 | 亚洲免费毛片基地 | 成人福利免费在线观看 | 亚洲欧美不卡视频 | 欧美亚成人 | sesee99| 香蕉久久久久久 | 中国黄色一级生活片 | 国产三级在线视频观看 | 国产男女爽爽爽爽爽免费视频 | 精品人伦一区二区三区蜜桃网站 | av播播| 久久成人激情视频 | 做羞羞视频|