1. 程式人生 > >品Spring:能工巧匠們對註解的“加持”

品Spring:能工巧匠們對註解的“加持”

問題的描述與方案的提出


在Spring從XML轉向註解時,為了自身的開發方便,對註解含義進行了擴充(具體參考本號上一篇文章)。

這個擴充直接導致了一個問題,就是需要從註解往元註解以及元元註解(即沿著從下向上的方向)裡傳遞資料。

為了更好的描述這個問題,請再看個示例:

@interface A {

    String a() default "";
}

@A
@interface B {

    String a() default "";

    String b() default "";
}

@B
@interface C {

    String a() default "";

    String b() default "";

    String c() default "";
}

@C(a = "a", b = "b", c = "c")
class D {

}

這裡共有三個註解@A、@B、@C。@A的級別最高,@C的級別最低,資料傳遞是從@C向@A,所以是從下向上。

溫馨提示,請務必明白註解級別的高低,什麼是高級別註解,什麼是低級別註解,本文會一直這樣稱呼。

我們最終設定的是註解@C的a、b、c三個屬性。同時急切期望的是註解@C能把a、b屬性傳遞給註解@B,註解@B能把a屬性傳遞給註解@A。

這個期望對Spring來說非常重要,可惜了,Java註解在這方面是空白的。不過Spring早已司空見慣,不必大驚小怪,沒有路就自己開一條。

首先要解決的就是一個對映的問題,總得知道哪個註解的哪個屬性對映到哪個註解的哪個屬性吧,其實就是需要再額外附加上一個對映資訊。

Spring給出的方案很簡單,就是用一個註解@AliasFor,把它標註在需要傳遞資料的屬性上,並指明傳遞給誰(哪個註解的哪個屬性)。

再次呼應一下上一篇文章的主題,要新增的對映資訊是額外附加資訊,這正是註解的功能所在呀,所以現在就是用註解去解決註解的問題。哈哈。

這樣一來的話,上面的示例就變為:

@interface A {

    String a() default "";
}

@A
@interface B {

    @AliasFor(annotation = A.class, attribute = "a")
    String a() default "";

    String b() default "";
}

@B
@interface C {

    @AliasFor(annotation = B.class, attribute = "a")
    String a() default "";

    @AliasFor(annotation = B.class, attribute = "b")
    String b() default "";

    String c() default "";
}

通過簡單的標註,就已經建立起了對映關係,可以說已經對上號了,至於程式碼如何實現則另當別論啦。

@AliasFor也有一些簡潔用法:

1)如果對映的屬性名稱一樣,則可以不指定屬性名,即attribute不用設定。

2)如果對映的是同一個註解裡的屬性名,則可以不指定註解,即annotation不用設定。

3)這個對映是可以跳級的,可以從註解跳過元註解直接對映到元元註解。

下面請再看下@AliasFor的原始碼:

public @interface AliasFor {

    @AliasFor("attribute")
    String value() default "";

    @AliasFor("value")
    String attribute() default "";

    Class<? extends Annotation> annotation() default Annotation.class;
}



@AliasFor註解的三種用法


一)註解內部的顯式別名

看下面這個註解:

@interface A {

    @AliasFor("y")
    String x() default "";

    @AliasFor("x")
    String y() default "";
}

在一個註解裡面,把一對屬性相互指定為對方的別名。

注意,只能是一對,即兩個,不能是三個及以上。

至於為什麼會有這種用法,我給出一個猜測吧。

Java註解規定:

當只需設定註解的一個屬性時,且屬性名稱是“value”,設定時可以省略屬性名稱。

看個示例:

@interface E {

    String value() default "";
}

在使用這個註解時有兩種寫法,@E(value = "abc") 和 @E("abc"),它倆是一回事,但後者要簡潔很多。

這裡有個問題,就是屬性名必須是“value”才行,但value這個名字很中性,不能很好的表達意圖。

所以很多時候,都會定義更有意義的屬性名,比如用“name”表示名稱要比value好得多,如下:

@interface E {

    String value() default "";

    String name() default "";
}

