1. 程式人生 > >品Spring:實現bean定義時採用的“先進生產力”

品Spring:實現bean定義時採用的“先進生產力”


前景回顧


當我們把寫好的業務程式碼交給Spring之後,Spring都會做些什麼呢?

仔細想象一下,再稍微抽象一下,Spring所做的幾乎全部都是:

“bean的例項化,bean的依賴裝配,bean的初始化,bean的方法呼叫,bean的銷燬回收”。

那問題來了,Spring為什麼能夠準確無誤的完成這波對bean的操作呢?答案很簡單,就是:

“Spring掌握了有關bean的足夠多的資訊”。

這就是本系列文章第一篇“帝國的基石”的核心思想。Spring通過bean定義的概念收集到了bean的全部資訊。

這件事也說明,當我們擁有了一個事物的大量有效資訊之後,就可以做出一些非常有價值的操作。如大資料分析,使用者畫像等。


緊接著就是第二個問題,Spring應該採用什麼樣的方式來收集bean的資訊呢?

這就是本系列文章第二篇“bean定義上梁山”主要講的內容。

首先是統一了程式設計模型,只要是圍繞Spring的開發,包括框架自身的開發,最後大都轉化為bean定義的註冊。

為了滿足不同的場景,Spring提供了兩大類的bean定義註冊方式:

實現指定介面,採用寫程式碼的方式來註冊,這是非常靈活的動態註冊,根據不同的條件註冊不同的bean,主要用於第三方元件和Spring的整合。

標上指定註解,採用註解掃描的方式來註冊,這相當於一種靜態的註冊,非常不靈活,但特別簡單易用,主要用於普通業務程式碼的開發。

Spring設計的這一切,看起來確實完美,用起來也確實很爽,但實現起來呢,也確實的非常麻煩。

尤其是在全部採用註解和Java配置的時候,那才叫一個繁瑣,看看原始碼便知一二。

所以本篇及接下來的幾篇都會寫一些和實現細節相關的內容,俗稱“乾貨”,哈哈。


最容易想到的實現方案


一個bean其實就是一個類,所以bean的資訊就是類的資訊。

那一個類都有哪些資訊呢,閉著眼睛都能說出來,共四大類資訊:

型別資訊,類名,父類,實現的介面,訪問控制/修飾符

欄位資訊,欄位名,欄位型別,訪問控制/修飾符

方法資訊,方法名,返回型別,引數型別,訪問控制/修飾符

註解資訊,類上的註解,欄位上的註解,方法上的註解/方法引數上的註解

注:還有內部類/外部類這些資訊,也是非常重要的。

看到這裡腦海中應該立馬蹦出兩個字,沒錯,就是反射。

但是,Spring並沒有採用反射來獲取這些資訊,個人認為可能有以下兩個大的原因:

效能損耗問題:

要想使用反射,JVM必須先載入類,然後生成對應的Class<?>物件,最後快取起來。

實際的工程可能會註冊較多的bean,但是真正執行時不一定都會用得到。

所以JVM載入過多的類,不僅會耗費較多的時間,還會佔用較多的記憶體,而且載入的類很多可能都不用。

資訊完整度問題:

JDK在1.8版本中新增加了一些和反射相關的API,比如和方法引數名稱相關的。此時才能使用反射獲取相對完善的資訊。

但Spring很早就提供了對註解的支援,所以當時的反射並不完善,也可能是通過反射獲取到的資訊並不能完全符合要求。

總之,Spring沒有選擇反射。

那如何獲取類的這些資訊呢?答案應該只剩一種,就是直接從位元組碼檔案中獲取。


採用先進的生產力


原始碼經過編譯變成位元組碼,所以原始碼中有的資訊,在位元組碼中肯定都有。只不過換了一種存在的形式。

Java原始碼遵循Java語法規範,生成的位元組碼遵循JVM中的位元組碼規範。

位元組碼檔案的結構確實有些複雜,應用程式想要直接從位元組碼中讀出需要的資訊也確實有些困難。

小平同志曾說過,“科學技術是第一生產力”。所以要解決複雜的問題,必須要有比較可靠的技術才行。

對於複雜的位元組碼來說,先進的生產力就是ASM了。ASM是一個小巧快速的Java位元組碼操作框架。

它既可以讀位元組碼檔案,也可以寫位元組碼檔案。Spring框架主要用它來讀取位元組碼。

