Springboot原始碼分析之番外篇
摘要:
大家都知道註解是實現了java.lang.annotation.Annotation介面,眼見為實,耳聽為虛,有時候眼見也不一定是真實的。
/** * The common interface extended by all annotation types. Note that an * interface that manually extends this one does <i>not</i> define * an annotation type. Also note that this interface does not itself * define an annotation type. * * More information about annotation types can be found in section 9.6 of * <cite>The Java™ Language Specification</cite>. * * The {@link java.lang.reflect.AnnotatedElement} interface discusses * compatibility concerns when evolving an annotation type from being * non-repeatable to being repeatable. * * @author Josh Bloch * @since 1.5 */
元註解:
元註解 一般用於指定某個註解生命週期以及作用目標等資訊。正如原始碼的註釋一樣,如果自定義的註解沒有新增元註解就和平常的註釋沒有多大的區別,有了元註解就會讓編譯器將資訊編譯進位元組碼檔案。
@Target
@Target
用於指明被修飾的註解最終可以作用的目標
ElementType
是一個列舉型別
ElementType.TYPE:類,介面(包括註釋型別)或列舉宣告 ElementType.FIELD:欄位宣告(包括列舉常量) ElementType.METHOD:方法宣告 ElementType.PARAMETER:正式引數宣告 ElementType.CONSTRUCTOR:構造器宣告 ElementType.LOCAL_VARIABLE:本地區域性變數宣告 ElementType.ANNOTATION_TYPE:註解宣告 ElementType.PACKAGE:包宣告 ElementType.TYPE_PARAMETER:型別引數宣告 jdk1.8新增 ElementType.TYPE_USE:使用一種型別 jdk1.8新增
- @Retention
@Retention
用於指明當前註解的生命週期
RetentionPolicy
是一個列舉型別
RetentionPolicy.SOURCE:編譯器將丟棄註釋。
RetentionPolicy.CLASS:註釋將由編譯器記錄在類檔案中,但在執行時不需要由VM保留。
RetentionPolicy.RUNTIME:註釋將由編譯器記錄在類檔案中並且在執行時由VM保留,因此可以反射性地讀取它們。
- @Documented
@Documented
表示具有型別的註釋將由javadoc記錄和預設的類似工具。 這種型別應該用來註釋註解影響註解使用的型別的宣告客戶的元素。 如果使用註解型別宣告記錄,其註解成為公共API的一部分註釋元素。
- @Inherited
@Inherited
表示自動繼承註解型別。 如果註解型別上存在繼承的元註解宣告,使用者查詢類的註解型別宣告,類宣告沒有此型別的註解,然後將自動查詢該類的超類註解型別。 將重複此過程,直到為此註解找到型別,或類層次結構的頂部(物件)到達了。 如果沒有超類具有此型別的註解,那麼查詢將指示有問題的類沒有這樣的註解。請注意,如果帶註解,則此元註解型別無效type
用於註解除類之外的任何內容。 另請注意這個元註解只會導致註解被繼承來自超類; 已實現的介面上的註解沒有效果。
註解實現
- 如何自定義註解?
package com.github.dqqzj.springboot.annotation;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author qinzhongjian
* @date created in 2019-07-28 07:54
* @description: TODO
* @since JDK 1.8.0_212-b10
*/
@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Component
public @interface Hello {
@AliasFor(
annotation = Component.class
)
String value() default "hi" ;
}
如何獲取註解元素資訊?
如上圖所示註解其實也是使用了代理,而且是JDK代理的。
註解原理分析
既然是執行時生成的代理類,我們就可以在啟動類上新增System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true")
或者
我們來分析一下生成的代理類
package com.sun.proxy;
import com.github.dqqzj.springboot.annotation.Hello;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy1 extends Proxy implements Hello {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m0;
private static Method m3;
public $Proxy1(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Class annotationType() throws {
try {
return (Class)super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String value() throws {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("com.github.dqqzj.springboot.annotation.Hello").getMethod("annotationType");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.github.dqqzj.springboot.annotation.Hello").getMethod("value");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
這裡的InvocationHandler
實際上是我們的AnnotationInvocationHandler
,這裡有一個memberValues
,它是一個Map
鍵值對,鍵是我們註解屬性名稱,值就是該屬性當初被賦上的值。接下來我除錯程式碼給大家分享一下奧祕。
Hello hello = TestAnnotation.class.getAnnotation(Hello.class)
這個部分的除錯程式碼我會忽略直接除錯
AnnotationInvocationHandler
的相關方法。
註解奧祕的準備工作
- 反編譯註解檔案,發現註解確實是實現了
Annotation
介面的
熟悉jdk規範的就會發現最底部的s#7RuntimeVisibleAnnotations
這個是執行時可訪問的註解資訊,可供我們反射獲取。
虛擬機器規範定義了一系列和註解相關的屬性表,無論是欄位、方法或是類本身,如果被註解修飾了,就可以被寫進位元組碼檔案。屬性表有以下幾種:
RuntimeVisibleAnnotations:執行時可見的註解
RuntimeInVisibleAnnotations:執行時不可見的註解
RuntimeVisibleParameterAnnotations:執行時可見的方法引數註解
RuntimeInVisibleParameterAnnotations:執行時不可見的方法引數註解
AnnotationDefault:註解類元素的預設值`
註解奧祕除錯
說明: 明明只有一個@Hello
註解為什麼左側會出現2個代理類的原因就在這個地方,會多出一個代理類
public final class $Proxy0 extends Proxy implements Retention {
//省略無關程式碼.......
}
反射註解工作原理:
- 我們通過鍵值對的形式可以為註解屬性賦值,像這樣:
@Hello(value = "hi")
- 用註解修飾某個元素,編譯器將在編譯期掃描每個類或者方法上的註解,會做一個基本的檢查,你的這個註解是否允許作用在當前位置,最後會將註解資訊寫入元素的屬性表
- 虛擬機器把生命週期在
RUNTIME
的註解取出並通過動態代理機制生成一個實現註解介面的代理類
如何動態修改代理值?
我們已經知道了註解的值是存放在Map<String, Object> memberValues
中的,那麼我們就可以使用反射獲取並重新賦值。
相關推薦
Springboot原始碼分析之番外篇
摘要: 大家都知道註解是實現了java.lang.annotation.Annotation介面,眼見為實,耳聽為虛,有時候眼見也不一定是真實的。 /** * The common interface extended by all annotation types. Note that
spring-boot-2.0.3不一樣系列之番外篇
前言 還記得當初寫spring-session實現分散式叢集session的共享的時候,裡面有說到利用filter和HttpServletRequestWrapper可以定製自己的getSession方法,實現對session的控制,從而將session存放到統一的位置進行儲存,達到session共享的目
人工智慧基礎(高中版)教材補充和資源分享之番外篇 Cozmo+Python+ROS+AI
Cozmo+Python+ROS+AI會產生什麼樣的奇妙反應呢? (玩Cozmo機器人,學Python程式設計,掌握ROS和AI技術) 跟隨綠色鐳射點運動?如何實現? 在黃色邊緣線的賽道上行駛?如何實現? 這是一篇輕鬆愉快的博文,簡單聊聊如何從玩機器人,升
數學分析教程 番外篇(3):空間解析幾何初步 學習感受
與上一個番外篇一樣,這本來也是要專門上一門課的,這裡只是點到為止,講講基本內容。 首先是平面,就是一個2元1次方程,最標準的是點法式。其次是空間直線,有兩種表達:點向式和一般式。其中一般式就是兩個平面相交。 對於空間曲面主要是介紹常見的二次曲面和它們對應的圖形,需要注意的是
Spring Cloud Netflix Zuul原始碼分析之路由註冊篇
微信公眾號:I am CR7如有問題或建議,請在下方留言;最近更新:2018-12-29 前言 繼上一篇Spring Cloud Netflix Zuul原始碼分析之預熱篇,我們知道了兩個重要的類:ZuulHandlerMapping和SimpleControllerHandlerA
Spring Cloud Netflix Zuul原始碼分析之請求處理篇-上
微信公眾號:I am CR7如有問題或建議,請在下方留言;最近更新:2019-01-03 微信公眾號:I am CR7如有問題或建議,請在下方留言最近更新:2019-01-03 前言 經過前面兩篇文章的鋪墊,大戲正式上場。本文將對zuul是如何根據配置
Tornado原始碼分析之http伺服器篇
一. Tornado是什麼? Facebook釋出了開源網路伺服器框架Tornado,該平臺基於Facebook剛剛收購的社交聚合網站FriendFeed的實時資訊服務開發而來.Tornado由Python編寫,是一款輕量級的Web伺服器,同時又是一個開發框架。採用非阻
你應該知道的DICOM之番外篇
你知道麼? 1.DICOM歷史簡介?2.2011版DICOM3.0標準幾個章節間的關係?3.DICOM中檔案格式有幾種?4.DICOM中有幾種VR?DICOM通訊有幾種PDU?幾種DIMSE?5.DICOM中Tag有幾種Type?每種Type含義?6.VR為CS的Tag,值
JVM原始碼分析之堆外記憶體完全解讀
概述 廣義的堆外記憶體 說到堆外記憶體,那大家肯定想到堆內記憶體,這也是我們大家接觸最多的,我們在jvm引數裡通常設定-Xmx來指定我們的堆的最大值,不過這還不是我們理解的Java堆,-Xmx的值是新生代和老生代的和的最大值,我們在jvm引數裡通常還會加一個引數-XX
element 原始碼學習(番外篇) —— SASS五分鐘快速入門
這算是 element 原始碼學習的番外篇,因為 element 中使用了大量 sass 來寫樣式。而 UI 框架的核心其實就是樣式。所以,抽空把 sass 學了一遍,寫了些小 demo 實踐,總結成此文。 SASS 安裝和除錯 簡單說下 sas
Netty 從零到一學習系列之番外篇
要搞懂Netty首先需要了解什麼是非同步I/O?什麼是同步I/O?什麼是阻塞I/O?什麼是非阻塞I/O? 在《UNIX網路程式設計》一書中介紹了五種I/O模型。分別為:阻塞I/O模型、非阻塞I/O模型、I/O多路複用模型、訊號驅動I/O模型和非同步I/O模型。下面分別
spark原始碼分析之worker原理篇
解釋: 1、master要求worker啟動driver和executor 2、worker啟動driver的一個基本的原理,worker會啟動一個執行緒DriverRunner,然後DriverRunner會去負責啟動driver程序,然後在之後對d
【xv6學習之番外篇】記憶體管理
An approach to space management that provides even further simplification of space-management software is to maintain a one-to-one correspondence between s
SpringBoot原始碼分析之---SpringBoot專案啟動類SpringApplication淺析
原始碼版本 本文原始碼採用版本為SpringBoot 2.1.0BUILD,對應的SpringFramework 5.1.0.RC1 注意:本文只是從整體上梳理流程,不做具體深入分析 SpringBoot入口類 @SpringBootAp
Springboot原始碼分析之專案結構
摘要: 無論是從IDEA還是其他的SDS開發工具亦或是https://start.spring.io/ 進行解壓,我們都會得到同樣的一個pom.xml檔案 xml <?xml version="1.0" encoding="UTF-8"?> <project xm
Springboot原始碼分析之jar探祕
摘要: 利用IDEA等工具打包會出現springboot-0.0.1-SNAPSHOT.jar,springboot-0.0.1-SNAPSHOT.jar.original,前面說過它們之間的關係了,接下來我們就一探究竟,它們之間到底有什麼聯絡。 檔案對比: 進入target目錄,unzip sprin
Springboot原始碼分析之EnableAspectJAutoProxy
摘要: Spring Framwork的兩大核心技術就是IOC和AOP,AOP在Spring的產品線中有著大量的應用。如果說反射是你通向高階的基礎,那麼代理就是你站穩高階的底氣。AOP的本質也就是大家所熟悉的CGLIB動態代理技術,在日常工作中想必或多或少都用過但是它背後的祕密值得我們去深思。本文主要從Spr
Springboot原始碼分析之代理三板斧
摘要: 在Spring的版本變遷過程中,註解發生了很多的變化,然而代理的設計也發生了微妙的變化,從Spring1.x的ProxyFactoryBean的硬編碼島Spring2.x的Aspectj註解,最後到了現在廣為熟知的自動代理。 說明: ProxyConfig代理的相關配置類 AdvisedSupp
Springboot原始碼分析之AbstractAdvisorAutoProxyCreator
摘要: Spring的代理在上層中主要分為ProxyCreatorSupport和ProxyProcessorSupport,前者是基於代理工廠,後者是基於後置處理器,也可以認為後置就是自動代理器。當spring容器中需要進行aop進行織入的bean較多時,簡單採用ProxyFacotryBean無疑會增加很
Springboot原始碼分析之TargetSource
摘要: 其實我第一次看見這個東西的時候也是不解,代理目標源不就是一個class嘛還需要封裝幹嘛。。。 其實proxy代理的不是target,而是TargetSource,這點非常重要,一定要分清楚!!! 通常情況下,一個代理物件只能代理一個target,每次方法呼叫的目標也是唯一固定的target。但是,如果