但是又想使用剛才那種簡潔的用法,咋辦呢?讓它倆互為別名即可,如下:

@interface E {

    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";
}

現在value和name已經互為對方的別名,所以@E("abc")、@E(name = "abc")都是一樣的,顯然前者更好看。

不知你是否注意到@AliasFor註解的原始碼本身就已經使用了這種方法。

二)元註解中屬性顯式別名

看個示例:

@E
@interface F {

    @AliasFor(annotation = E.class, attribute = "name")
    String id() default "";
}

這樣就把註解@F的id屬性對映為元註解@E的name屬性了。

三)註解內部的隱式別名

看個示例:

@F
@interface G {

    @AliasFor(annotation = F.class, attribute = "id")
    String a() default "";

    @AliasFor(annotation = F.class, attribute = "id")
    String b() default "";

    @AliasFor(annotation = E.class, attribute = "name")
    String c() default "";
}

屬性a和b都對映為註解@F的id屬性,所以它倆指向一處,因此互為別名。

註解@F的id屬性又對映為註解@E的name屬性,這恰好又和屬性c的對映一樣。

因此屬性a、b和c三者互相互為別名。因為它們雖然殊途但是同歸。

由此也可以看出,對映是具有傳遞性的。X -> Y -> Z,可以推匯出X -> Z。

這個對映的建立是有前提條件的:

1)屬性的返回型別必須是同一個

2)屬性必須要有預設值

3)屬性的預設值必須是同一個

在使用時也是有限制的:

1)互為別名的屬性只需設定其中任何一個即可

2)如果設定了其中多個,設定的值必須一樣。

這些其實很好理解,就像不管是大名、小名、筆名或曾用名,最後指向的必須是同一個人才行。

這就是Spring給出的方案,既簡單又直白,所見即所得啊,個人覺得這是上等的方案,也是鄙人的追求。

終於還是要走到該思索如何實現這一步,就像不管什麼樣的媳婦兒最終都要見公婆。那就勇敢面對吧。

不過可以預見的是,實現起來應該比較麻煩。因為不可能既有魔鬼身材又有天使面孔。

好事不會都讓一個人佔完,上帝對誰都是公平的。

長得帥的學習差,學習好的長的醜。哈哈哈哈。


從簡單處入手,先撿軟柿子捏


從Java語言規範中得知,註解就是一個介面(多了個@而已),註解的屬性就是介面方法,而且方法必須是無參的,返回值必須不能為void。

這些方法就相當於“getter”方法,啥意思呢?只讀的唄。明白了吧,就是註解的屬性值在執行時是無法修改的。

因此,我們在向上傳遞屬性值的時候,是不能像普通Java bean那樣,去設定屬性值的。所以只能想別的辦法。

先從最簡單的情況入手,看個示例:

@interface H {

    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";
}

@H("程式設計新說")
class K {

}

來讀取下註解的屬性看看,如下:

H h = K.class.getAnnotation(H.class);

h.value(); //"程式設計新說"

h.name(); //""

value屬性的值就是“程式設計新說”,因為我們設定的就是它,這毋庸置疑。

name屬性的值是空字串,因為我們沒有顯式設定它,所以是它的預設值。

這就是Java註解的表現行為,是正常合理的。

Spring想做的大家都知道了,就是標上@AliasFor註解後,name屬性也能返回“程式設計新說”,即使站在Java註解的角度來看,name屬性並沒有被設定。

相關推薦

Spring能工巧匠註解的“