ASM框架是採用訪問者模式設計出來的,如果不熟悉這個設計模式的可以閱讀本公眾號上一篇文章“趣說訪問者模式”。

該模式的核心思想就是,訪問者按照一定的規則順序進行訪問,期間會自動獲取到相關資訊,把有用的資訊儲存下來即可。

下面介紹一下ASM的具體使用方式,可以看看作為了解,說不定以後會用到。哈哈。

ASM定義了ClassVisitor來獲取型別資訊,AnnotationVisitor來獲取註解資訊,FieldVisitor來獲取欄位資訊,MethodVisitor來獲取方法資訊。

先準備好產生位元組碼的素材,其實就是一個類啦,這個類僅作測試使用,不用考慮是否合理,如下:

@Configuration("ddd")
@ComponentScan(basePackages = {"a.b.c", "x.y.z"},
scopedProxy = ScopedProxyMode.DEFAULT,
includeFilters = {@Filter(classes = Integer.class)})
@Ann0(ann1 = @Ann1(name = "ann1Name"))
public class D<@Null T extends Number> extends C<@Valid Long, @NotNull Date> implements A, B {

    protected Long lon = Long.MAX_VALUE;

    private String str;

    @Autowired(required = false)
    private Date date;

    @Resource(name = "aaa", lookup = "bbb")
    private Map<@NotNull String, @Null Object> map;

    @Bean(name = {"cc", "dd"}, initMethod = "init")
    public String getStr(@NotNull String sssss, @Null int iiiii, double dddd, @Valid long llll) throws Exception {
        return sssss;
    }

    @Override
    public double getDouble(double d) {
        return d;
    }
}


這個類裡面包含了較為全面的資訊,泛型、父類、實現的介面、欄位、方法、註解等。

按照ASM規定的訪問順序,首先訪問型別資訊,使用ClassVisitor的visit方法,如下:

@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
    log("---ClassVisitor-visit---");
    log("version", version);
    log("access", access);
    log("name", name);
    log("signature", signature);
    log("superName", superName);
    log("interfaces", Arrays.toString(interfaces));
}


這個方法會由ASM框架呼叫,方法引數的值是框架傳進來的,我們要做的只是在方法內部把這些引數值儲存下來就行了。

然後可以按照自己的需求去解析和使用,我這裡只是簡單輸出一下。如下:

//版本資訊,52表示的是JDK1.8
version = 52
//訪問控制資訊,表示的是public class
access = 33
//型別的名稱
name = org/cnt/ts/asm/D
//型別的簽名,依次為,本類的泛型、父類、父類的泛型、實現的介面
signature = <T:Ljava/lang/Number;>Lorg/cnt/ts/asm/C<Ljava/lang/Long;Ljava/util/Date;>;Lorg/cnt/ts/asm/A;Lorg/cnt/ts/asm/B;
//父型別的名稱
superName = org/cnt/ts/asm/C
//實現的介面
interfaces = [org/cnt/ts/asm/A, org/cnt/ts/asm/B]


現在我們已經獲取到了這些資訊,雖然我們並不知道它是如何在位元組碼中存著的,這就是訪問者模式的好處。

型別名稱都是以斜線“/”分割,是因為斜線是路徑分隔符,可以非常方便的拼出完整路徑,從磁碟上讀取.class檔案的內容。

還有以大寫“L”開頭後跟一個型別名稱的,這個大寫L表示的是“物件”的意思,後跟的就是物件的型別名稱,說白了就是類、介面、列舉、註解等這些。

接著訪問的是型別上標的註解,使用ClassVisitor的visitAnnotation方法,如下:

@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
    log("---ClassVisitor-visitAnnotation---");
    log("descriptor", descriptor);
    log("visible", visible);
    return new _AnnotationVisitor();
}


需要說明的是,這個方法只能訪問到註解的型別資訊,註解的屬性資訊需要使用AnnotationVisitor去訪問,也就是這個方法的返回型別。

類上標有@Configuration("ddd"),所以輸出結果如下:

//型別描述/名稱
descriptor = Lorg/springframework/context/annotation/Configuration;
//這個是可見性,表明在執行時可以獲取到註解的資訊
visible = true


然後使用AnnotationVisitor去訪問顯式設定過的註解屬性資訊,使用visit方法訪問基本的資訊,如下:

