您現在的位置是:首頁 > 運動
程式設計師:你想自己寫框架嗎?快速原始碼瞭解Java註解機制
什麼叫做註釋
無論是在JDK還是框架中,註解都是很重要的一部分,我們使用過很多註解,但是你有真正去了解過他的實現原理麼?你有去自己寫過註解麼?
概念
註解(Annotation),也叫元資料。一種程式碼級別的說明。它是JDK1。5及以後版本引入的一個特性,與類、介面、列舉是在同一個層次。它可以宣告在包、類、欄位、方法、區域性變數、方法引數等的前面,用來對這些元素進行說明,註釋。
在JDK中定義了許多註解,其作用大致可以分為以下幾類:
編寫文件:透過程式碼裡標識的元資料生成文件【生成文件doc文件】
程式碼分析:透過程式碼裡標識的元資料對程式碼進行分析【使用反射】
編譯檢查:透過程式碼裡標識的元資料讓編譯器能夠實現基本的編譯檢查【Override】
註解功能的實現
我們以spring中比較常見的Autowired來舉例分析
建立註解
@Target({ElementType。CONSTRUCTOR, ElementType。METHOD, ElementType。PARAMETER, ElementType。FIELD, ElementType。ANNOTATION_TYPE})
@Retention(RetentionPolicy。RUNTIME)
@Documented
public @interface Autowired {
/**
* Declares whether the annotated dependency is required。
*
Defaults to {@code true}。
*/
boolean required() default true;
}
註解的建立看起來很像介面,需要用@interface來修飾,然後我們看到在Autowired註釋之上還有三個註釋來進行修飾。
他們三個都叫做“元註釋”,Jdk5所定義的源註釋還有@Retention、@Documented、@Inherited,這些型別和它們所支援的類在java。lang。annotation包中可以找到。
@Target
用於描述註解的使用範圍,也就是在什麼時候生效,一般情況下,我們的註釋可能在packages、types(類、介面、列舉、Annotation型別)、型別成員(方法、構造方法、成員變數、列舉值)、方法引數和本地變數(如迴圈變數、catch引數)等任何一個地方生效。
@Documented
@Retention(RetentionPolicy。RUNTIME)
@Target(ElementType。ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to。
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
注:比較有意思的是,@Target也被自己修飾,這可能有更深層的原理,在此不再深入
透過原始碼我們看出@Target下有一個型別為ElementType[] 的值value(),進入ElementType[],這是一個列舉型別,他提供了我們對於這個值的選項,可供選擇的值有:
public enum ElementType {
/** 描述類、介面(包括註解型別) 或enum宣告 */
TYPE,
/** 描述域 */
FIELD,
/** 描述方法 */
METHOD,
/** 描述引數 */
PARAMETER,
/** 描述構造器*/
CONSTRUCTOR,
/** 描述本地值 */
LOCAL_VARIABLE,
/** 描述註解型別 */
ANNOTATION_TYPE,
/** 描述包 */
PACKAGE,
/**
* 描述型別引數
*
* @since 1。8
*/
TYPE_PARAMETER,
/**
* 一個型別的使用者
*
* @since 1。8
*/
TYPE_USE
}
在Autowired中我們給值是{ElementType。CONSTRUCTOR, ElementType。METHOD, ElementType。PARAMETER, ElementType。FIELD, ElementType。ANNOTATION_TYPE},也就是說,我們的這個註解可以用於建構函式、方法、引數、域,註解等等。
@Retention
用於描述註解的被保留的時間段,我們定義註解時有時候會希望它一直儲存,而不是在編譯時就被拋棄,就可以用到這個註解。
@Documented
@Retention(RetentionPolicy。RUNTIME)
@Target(ElementType。ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy。
* @return the retention policy
*/
RetentionPolicy value();
}
@Retention下有一個型別為Retention的值,可供選擇的值有:
public enum RetentionPolicy {
/**
* 在原始檔中有效(即原始檔保留)
*/
SOURCE,
/**
* 在class檔案中有效(即class保留)
*/
CLASS,
/**
* 在執行時有效(即執行時保留)
*
* @see java。lang。reflect。AnnotatedElement
*/
RUNTIME
}
我們再回頭看@Autowired,對應Retention中的取值是RetentionPolicy。RUNTIME,也就是說我們的Autowired註解是應行時有效的,這也正是我們預期的其在spring框架工作時的狀態。
@Documented
@Documented用於描述其它型別的annotation應該被作為被標註的程式成員的公共API,因此可以被例如javadoc此類的工具文件化。Documented是一個標記註解,沒有成員。
@Documented
@Retention(RetentionPolicy。RUNTIME)
@Target(ElementType。ANNOTATION_TYPE)
public @interface Documented {
}
@Inherited
此註解是一個標記註解,如果我們的註解被@Inherited註解,那麼我們的註解被用於一個類時,就說明我們的註解應當是這個類的子類
@Documented
@Retention(RetentionPolicy。RUNTIME)
@Target(ElementType。ANNOTATION_TYPE)
public @interface Inherited {
}
註解中的值設定
註解中的值設定是可選擇的,並不是每一個註解都需要去設定值,比如我們的@Inherited註解僅作為一個標記註解,其中是沒有值的。同時,如果我們的註解中有值,那我們也可以去透過“default”關鍵字設定一個預設值,比如我們上面的@Autowired註解中required這個值,就有一個預設的值:true,我們在使用這個註解的時候,就可以不宣告這個值,採用他的預設值
小總結
看到這裡,我們應當明白幾件事
註解應當被@Target標註,用以確定我們的註解的作用範圍
註解應當被@Retention標註,用以確定我們的註解可以工作到什麼時候
註解可選擇地去設定自己的值,也可以設定一個預設值,用以後來的工作
實現註解功能
我們想要用註解去配合被我們註解的類或其他來實現某種功能,那麼首先我們得明白如何將註解與被我們註解的類聯絡起來,我們都知道Autowired可以對類成員變數、方法及建構函式進行標註,讓 spring 完成 bean 自動裝配的工作,那麼Spring內部是如何進行對被Autowired註解的變數進行操作的呢?
首先我要你明白Spring的載入機制,你需要知道,所有被@Service標註的類都在初始化時被例項化。
我們來看一下Spring中實現@Autowired的邏輯程式碼,該程式碼在org。springframework。beans。factory。annotation的org。springframework。beans。factory。annotation包下,有興趣的可以自己去看一下
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
LinkedList
Class<?> targetClass = clazz;//需要處理的目標類
do {
final LinkedList
new LinkedList
//透過反射獲取目標類的所有欄位,遍歷所有欄位,如果有欄位用@Autowired註解,那就返回Autowired的相關屬性
ReflectionUtils。doWithLocalFields(targetClass, new ReflectionUtils。FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
AnnotationAttributes ann = findAutowiredAnnotation(field);
if (ann != null) {//判斷Autowired註解是否是在static方法上
if (Modifier。isStatic(field。getModifiers())) {
if (logger。isWarnEnabled()) {
logger。warn(“Autowired annotation is not supported on static fields: ” + field);
}
return;
}
boolean required = determineRequiredStatus(ann);//判斷required
currElements。add(new AutowiredFieldElement(field, required));
}
}
});
//和上面一樣的邏輯,但是是透過反射處理類的method
ReflectionUtils。doWithLocalMethods(targetClass, new ReflectionUtils。MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Method bridgedMethod = BridgeMethodResolver。findBridgedMethod(method);
if (!BridgeMethodResolver。isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method。equals(ClassUtils。getMostSpecificMethod(method, clazz))) {
if (Modifier。isStatic(method。getModifiers())) {
if (logger。isWarnEnabled()) {
logger。warn(“Autowired annotation is not supported on static methods: ” + method);
}
return;
}
if (method。getParameterTypes()。length == 0) {
if (logger。isWarnEnabled()) {
logger。warn(“Autowired annotation should only be used on methods with parameters: ” +
method);
}
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils。findPropertyForMethod(bridgedMethod, clazz);
currElements。add(new AutowiredMethodElement(method, required, pd));
}
}
});
//用@Autowired修飾的註解可能不止一個,因此都加在currElements這個容器裡面,一起處理
elements。addAll(0, currElements);
targetClass = targetClass。getSuperclass();
}
while (targetClass != null && targetClass != Object。class);
return new InjectionMetadata(clazz, elements);
}
加了註釋是不是很容易懂了?什麼?還不懂?別急,我這裡有一份簡化版的spring程式碼
/**
*@描述
*@方法名 populateBean
*@引數 [beanName, beanDefinition, beanWrapper]
*@返回值 void
*@建立人 Baldwin
*@建立時間 2020/3/9
*@修改人和其它資訊
*/
private void populateBean(String beanName, BeanDefinition beanDefinition, BeanWrapper beanWrapper) {
Class<?> clazz = beanWrapper。getWrappedClass();
//獲得所有的成員變數
Field[] fields = clazz。getDeclaredFields();
for (Field field : fields) {
//如果沒有被Autowired註解的成員變數則直接跳過
if (!field。isAnnotationPresent(YzAutowired。class)) {
continue;
}
YzAutowired autowired = field。getAnnotation(YzAutowired。class);
//拿到需要注入的類名
String autowiredBeanName = autowired。value()。trim();
if (“”。equals(autowiredBeanName)) {
autowiredBeanName = field。getType()。getName();
}
//強制訪問該成員變數
field。setAccessible(true);
try {
if (this。factoryBeanInstanceCache。get(autowiredBeanName) == null) {
continue;
}
//將容器中的例項注入到成員變數中
field。set(beanWrapper。getWrapperInstance(), this。factoryBeanInstanceCache。get(autowiredBeanName)。getWrapperInstance());
} catch (IllegalAccessException e) {
e。printStackTrace();
}
}
}
上面這個是我手寫的簡版spring框架,雖然沒有完成,但是在這裡用來解釋@Autowired的實現機制已經是足夠的。
我們來看上面的程式碼,其中YzAutowired對應著spring中的Autowired,我們來看一下他的邏輯步驟
獲取目標類:透過反射獲取所有的目標類,具體實現過程,有需要的話我再詳解
獲取目標類的所有成員變數進行遍歷:這一步是為了得到那些被YzAutowired註解的變數
判斷:如果當前變數沒有被YzAutowired註解,那麼下一個,如果有被註解,那麼開始我們實現需要的功能
實現功能:首先我們註解的時候是有引數的,我們可以透過註解引數名的方式來獲取這個註解的引數值,然後去使用它,然後就是我們的功能邏輯程式碼了
到這裡,我們的註解與被修飾這之間已經聯絡上,而且也實現了我們預期的功能了
小總結
在功能實現這一部分我們最終應該要做的事主要有兩件
獲取被註解者
獲取值並且完成功能實現
註解實戰:建立一個自己的註解
看完上面的內容,我相信你或許對註解已經有了一定的瞭解,現在可以跟著作者一起來建立一個註解並且實現一個功能。功能要求比較簡單,就是為透過註解為某個變數注入一個值。
1.建立註解:InjectInt
import java。lang。annotation。*;
@Target({ElementType。FIELD})//作用於域
@Retention(RetentionPolicy。RUNTIME)//存在於執行時
@Documented
public @interface InjectInt {
int value() default 0;//預設值為0
}
實現註解功能
import java。lang。reflect。Field;
/**
* 類描述
*
* @author: 12405
* @date: 2020/3/27-23:48
*/
public class DoInject {
public static void inject(){
//反射獲取物件類的class
Class clazz = AnnotationDemo。class;
//獲得所有的成員變數
Field[] fields = clazz。getDeclaredFields();
for (Field field : fields){
//如果沒有被InjectInt註解的成員變數則直接跳過
if (!field。isAnnotationPresent(InjectInt。class)) {
continue;
}
//拿到當前變數的註解
InjectInt injectInt = field。getAnnotation(InjectInt。class);
//拿到註解值
int value = injectInt。value();
//強制訪問該成員變數
field。setAccessible(true);
//將值注入
try {
field。setInt(Integer。class,value);
} catch (IllegalAccessException e) {
e。printStackTrace();
}
}
}
}
應用註解
/**
* 類描述
*
* @author: 12405
* @date: 2020/3/27-23:37
*/
public class AnnotationDemo {
//註解並設定值
@InjectInt(value = 12) static int m;
public static void main(String[] args) {
//開啟註解功能
DoInject。inject();
System。out。println(m);
}
}
輸出
“C:\Program Files\Java\jdk1。8。0_171\bin\java。exe” “-javaagent:E:\tools\IntelliJ IDEA 2019。3。3\lib\idea_rt。jar=61875:E:\tools\IntelliJ IDEA 2019。3。3\bin” -Dfile。encoding=UTF-8 -classpath “C:\Program Files\Java\jdk1。8。0_171\jre\lib\charsets。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\deploy。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\ext\access-bridge-64。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\ext\cldrdata。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\ext\dnsns。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\ext\jaccess。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\ext\jfxrt。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\ext\localedata。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\ext\nashorn。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\ext\sunec。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\ext\sunjce_provider。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\ext\sunmscapi。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\ext\sunpkcs11。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\ext\zipfs。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\javaws。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\jce。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\jfr。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\jfxswt。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\jsse。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\management-agent。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\plugin。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\resources。jar;C:\Program Files\Java\jdk1。8。0_171\jre\lib\rt。jar;E:\Workspaces\IdeaProjects\DemoTest\out\production\DemoTest” cn。yzstu。annotation。AnnotationDemo
Process finished with exit code 0
這樣我們就完成了一個簡單的註解的實現,再次建立int值時可以使用@InjectInt來實現賦值,當然這個功能並不是實用性功能,只是拋磚引玉來給大家展示註解的實現。
總結
最後總結一下註解實現的三部曲:
建立註解,選擇合適的作用域和生存時機
實現註解邏輯,這一步需要我們找到註解的位置
開啟註解,讓註解功能實現
實際上,我們在正常工作時需要自己建立註解的時候並不多,大多數時候只需要我們理解註解的用法即可,但是註解的應用在造輪子時是非常重要的,所以如果我們希望自己能夠有朝一日向大佬們一樣去自己造輪子的話,還是要多瞭解一些註解的知識,同時,瞭解註解機制的實現還可以讓我們更好的瞭解現有框架的實現。
推薦文章
- 哈哈!啥都能共享啊!
友唱共享KTV七、共享書店前幾天,從網上看到了好多共享類的東西,方便了我們讀書,改變了我們平時用電子裝置讀書的侷限,但是畢竟剛投入,我覺得它要是想發展,還是很困難的...
- 洗頭時堅持這“4個步驟”的女人,不僅髮質柔順,髮量還越來越多
9淘寶購買價格也只要一杯奶茶錢,真的建議大家用發膜,用過一段時間後你會發現髮質不是好了一點半點,對顏值的提高特別大...
- 一週四地開庭“打假”,股民維權迎來“超級周”!律師提示:買過這四支股票的投資者或可索賠
(一)億晶光電投資者索賠案再度開庭,此前上市公司已和解、調解賠付1000多人7月22日,投資者訴億晶光電虛假陳述民事責任糾紛在南京市中級人民法院進行新一輪開庭,上海市東方劍橋律師事務所吳立駿律師代理部分原告股民出庭應訴...