問題的描述與方案的提出在Spring從XML轉向註解時,為了自身的開發方便,對註解含義進行了擴充(具體參考本號上一篇文章)。這個擴充直接導致了一個問題,就是需要從註解往元註解以及元元註解(即沿著從下向上的方向)裡傳遞資料。為了更好的描述這個問題,請再看個示例:@interface A {&

Spring註解之王@Configuration和它的一眾“小弟

其實對Spring的瞭解達到一定程度後,你就會發現,無論是使用Spring框架開發的應用,還是Spring框架本身的開發都是圍繞著註解構建起來的。空口無憑,那就說個最普通的例子吧。在Spring中要啟用一項XXX功能,標準做法就是用@EnableXXX這種“啟用”型別的註解。那麼這種型

Spring@PostConstruct和@PreDestroy註解的處理方法

在bean的例項化過程中,也會用到一系列的相關注解。如@PostConstruct和@PreDestroy用來標記初始化和銷燬方法。平常更多的是側重於應用,很少會有人去了解它背後發生的事情。今天就來看下它們的原始碼,這樣它們對你來說就不再是黑盒子了,而且學習原始碼對每個技術人來說都是必經之路。人們對事物的認知

Spring@Resource註解的處理方法

@Resource是Java的註解,表示一個資源,它具有雙向的含義,一個是從外部獲取一個資源,一個是向外部提供一個資源。這其實就對應於Spring的注入和註冊。當它用在欄位和方法上時,表示前者。當它用在類上時表示後者。Spring只提供了對前者的支援。該註解本身表示的是資源,資源的含義是很寬泛的。由於絕大部分

Spring@Autowired和@Value註解的處理方法

在Spring中能夠完成依賴注入的註解有JavaSE提供的@Resource註解,就是上一篇文章介紹的。還有JavaEE提供的@javax.inject.Inject註解,這個用的很少,因為一般都不會去引用JavaEE的jar包。程式設計新說注:JavaEE早已經被Oracle拋棄了。JavaEE這個名字已經

Spring依賴注入(註解方式)、泛型依賴注入

註解方式實現依賴注入支援手工裝配和自動裝配(慎用) 一般是宣告bean和bean直接的依賴關係的時候用比較好 使用註解方式時,也支援給Field注入值(在XML中不可以給Field注入)。另外就是setter方式注入。 @Resource註解在spring安裝目錄的lib\

Spring MVC 深入及註解的詳細講解

 核心原理 1.       使用者傳送請求給伺服器。url:user.do 2.       伺服器收到請求。發現Dispatchservlet可以處理。於是呼叫DispatchServlet。 3.       DispatchServlet內部,通過HandleM

Spring帝國的基石

序生活是一杯酒,有時需要麻醉自己,才能夠暫時忘卻痛苦與不快。生活是一杯茶,有時需要細細品味,才發現苦澀背後也會有甘甜。Spring是一杯酒,一眼望不到邊的官方文件,著實讓人難以下嚥。Spring是一杯茶,在無邊的原始碼中暢遊之後,發現色相味道俱全。高考狀元是六月份的網紅,Spring帝國是Java界的明星。狀

Springbean定義上梁山

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

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

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

SpringSpringBoot和Spring到底有沒有本質的不同?

現在的Spring相關開發都是基於SpringBoot的。最後在打包時可以把所有依賴的jar包都打進去,構成一個獨立的可執行的jar包。如下圖13: 使用java -jar命令就可以執行這個獨立的jar包。如下圖14: 這個jar包的執行入口就是一個main函式,典型的格式如下: @Spri

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

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

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關於@Scheduled定時任務的思考與探索,結果尷尬了

非Spring風格的程式碼與Spring的結合現在的開發都是基於Spring的,所有的依賴都有Spring管理,這沒有問題。但是要突然寫一些非Spring風格的程式碼時,可能會很不習慣,如果還要和Spring風格的程式碼結合起來的話,就會稍顯麻煩。因為非Spring風格的程式碼不由Spring管理,所以Spr

######Spring第5天ssh整合(註解版)難理解知識點【自定義 方法註解的方式】給dao注入sessionFactory屬性+【搞清@Autowared和@Resource含義】

===【方法加註解的方式】給dao注入sessionFactory屬性(繼承父類的屬性),第一次見!===具體怎麼用的,看原始碼。(後面有寫) 以前都是屬性+註解注入屬性。 [email protected]和@Resource到底是幹嘛的? 剛學IOC時可能能

day39-Spring 08-Spring的AOP基於AspectJ的註解

ima spring mage 開發 技術 asp day3 cnblogs ring 基於AspectJ的註解的開發要重點掌握. day39-Spring 08-Spring的AOP:基於AspectJ的註解