@Override
public void visit(String name, Object value) {
    log("---AnnotationVisitor-visit---");
    log("name", name);
    log("value", value);
}


實際上我們是把ddd設定給了註解的value屬性,所以結果如下:

//屬性名稱,是value
name = value
//屬性值,是ddd
value = ddd


至此,@Configuration註解已經訪問完畢。

然後再訪問@ComponentScan註解,同樣使用ClassVisitor的visitAnnotation方法,和上面的那個一樣。

得到的結果如下:

descriptor = Lorg/springframework/context/annotation/ComponentScan;
visible = true


然後使用AnnotationVisitor去訪問設定過的註解屬性資訊,使用visitArray方法訪問陣列型別的資訊,如下:

@Override
public AnnotationVisitor visitArray(String name) {
    log("---AnnotationVisitor-visitArray---");
    log("name", name);
    return new _AnnotationVisitor();
}


這個方法只能訪問到陣列型別屬性的名稱,結果如下:

name = basePackages


屬性的值還是使用基本的visit方法去訪問,因為陣列的值是多個,所以visit方法會多次呼叫,按順序依次獲取陣列的每個元素值。

因陣列有兩個值,所以方法呼叫兩次,結果如下:

name = null
value = a.b.c

name = null
value = x.y.z


因為陣列的值沒有名稱,所以name總是null。value的值就是陣列的元素值,按先後順序儲存在一起即可。

然後由於註解的下一個屬性是列舉型別的,所以使用visitEnum方法來訪問,如下:


            
           

相關推薦

Spring實現bean定義採用的“先進生產力

前景回顧當我們把寫好的業務程式碼交給Spring之後,Spring都會做些什麼呢?仔細想象一下,再稍微抽象一下,Spring所做的幾乎全部都是:“bean的例項化,bean的依賴裝配,bean的初始化,bean的方法呼叫,bean的銷燬回收”。那問題來了,Spring為什麼能夠準確無誤

Spring負責bean定義註冊的兩個“排頭兵”

別看Spring現在玩的這麼花,其實它的“籌碼”就兩個,“容器”和“bean定義”。只有先把bean定義註冊到容器裡,後續的一切可能才有可能成為可能。所以在進階的路上如果要想走的順暢些,徹底搞清楚bean定義註冊的所有細節至關重要。畢竟這

Spring註解實現Bean定義

12.3.1  概述 前邊介紹的Bean定義全是基於XML方式定義配置元資料,且在【12.2註解實現Bean依賴注入】一節中介紹了通過註解來減少配置數量,但並沒有完全消除在XML配置檔案中的Bean定義,因此有沒有方式完全消除XML配置Bean定義呢?

Springbean定義上梁山

認真閱讀,收穫滿滿,向智慧又邁進一步。。。技術不枯燥,先來點閒聊先說點好事高興一下。前段時間看新聞說,我國正式的空間站建設已在進行當中。下半年,長征五號B運載火箭將在海南文昌航天發射場擇機將空間站核心艙發射升空。預計用2到3年將空間站建好。雖然到時你們不讓我上去,不過我也為這件事出不了什麼力,算扯平了。哈哈,

SpringSpringBoot輕鬆取勝bean定義註冊的“第一階段”

