一文搞懂Spring中的注解與反射
前言
注解(Annotation)不是程序,但可以對程序作出解釋,也可以被其它程序(如編譯器)讀取。
注解的格式:以@注釋名在代碼中存在,還可以添加一些參數(shù)值例如@SuppressWarnings(value="unchecked")。
注解可在package、class、method、field等上面使用,作用是為它們添加了額外的輔助信息,從而可以通過反射機(jī)制實(shí)現(xiàn)對這些元數(shù)據(jù)的訪問。
一、內(nèi)置(常用)注解
1.1@Overrode
表示某方法旨在覆蓋超類中的方法聲明,該方法將覆蓋或?qū)崿F(xiàn)在超類中聲明的方法。
1.2@RequestMapping
@RequestMapping注解的主要用途是將Web請求與請求處理類中的方法進(jìn)行映射,注意有以下幾個屬性:
- value:映射的請求URL或者其別名
- value:映射的請求URL或者其別名
- params:根據(jù)HTTP參數(shù)的存在、缺省或值對請求進(jìn)行過濾
1.3@RequestBody
@RequestBody在處理請求方法的參數(shù)列表中使用,它可以將請求主體中的參數(shù)綁定到一個對象中,請求主體參數(shù)是通過HttpMessageConverter傳遞的,根據(jù)請求主體中的參數(shù)名與對象的屬性名進(jìn)行匹配并綁定值。此外,還可以通過@Valid注解對請求主體中的參數(shù)進(jìn)行校驗(yàn)。
1.4@GetMapping
@GetMapping注解用于處理HTTP GET請求,并將請求映射到具體的處理方法中。具體來說,@GetMapping是一個組合注解,它相當(dāng)于是@RequestMapping(method=RequestMethod.GET)的快捷方式。
1.5@PathVariable
@PathVariable注解是將方法中的參數(shù)綁定到請求URI中的模板變量上??梢酝ㄟ^@RequestMapping注解來指定URI的模板變量,然后使用@PathVariable注解將方法中的參數(shù)綁定到模板變量上。
1.6@RequestParam
@RequestParam注解用于將方法的參數(shù)與Web請求的傳遞的參數(shù)進(jìn)行綁定。使用@RequestParam可以輕松的訪問HTTP請求參數(shù)的值。
1.7@ComponentScan
@ComponentScan注解用于配置Spring需要掃描的被組件注解注釋的類所在的包??梢酝ㄟ^配置其basePackages屬性或者value屬性來配置需要掃描的包路徑。value屬性是basePackages的別名。
1.8@Component
@Component注解用于標(biāo)注一個普通的組件類,它沒有明確的業(yè)務(wù)范圍,只是通知Spring被此注解的類需要被納入到Spring Bean容器中并進(jìn)行管理。
1.9@Service
@Service注解是@Component的一個延伸(特例),它用于標(biāo)注業(yè)務(wù)邏輯類。與@Component注解一樣,被此注解標(biāo)注的類,會自動被Spring所管理。
1.10@Repository
@Repository注解也是@Component注解的延伸,與@Component注解一樣,被此注解標(biāo)注的類會被Spring自動管理起來,@Repository注解用于標(biāo)注DAO層的數(shù)據(jù)持久化類。
二、元注解
4個元個元注解分別是:@Target、@Retention、@Documented、@Inherited 。
再次強(qiáng)調(diào)下元注解是java API提供,是專門用來定義注解的注解。
@Target
描述注解能夠作用的位置,ElementType取值:
- ElementType.TYPE,可以作用于類上
- ElementType.METHOD,可以作用于方法上
- ElementType.FIELD,可以作用在成員變量上
@Retention
表示需要在什么級別保存該注釋信息(生命周期):
RetentionPolicy.RUNTIME
:內(nèi)存中的字節(jié)碼,VM將在運(yùn)行時也保留注解,因此可以通過反射機(jī)制讀取注解的信息
@Documented
描述注解是否被抽取到api文檔中。
@Inherited
描述注解是否被子類繼承。
三、自定義注解
學(xué)習(xí)自定義注解對于理解Spring框架十分有好處,即使在實(shí)際項(xiàng)目中可能不需要使用自定義注解,但可以幫助我們掌握Spring的一些底層原理,從而提高對整體項(xiàng)目的把握。
/** * 自定義注解 * @author Created by zhuzqc on 2022/5/31 23:03 */ public class CustomAnnotation { /** * 注解中可以為參數(shù)賦值,如果沒有默認(rèn)值,那么必須為注解的參數(shù)賦值 * */ @MyAnnotation(value = "解釋") public void test(){ } } /** * @author zhuzqc */ //自定義注解必須的元注解target,指明注解的作用域(此處指明的是在類和方法上起作用) @Target({ElementType.TYPE,ElementType.METHOD}) //元注解retention聲明該注解在何時起作用(此處指明的是在運(yùn)行時起作用) @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation{ //注解中需聲明參數(shù),格式為:參數(shù)類型 + 參數(shù)名(); String value() default ""; }
四、反射機(jī)制概述
4.1動態(tài)語言與靜態(tài)語言
4.1.1動態(tài)語言
是一種在運(yùn)行時可以改變其結(jié)構(gòu)的語言,例如新的函數(shù)、對象甚至代碼可以被引進(jìn),已有的函數(shù)可以被刪除或是進(jìn)行其它結(jié)構(gòu)上的變化。
主要的動態(tài)語言有:Object-C、C#、PHP、Python、JavaScript 等。
以 JavaScript 語言舉例:
/** * 由于未指定var的具體類型,函數(shù)在運(yùn)行時間可以改變var的類型 * */ function f(){ var x = "var a = 3; var b = 5; alert(a+b)"; eval(x) }
4.2.2靜態(tài)語言
與動態(tài)語言相對的、運(yùn)行時結(jié)構(gòu)不可變的語言就是靜態(tài)語言,如 Java、C、C++ 等。
Java 不是動態(tài)語言,但 Java 可以稱為”準(zhǔn)動態(tài)語言“。即 Java 有一定的動態(tài)性,可以利用反射機(jī)制獲得類似于動態(tài)語言的特性,從而使得 Java 語言在編程時更加靈活。
4.2Java Reflection(Java 反射)
Reflection(反射)是 Java 被視為準(zhǔn)動態(tài)語言的關(guān)鍵:反射機(jī)制允許程序在執(zhí)行期間借助 Reflection API 獲取任何類的內(nèi)部信息,并能直接操作任意對象的內(nèi)部屬性及方法。
Class c = Class.forName("java.lang.String")
加載完類后,在堆內(nèi)存的方法區(qū)就產(chǎn)生了一個Class類型的對象(一個類只有一個Class對象),這個類就包含了完整的類的結(jié)構(gòu)信息。我沒可以通過這個對象,像鏡子一樣看到類的結(jié)構(gòu),這個過程形象地被稱之為反射。
通過代碼更易于理解:
/** * 反射的概念 * @author Created by zhuzqc on 2022/6/1 17:40 */ public class ReflectionTest extends Object{ public static void main(String[] args) throws ClassNotFoundException { //通過反射獲取類的Class對象 Class c = Class.forName("com.dcone.zhuzqc.demo.User"); //一個類在內(nèi)存中只有唯一個Class對象 System.out.println(c.hashCode()); } } /** * 定義一個實(shí)體類entity * */ @Data class User{ private String userName; private Long userId; private Date loginTime; }
由于該類繼承 Object,在 Object 類中有 getClass() 方法,該方法被所有子類繼承:
@HotSpotIntrinsicCandidate public final native Class<?> getClass();
注:該方法的返回值類型是一個 Class 類,該類是 Java 反射的源頭。
反射的優(yōu)點(diǎn):運(yùn)行期類型的判斷、動態(tài)加載類、提高代碼靈活度。
4.2.1反射機(jī)制主要功能
- 在運(yùn)行時判斷、調(diào)用任意一個類的對象信息(成員變量和方法等);
- 在運(yùn)行時獲取泛型信息;
- 在運(yùn)行時處理注解;
- 生成動態(tài)代理。
4.2.2主要API
- java.lang.Class:代表一個類
- java.lang.reflect.Field:代表類的成員變量
- java.lang.reflect.Method:代表類的方法
- java.lang.reflect.Constructor:代表類的構(gòu)造器
五、理解Class類并獲取Class實(shí)例
5.1Class類
前面提到,反射后可以得到某個類的屬性、方法和構(gòu)造器、實(shí)現(xiàn)的接口。
- 對于每個類而言,JRE都為其保留一個不變的 Class 類型的對象;
- 一個加載的類在 JVM 中只會有一個 Class 實(shí)例;
- Class 類是Reflection的根源,想要通過反射獲得任何動態(tài)加載的、運(yùn)行的類,都必須先獲取相應(yīng)的 Class 對象。
5.2獲取Class類實(shí)例
有以下5種方式可以獲取Class類的實(shí)例:
1.若已知具體的類,可以通過類的class屬性獲取,該fang'shi最為安全可靠,且程序性能最高。
//類的class屬性 Class classOne = User.class;
2. 已知某個類的實(shí)例,通過調(diào)用該實(shí)例的getClass方法獲取Class對象。
//已有類對象的getClass方法 Class collatz = user.getClass();
3.已知一個類的全類名,且該類在類路徑下,可以通過靜態(tài)方法forName()獲取。
Class c = Class.forName("com.dcone.zhuzqc.demo.User");
4.內(nèi)置基本數(shù)據(jù)類型可以直接使用類名.Type獲取。
//內(nèi)置對象才有的TYPE屬性,較大的局限性 Class<Integer> type = Integer.TYPE;
5.利用ClassLoader(類加載器)獲取。
5.3可獲得Class對象的類型
1.class:外部類、成員(成員內(nèi)部類,靜態(tài)內(nèi)部類),局部內(nèi)部類,匿名內(nèi)部類;
//類可以反射 Class c1 = Person.class;
2.interface:所有接口;
//接口可以反射 Class c2 = Comparable.class;
3.[]:數(shù)組;
//數(shù)組可以反射 Class c3 = String[].class; Class c4 = int[][].class;
4.enum:枚舉;
//枚舉可以反射 Class c6 = ElementType.class;
5.annotation:注解(@interface);
//注解可以反射 Class c5 = Data.class;
6.基本數(shù)據(jù)類型;
//基本數(shù)據(jù)類型(包裝類)可以反射 Class c7 = int.class; Class c8 = Integer.class;
7.void。
//void可以反射 Class c9 = void.class;
六、類的加載與ClassLoader
6.1類的加載過程
當(dāng)程序主動使用某個類時,如果該類還未被加載到內(nèi)存中,則系統(tǒng)會通過如下3個步驟來對該類進(jìn)行初始化。
1.類的加載(Load):將類的 class 文件字節(jié)碼內(nèi)容讀入內(nèi)存,并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)運(yùn)行時的數(shù)據(jù)結(jié)構(gòu),同時創(chuàng)建一個java.lang.Class對象,此過程由類加載器完成;
2.類的鏈接(Link):將類的二進(jìn)制數(shù)據(jù)合并到 JRE 中,確保加載的類信息符合 JVM 規(guī)范,同時 JVM 將常量池內(nèi)的引用替換為地址。
3.類的初始化(Initialize):JVM 負(fù)責(zé)對類進(jìn)行初始化,分為類的主動引用和被動引用。
類的主動引用
- 虛擬器啟動時,先初始化main方法所在的類;
- new 類的對象;
- 調(diào)用類的靜態(tài)(static)成員和靜態(tài)(static)方法;
- 使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用;
- 如果該類的父類沒有被初始化,則會先初始化它的父類。
類的被動引用
- 當(dāng)訪問到一個靜態(tài)域時,只有真正聲明這個域的類才會被初始化;
- 通過數(shù)組定義類的引用,不會觸發(fā)此類的初始化;
- 引用常量不會觸發(fā)此類的初始化
6.2類加載器
JVM支持兩種類型的類加載器,分別為引導(dǎo)類加載器(BootstrapClassLoader)和自定義類加載器(User-Defined ClassLoader)。
從概念上來講,自定義類加載器一般指的是程序中由開發(fā)人員自定義的一類,類加載器。
但是Java虛擬機(jī)規(guī)范卻沒有這么定義,而是將所有派生于抽象類ClassLoader的類加載器都劃分為自定義類加載器。
無論類加載器的類型如何劃分,在程序中我們最常見的類加載器始終只有3個,具體如下圖所示:
類加載器
所以具體為引導(dǎo)類加載器(BootstrapClassLoader)和自定義類加載器(包括ExtensionClassLoader、Application ClassLoader(也叫System ClassLoader)、User Defined ClassLoader)。
public class Test03 { public static void main(String[] args) { //獲取系統(tǒng)類的加載器 ClassLoader sysLoader = ClassLoader.getSystemClassLoader(); System.out.println(sysLoader); //獲取系統(tǒng)類的父類加載器 ClassLoader parent = sysLoader.getParent(); System.out.println(parent); } }
七、獲取運(yùn)行時類的完整對象
通過反射獲取運(yùn)行時類的完整結(jié)構(gòu):Field、Method、Constructor、Superless、Interface、Annotation等。
即:實(shí)現(xiàn)的全部接口、所繼承的父類、全部的構(gòu)造器、全部的方法、全部的成員變量(局部變量)、注解等。
/** * @author Created by zhuzqc on 2022/6/5 0:16 */ public class Test04 { public static void main(String[] args) throws ClassNotFoundException { Class c1 = Class.forName("com.dcone.zhuzqc.demo.User"); //獲取所有屬性 Field field[]; field = c1.getDeclaredFields(); for (Field f:field){ System.out.println(f); } //獲得類的方法 Method method[]; method = c1.getDeclaredMethods(); for (Method m:method){ System.out.println(m); } } }
八、反射獲取泛型信息
Java 中采用泛型擦除的機(jī)制來引入泛型,Java 中的泛型僅僅是給編譯器 javac 使用的,目的是確保數(shù)據(jù)的安全性以及免去強(qiáng)制類型轉(zhuǎn)換的問題。一旦編譯完成,所有和泛型相關(guān)的類型全部擦除。
在Java中可以通過反射獲取泛型信息的場景有如下三個:
- (1)成員變量的泛型
- (2)方法參數(shù)的泛型
- (3)方法返回值的泛型
在Java中不可以通過反射獲取泛型信息的場景有如下兩個:
- (1)類或接口聲明的泛型
- (2)局部變量的泛型
要獲取泛型信息,必須要注意ParameterizedType類,該類中的getActualTypeArguments()方法可以有效獲取泛型信息。
下面以獲取成員方法參數(shù)的泛型類型信息為例:
public class Demo { public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException { // 獲取成員方法參數(shù)的泛型類型信息 getMethodParametricGeneric(); }
/** * 獲取方法參數(shù)的泛型類型信息 * * @throws NoSuchMethodException */ public static void getMethodParametricGeneric() throws NoSuchMethodException { // 獲取MyTestClass類中名為"setList"的方法 Method setListMethod = MyClass.class.getMethod("setList", List.class); // 獲取該方法的參數(shù)類型信息(帶有泛型) Type[] genericParameterTypes = setListMethod.getGenericParameterTypes(); // 但我們實(shí)際上需要獲取返回值類型中的泛型信息,所以要進(jìn)一步判斷,即判斷獲取的返回值類型是否是參數(shù)化類型ParameterizedType for (Type genericParameterType : genericParameterTypes) { ParameterizedType parameterizedType = (ParameterizedType) genericParameterType; // 獲取成員方法參數(shù)的泛型類型信息 Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { Class realType = (Class) actualTypeArgument; System.out.println("成員方法參數(shù)的泛型信息:" + realType); } } }
九、反射獲取注解信息
在開發(fā)中可能會遇到這樣的場景:獲取類的屬性釋義,這些釋義定義在類屬性的注解中。
/** * 定義一個實(shí)體類entity * */ @Data class User{ @ApiModelProperty(value = "姓名") private String userName; @ApiModelProperty(value = "用戶id") private Long userId; @ApiModelProperty(value = "登錄時間") private Date loginTime; }
那么可以如何獲取注解中的屬性信息呢?
解決方案:
這里我們使用反射,以及java.lang下的兩個方法:
//如果指定類型的注釋存在于此元素上, 方法返回true java.lang.Package.isAnnotationPresent(Class<? extends Annotation> annotationClass) //如果是該類型的注釋, 方法返回該元素的該類型的注釋 java.lang.Package.getAnnotation(Class< A > annotationClass)
public static void main(String[] args) throws ClassNotFoundException { Class c1 = Class.forName("com.dcone.zhuzqc.demo.User"); if(User.class.isAnnotationPresent(ApiModel.class)){ System.out.println(User.class.getAnnotation(ApiModel.class).value()); } // 獲取類變量注解 Field[] fields = User.class.getDeclaredFields(); for (Field f : fields) { if(f.isAnnotationPresent(ApiModelProperty.class)){ System.out.print(f.getAnnotation(ApiModelProperty.class).name() + ","); } } }
拓展1:獲取方法上的注解
@Bean("sqlSessionFactory") public String test(@RequestBody User user) throws ClassNotFoundException { Class c2 = Class.forName("com.dcone.zhuzqc.demo.User"); // 獲取方法注解: Method[] methods = User.class.getDeclaredMethods(); for(Method m : methods){ if (m.isAnnotationPresent((Class<? extends Annotation>) User.class)) { System.out.println(m.getAnnotation(ApiModelProperty.class).annotationType()); } } return "test"; }
拓展2:獲取方法參數(shù)上的注解
@Bean("sqlSessionFactory") public String test(@RequestBody User user) throws ClassNotFoundException { Class c2 = Class.forName("com.dcone.zhuzqc.demo.User"); // 獲取方法參數(shù)注解 Method[] methods2 = User.class.getDeclaredMethods(); for (Method m : methods2) { // 獲取方法的所有參數(shù) Parameter[] parameters = m.getParameters(); for (Parameter p : parameters) { // 判斷是否存在注解 if (p.isAnnotationPresent(ApiModelProperty.class)) { System.out.println(p.getAnnotation(ApiModelProperty.class).name()); } } } return "test"; }
以上就是一文搞懂Spring中的注解與反射的詳細(xì)內(nèi)容,更多關(guān)于Spring注解 反射的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IDEA中創(chuàng)建maven項(xiàng)目webapp目錄無法識別即未被標(biāo)識的解決辦法
在學(xué)習(xí)SpringMVC課程中,基于IDEA新建maven項(xiàng)目模塊后,webapp目錄未被標(biāo)識,即沒有小藍(lán)點(diǎn)的圖標(biāo)顯示,所以本文給大家介紹了IDEA中創(chuàng)建maven項(xiàng)目webapp目錄無法識別即未被標(biāo)識的解決辦法,需要的朋友可以參考下2024-03-03java 用redisTemplate 的 Operations存取list集合操作
這篇文章主要介紹了java 用redisTemplate 的 Operations存取list集合操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08Netty結(jié)合Protobuf進(jìn)行編解碼的方法
這篇文章主要介紹了Netty結(jié)合Protobuf進(jìn)行編解碼,通過文檔表述和代碼實(shí)例充分說明了如何進(jìn)行使用和操作,需要的朋友可以參考下2021-06-06jxl 導(dǎo)出數(shù)據(jù)到excel的實(shí)例講解
下面小編就為大家分享一篇jxl 導(dǎo)出數(shù)據(jù)到excel的實(shí)例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12