品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定義呢?
品Spring:bean定義上梁山
認真閱讀,收穫滿滿,向智慧又邁進一步。。。技術不枯燥,先來點閒聊先說點好事高興一下。前段時間看新聞說,我國正式的空間站建設已在進行當中。下半年,長征五號B運載火箭將在海南文昌航天發射場擇機將空間站核心艙發射升空。預計用2到3年將空間站建好。雖然到時你們不讓我上去,不過我也為這件事出不了什麼力,算扯平了。哈哈,
品Spring:SpringBoot輕鬆取勝bean定義註冊的“第一階段”
上一篇文章強調了bean定義註冊佔Spring應用的半壁江山。而且詳細介紹了兩個重量級的註冊bean定義的類。今天就以SpringBoot為例,來看看整個SpringBoot應用的bean定義是如何註冊進容器的。先來看看經典的啟動入口,如下圖01:
可以看到呼叫的是run方法,並把主類(main或pr
品Spring:SpringBoot發起bean定義註冊的“二次攻堅戰”
上一篇文章整體非常輕鬆,因為在容器啟動前,只註冊了一個bean定義,就是SpringBoot的主類。OK,今天接著從容器的啟動入手,找出剩餘所有的bean定義的註冊過程。具體細節肯定會頗為複雜,同樣,大家只需關注都幹了什麼,不用考慮如何幹的。來巨集觀的看下容器的啟動過程,即refresh方法,如下圖01:
品Spring:bean工廠後處理器的呼叫規則
上一篇文章介紹了對@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限定描述符除了能根據名字進行注入,更能進行更細粒度的控制如何選擇