上一篇文章強調了bean定義註冊佔Spring應用的半壁江山。而且詳細介紹了兩個重量級的註冊bean定義的類。今天就以SpringBoot為例,來看看整個SpringBoot應用的bean定義是如何註冊進容器的。先來看看經典的啟動入口,如下圖01: 可以看到呼叫的是run方法,並把主類(main或pr

SpringSpringBoot發起bean定義註冊的“二次攻堅戰”

上一篇文章整體非常輕鬆,因為在容器啟動前,只註冊了一個bean定義,就是SpringBoot的主類。OK,今天接著從容器的啟動入手,找出剩餘所有的bean定義的註冊過程。具體細節肯定會頗為複雜,同樣,大家只需關注都幹了什麼,不用考慮如何幹的。來巨集觀的看下容器的啟動過程,即refresh方法,如下圖01:

Springbean工廠後處理器的呼叫規則

上一篇文章介紹了對@Configuration類的處理邏輯,這些邏輯都寫在ConfigurationClassPostProcessor類中。 這個類不僅是一個“bean工廠後處理器”,還是一個“bean定義註冊後處理器”。這其實是兩個介面,它們都是來操作be

Spring詳細解說bean後處理器

一個小小的里程碑首先感謝能看到本文的朋友,感謝你的一路陪伴。如果每篇都認真看的話,會發現本系列以bean定義作為切入點,先是詳細解說了什麼是bean定義,接著又強調了bean定義為什麼如此重要。然後又講了獲取bean定義詳細資訊的方法,接著又講了bean定義註冊的若干種方式,然後是bean定義註冊方式的實現細

Spring真沒想到,三十步才能完成一個bean例項的建立

在容器啟動快完成時,會把所有的單例bean進行例項化,也可以叫做預先例項化。這樣做的好處之一是,可以及早地發現問題,及早的丟擲異常,及早地解決掉。本文就來看下整個的例項化過程。其實還是比較繁瑣的。一、從容器中找出所有的bean定義名稱因為不知道誰是單例bean,所以只能先全部找出來。如下圖01: 二、

spring mvc實現定義註解

poi org param 運行時 onf dha ogg logs exec 實現方式:使用@Aspect實現: 1. 新建註解接口:CheckSign package com.soeasy.web.utils; import org.springframework.

Spring(五)之Bean定義繼承和依賴註入

dia 繼承 map() path 形式 集合 .text inf ima 一、Bean定義繼承 bean定義可以包含許多配置信息,包括構造函數參數,屬性值和特定於容器的信息,例如初始化方法,靜態工廠方法名稱等。 子bean定義從父定義繼承配置數據。子定義可以根據需要覆蓋某

spring3零配置註解實現Bean定義(包括JSR-250、JSR-330)

 註解實現Bean定義  概述 前邊介紹的Bean定義全是基於XML方式定義配置元資料,且在【12.2註解實現Bean依賴注入】一節中介紹了通過註解來減少配置數量,但並沒有完全消除在XML配置檔案中的Bean定義,因此有沒有方式完全消除XML配置Be

Spring Boot 實現定義錯誤頁面

sprin 支援實現ErrorController 來自定義錯誤頁面 下面是具體程式碼的實現 @Controller public class CustomErrorController implements ErrorController { @Autowired

潤乾報表設計器中使用 spring 框架實現定義資料集

spring是一個開源框架,是為了解決企業應用程式開發複雜性而建立的。在 web 開發環境中經常會與 struts、hibernate聯合起來使用,進行規範的框架結構開發。潤乾中的二次開發也可以與 SSH 框架相結合部署到 web 專案中。本文介紹在設計時沒有啟動 web 服務,在設計器中使用 sp

[學習筆記]Spring註解實現Bean

12.4  基於Java類定義Bean配置元資料 12.4.1  概述 基於Java類定義Bean配置元資料,其實就是通過Java類定義Spring配置元資料,且直接消除XML配置檔案。 基於Java類定義Bean配置元資料中的@Configuration註解的類等價於XML配置檔案

torch學習筆記3.2實現定義模組(cpu)

在使用torch時,如果想自己實現一個層,則可以按照《torch學習筆記1:實現自定義層》 中的方法來實現。但是如果想要實現一個比較複雜的網路,往往需要自己實現多個層(或類),並且有時可能需要重寫其他模組中已有的函式來達到自己的目的,如果還是在nn模組中新

spring Aspect 實現定義註解的日誌記錄,有時候註解類不起作用的原因分析

使用只要在controller的method上加上@ActionLog(actionGroup = "freeorder",actionType = "update",actionDesc = "操作",insertDb = true)其中insertDb 代表是否插入資料

spring中建立bean物件多例和單例的區別

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w

torch學習筆記3.3實現定義模組(gpu)

在使用torch時,如果想自己實現一個層,則可以按照《torch學習筆記1:實現自定義層》 中的方法來實現。但是如果想要實現一個比較複雜的網路,往往需要自己實現多個層(或類),並且有時可能需要重寫其他模組中已有的函式來達到自己的目的,如果還是在nn模組中新

Spring 註解實現Bean依賴注入之@Qualifier

三、@Qualifier:限定描述符,用於細粒度選擇候選者; @Autowired預設是根據型別進行注入的,因此如果有多個型別一樣的Bean候選者,則需要限定其中一個候選者,否則將丟擲異常 @Qualifier限定描述符除了能根據名字進行注入,更能進行更細粒度的控制如何選擇