一文搞懂Spring中的注解與反射
前言
注解(Annotation)不是程序,但可以對(duì)程序作出解釋,也可以被其它程序(如編譯器)讀取。
注解的格式:以@注釋名在代碼中存在,還可以添加一些參數(shù)值例如@SuppressWarnings(value="unchecked")。
注解可在package、class、method、field等上面使用,作用是為它們添加了額外的輔助信息,從而可以通過反射機(jī)制實(shí)現(xiàn)對(duì)這些元數(shù)據(jù)的訪問。
一、內(nèi)置(常用)注解
1.1@Overrode
表示某方法旨在覆蓋超類中的方法聲明,該方法將覆蓋或?qū)崿F(xiàn)在超類中聲明的方法。
1.2@RequestMapping
@RequestMapping注解的主要用途是將Web請(qǐng)求與請(qǐng)求處理類中的方法進(jìn)行映射,注意有以下幾個(gè)屬性:
- value:映射的請(qǐng)求URL或者其別名
- value:映射的請(qǐng)求URL或者其別名
- params:根據(jù)HTTP參數(shù)的存在、缺省或值對(duì)請(qǐng)求進(jìn)行過濾
1.3@RequestBody
@RequestBody在處理請(qǐng)求方法的參數(shù)列表中使用,它可以將請(qǐng)求主體中的參數(shù)綁定到一個(gè)對(duì)象中,請(qǐng)求主體參數(shù)是通過HttpMessageConverter傳遞的,根據(jù)請(qǐng)求主體中的參數(shù)名與對(duì)象的屬性名進(jìn)行匹配并綁定值。此外,還可以通過@Valid注解對(duì)請(qǐng)求主體中的參數(shù)進(jìn)行校驗(yàn)。
1.4@GetMapping
@GetMapping注解用于處理HTTP GET請(qǐng)求,并將請(qǐng)求映射到具體的處理方法中。具體來說,@GetMapping是一個(gè)組合注解,它相當(dāng)于是@RequestMapping(method=RequestMethod.GET)的快捷方式。
1.5@PathVariable
@PathVariable注解是將方法中的參數(shù)綁定到請(qǐng)求URI中的模板變量上??梢酝ㄟ^@RequestMapping注解來指定URI的模板變量,然后使用@PathVariable注解將方法中的參數(shù)綁定到模板變量上。
1.6@RequestParam
@RequestParam注解用于將方法的參數(shù)與Web請(qǐng)求的傳遞的參數(shù)進(jìn)行綁定。使用@RequestParam可以輕松的訪問HTTP請(qǐng)求參數(shù)的值。
1.7@ComponentScan
@ComponentScan注解用于配置Spring需要掃描的被組件注解注釋的類所在的包??梢酝ㄟ^配置其basePackages屬性或者value屬性來配置需要掃描的包路徑。value屬性是basePackages的別名。
1.8@Component
@Component注解用于標(biāo)注一個(gè)普通的組件類,它沒有明確的業(yè)務(wù)范圍,只是通知Spring被此注解的類需要被納入到Spring Bean容器中并進(jìn)行管理。
1.9@Service
@Service注解是@Component的一個(gè)延伸(特例),它用于標(biāo)注業(yè)務(wù)邏輯類。與@Component注解一樣,被此注解標(biāo)注的類,會(huì)自動(dòng)被Spring所管理。
1.10@Repository
@Repository注解也是@Component注解的延伸,與@Component注解一樣,被此注解標(biāo)注的類會(huì)被Spring自動(dòng)管理起來,@Repository注解用于標(biāo)注DAO層的數(shù)據(jù)持久化類。
二、元注解
4個(gè)元個(gè)元注解分別是:@Target、@Retention、@Documented、@Inherited 。
再次強(qiáng)調(diào)下元注解是java API提供,是專門用來定義注解的注解。
@Target
描述注解能夠作用的位置,ElementType取值:
- ElementType.TYPE,可以作用于類上
- ElementType.METHOD,可以作用于方法上
- ElementType.FIELD,可以作用在成員變量上
@Retention
表示需要在什么級(jí)別保存該注釋信息(生命周期):
RetentionPolicy.RUNTIME:內(nèi)存中的字節(jié)碼,VM將在運(yùn)行時(shí)也保留注解,因此可以通過反射機(jī)制讀取注解的信息
@Documented
描述注解是否被抽取到api文檔中。
@Inherited
描述注解是否被子類繼承。
三、自定義注解
學(xué)習(xí)自定義注解對(duì)于理解Spring框架十分有好處,即使在實(shí)際項(xiàng)目中可能不需要使用自定義注解,但可以幫助我們掌握Spring的一些底層原理,從而提高對(duì)整體項(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聲明該注解在何時(shí)起作用(此處指明的是在運(yùn)行時(shí)起作用)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
//注解中需聲明參數(shù),格式為:參數(shù)類型 + 參數(shù)名();
String value() default "";
}
四、反射機(jī)制概述
4.1動(dòng)態(tài)語(yǔ)言與靜態(tài)語(yǔ)言
4.1.1動(dòng)態(tài)語(yǔ)言
是一種在運(yùn)行時(shí)可以改變其結(jié)構(gòu)的語(yǔ)言,例如新的函數(shù)、對(duì)象甚至代碼可以被引進(jìn),已有的函數(shù)可以被刪除或是進(jìn)行其它結(jié)構(gòu)上的變化。
主要的動(dòng)態(tài)語(yǔ)言有:Object-C、C#、PHP、Python、JavaScript 等。
以 JavaScript 語(yǔ)言舉例:
/**
* 由于未指定var的具體類型,函數(shù)在運(yùn)行時(shí)間可以改變var的類型
* */
function f(){
var x = "var a = 3; var b = 5; alert(a+b)";
eval(x)
}
4.2.2靜態(tài)語(yǔ)言
與動(dòng)態(tài)語(yǔ)言相對(duì)的、運(yùn)行時(shí)結(jié)構(gòu)不可變的語(yǔ)言就是靜態(tài)語(yǔ)言,如 Java、C、C++ 等。
Java 不是動(dòng)態(tài)語(yǔ)言,但 Java 可以稱為”準(zhǔn)動(dòng)態(tài)語(yǔ)言“。即 Java 有一定的動(dòng)態(tài)性,可以利用反射機(jī)制獲得類似于動(dòng)態(tài)語(yǔ)言的特性,從而使得 Java 語(yǔ)言在編程時(shí)更加靈活。
4.2Java Reflection(Java 反射)
Reflection(反射)是 Java 被視為準(zhǔn)動(dòng)態(tài)語(yǔ)言的關(guān)鍵:反射機(jī)制允許程序在執(zhí)行期間借助 Reflection API 獲取任何類的內(nèi)部信息,并能直接操作任意對(duì)象的內(nèi)部屬性及方法。
Class c = Class.forName("java.lang.String")
加載完類后,在堆內(nèi)存的方法區(qū)就產(chǎn)生了一個(gè)Class類型的對(duì)象(一個(gè)類只有一個(gè)Class對(duì)象),這個(gè)類就包含了完整的類的結(jié)構(gòu)信息。我沒可以通過這個(gè)對(duì)象,像鏡子一樣看到類的結(jié)構(gòu),這個(gè)過程形象地被稱之為反射。
通過代碼更易于理解:
/**
* 反射的概念
* @author Created by zhuzqc on 2022/6/1 17:40
*/
public class ReflectionTest extends Object{
public static void main(String[] args) throws ClassNotFoundException {
//通過反射獲取類的Class對(duì)象
Class c = Class.forName("com.dcone.zhuzqc.demo.User");
//一個(gè)類在內(nèi)存中只有唯一個(gè)Class對(duì)象
System.out.println(c.hashCode());
}
}
/**
* 定義一個(gè)實(shí)體類entity
* */
@Data
class User{
private String userName;
private Long userId;
private Date loginTime;
}
由于該類繼承 Object,在 Object 類中有 getClass() 方法,該方法被所有子類繼承:
@HotSpotIntrinsicCandidate public final native Class<?> getClass();
注:該方法的返回值類型是一個(gè) Class 類,該類是 Java 反射的源頭。
反射的優(yōu)點(diǎn):運(yùn)行期類型的判斷、動(dòng)態(tài)加載類、提高代碼靈活度。
4.2.1反射機(jī)制主要功能
- 在運(yùn)行時(shí)判斷、調(diào)用任意一個(gè)類的對(duì)象信息(成員變量和方法等);
- 在運(yùn)行時(shí)獲取泛型信息;
- 在運(yùn)行時(shí)處理注解;
- 生成動(dòng)態(tài)代理。
4.2.2主要API
- java.lang.Class:代表一個(gè)類
- java.lang.reflect.Field:代表類的成員變量
- java.lang.reflect.Method:代表類的方法
- java.lang.reflect.Constructor:代表類的構(gòu)造器
五、理解Class類并獲取Class實(shí)例
5.1Class類
前面提到,反射后可以得到某個(gè)類的屬性、方法和構(gòu)造器、實(shí)現(xiàn)的接口。
- 對(duì)于每個(gè)類而言,JRE都為其保留一個(gè)不變的 Class 類型的對(duì)象;
- 一個(gè)加載的類在 JVM 中只會(huì)有一個(gè) Class 實(shí)例;
- Class 類是Reflection的根源,想要通過反射獲得任何動(dòng)態(tài)加載的、運(yùn)行的類,都必須先獲取相應(yīng)的 Class 對(duì)象。
5.2獲取Class類實(shí)例
有以下5種方式可以獲取Class類的實(shí)例:
1.若已知具體的類,可以通過類的class屬性獲取,該fang'shi最為安全可靠,且程序性能最高。
//類的class屬性 Class classOne = User.class;
2. 已知某個(gè)類的實(shí)例,通過調(diào)用該實(shí)例的getClass方法獲取Class對(duì)象。
//已有類對(duì)象的getClass方法 Class collatz = user.getClass();
3.已知一個(gè)類的全類名,且該類在類路徑下,可以通過靜態(tài)方法forName()獲取。
Class c = Class.forName("com.dcone.zhuzqc.demo.User");
4.內(nèi)置基本數(shù)據(jù)類型可以直接使用類名.Type獲取。
//內(nèi)置對(duì)象才有的TYPE屬性,較大的局限性 Class<Integer> type = Integer.TYPE;
5.利用ClassLoader(類加載器)獲取。
5.3可獲得Class對(duì)象的類型
1.class:外部類、成員(成員內(nèi)部類,靜態(tài)內(nèi)部類),局部?jī)?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)程序主動(dòng)使用某個(gè)類時(shí),如果該類還未被加載到內(nèi)存中,則系統(tǒng)會(huì)通過如下3個(gè)步驟來對(duì)該類進(jìn)行初始化。
1.類的加載(Load):將類的 class 文件字節(jié)碼內(nèi)容讀入內(nèi)存,并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu),同時(shí)創(chuàng)建一個(gè)java.lang.Class對(duì)象,此過程由類加載器完成;
2.類的鏈接(Link):將類的二進(jìn)制數(shù)據(jù)合并到 JRE 中,確保加載的類信息符合 JVM 規(guī)范,同時(shí) JVM 將常量池內(nèi)的引用替換為地址。
3.類的初始化(Initialize):JVM 負(fù)責(zé)對(duì)類進(jìn)行初始化,分為類的主動(dòng)引用和被動(dòng)引用。
類的主動(dòng)引用
- 虛擬器啟動(dòng)時(shí),先初始化main方法所在的類;
- new 類的對(duì)象;
- 調(diào)用類的靜態(tài)(static)成員和靜態(tài)(static)方法;
- 使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用;
- 如果該類的父類沒有被初始化,則會(huì)先初始化它的父類。
類的被動(dòng)引用
- 當(dāng)訪問到一個(gè)靜態(tài)域時(shí),只有真正聲明這個(gè)域的類才會(huì)被初始化;
- 通過數(shù)組定義類的引用,不會(huì)觸發(fā)此類的初始化;
- 引用常量不會(huì)觸發(fā)此類的初始化
6.2類加載器
JVM支持兩種類型的類加載器,分別為引導(dǎo)類加載器(BootstrapClassLoader)和自定義類加載器(User-Defined ClassLoader)。
從概念上來講,自定義類加載器一般指的是程序中由開發(fā)人員自定義的一類,類加載器。
但是Java虛擬機(jī)規(guī)范卻沒有這么定義,而是將所有派生于抽象類ClassLoader的類加載器都劃分為自定義類加載器。
無(wú)論類加載器的類型如何劃分,在程序中我們最常見的類加載器始終只有3個(gè),具體如下圖所示:

類加載器
所以具體為引導(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)行時(shí)類的完整對(duì)象
通過反射獲取運(yùn)行時(shí)類的完整結(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中可以通過反射獲取泛型信息的場(chǎng)景有如下三個(gè):
- (1)成員變量的泛型
- (2)方法參數(shù)的泛型
- (3)方法返回值的泛型
在Java中不可以通過反射獲取泛型信息的場(chǎng)景有如下兩個(gè):
- (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ā)中可能會(huì)遇到這樣的場(chǎng)景:獲取類的屬性釋義,這些釋義定義在類屬性的注解中。
/**
* 定義一個(gè)實(shí)體類entity
* */
@Data
class User{
@ApiModelProperty(value = "姓名")
private String userName;
@ApiModelProperty(value = "用戶id")
private Long userId;
@ApiModelProperty(value = "登錄時(shí)間")
private Date loginTime;
}
那么可以如何獲取注解中的屬性信息呢?
解決方案:
這里我們使用反射,以及java.lang下的兩個(gè)方法:
//如果指定類型的注釋存在于此元素上, 方法返回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注解 反射的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IDEA中創(chuàng)建maven項(xiàng)目webapp目錄無(wú)法識(shí)別即未被標(biāo)識(shí)的解決辦法
在學(xué)習(xí)SpringMVC課程中,基于IDEA新建maven項(xiàng)目模塊后,webapp目錄未被標(biāo)識(shí),即沒有小藍(lán)點(diǎn)的圖標(biāo)顯示,所以本文給大家介紹了IDEA中創(chuàng)建maven項(xiàng)目webapp目錄無(wú)法識(shí)別即未被標(biāo)識(shí)的解決辦法,需要的朋友可以參考下2024-03-03
java 用redisTemplate 的 Operations存取list集合操作
這篇文章主要介紹了java 用redisTemplate 的 Operations存取list集合操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Netty結(jié)合Protobuf進(jìn)行編解碼的方法
這篇文章主要介紹了Netty結(jié)合Protobuf進(jìn)行編解碼,通過文檔表述和代碼實(shí)例充分說明了如何進(jìn)行使用和操作,需要的朋友可以參考下2021-06-06
jxl 導(dǎo)出數(shù)據(jù)到excel的實(shí)例講解
下面小編就為大家分享一篇jxl 導(dǎo)出數(shù)據(jù)到excel的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2017-12-12
配置java.library.path加載庫(kù)文件問題
這篇文章主要介紹了配置java.library.path加載庫(kù)文件問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12

