註解(Annotation)不是程式,但可以對程式作出解釋,也可以被其它程式(如編譯器)讀取。
註解的格式:以@註釋名在程式碼中存在,還可以新增一些引數值例如@SuppressWarnings(value="unchecked")。
註解可在package、class、method、field等上面使用,作用是為它們新增了額外的輔助資訊,從而可以通過反射機制實現對這些後設資料的存取。
表示某方法旨在覆蓋超類中的方法宣告,該方法將覆蓋或實現在超類中宣告的方法。
@RequestMapping註解的主要用途是將Web請求與請求處理類中的方法進行對映,注意有以下幾個屬性:
@RequestBody在處理請求方法的參數列中使用,它可以將請求主體中的引數繫結到一個物件中,請求主體引數是通過HttpMessageConverter傳遞的,根據請求主體中的引數名與物件的屬性名進行匹配並繫結值。此外,還可以通過@Valid註解對請求主體中的引數進行校驗。
@GetMapping註解用於處理HTTP GET請求,並將請求對映到具體的處理方法中。具體來說,@GetMapping是一個組合註解,它相當於是@RequestMapping(method=RequestMethod.GET)的快捷方式。
@PathVariable註解是將方法中的引數繫結到請求URI中的模板變數上。可以通過@RequestMapping註解來指定URI的模板變數,然後使用@PathVariable註解將方法中的引數繫結到模板變數上。
@RequestParam註解用於將方法的引數與Web請求的傳遞的引數進行繫結。使用@RequestParam可以輕鬆的存取HTTP請求引數的值。
@ComponentScan註解用於設定Spring需要掃描的被元件註解註釋的類所在的包。可以通過設定其basePackages屬性或者value屬性來設定需要掃描的包路徑。value屬性是basePackages的別名。
@Component註解用於標註一個普通的元件類,它沒有明確的業務範圍,只是通知Spring被此註解的類需要被納入到Spring Bean容器中並進行管理。
@Service註解是@Component的一個延伸(特例),它用於標註業務邏輯類。與@Component註解一樣,被此註解標註的類,會自動被Spring所管理。
@Repository註解也是@Component註解的延伸,與@Component註解一樣,被此註解標註的類會被Spring自動管理起來,@Repository註解用於標註DAO層的資料持久化類。
4個元個元註解分別是:@Target、@Retention、@Documented、@Inherited 。
再次強調下元註解是java API提供,是專門用來定義註解的註解。
@Target
描述註解能夠作用的位置,ElementType取值:
@Retention
表示需要在什麼級別儲存該註釋資訊(生命週期):
RetentionPolicy.RUNTIME
:記憶體中的位元組碼,VM將在執行時也保留註解,因此可以通過反射機制讀取註解的資訊@Documented
描述註解是否被抽取到api檔案中。
@Inherited
描述註解是否被子類繼承。
學習自定義註解對於理解Spring框架十分有好處,即使在實際專案中可能不需要使用自定義註解,但可以幫助我們掌握Spring的一些底層原理,從而提高對整體專案的把握。
/**
* 自定義註解
* @author Created by zhuzqc on 2022/5/31 23:03
*/
public class CustomAnnotation {
/**
* 註解中可以為引數賦值,如果沒有預設值,那麼必須為註解的引數賦值
* */
@MyAnnotation(value = "解釋")
public void test(){
}
}
/**
* @author zhuzqc
*/
//自定義註解必須的元註解target,指明註解的作用域(此處指明的是在類和方法上起作用)
@Target({ElementType.TYPE,ElementType.METHOD})
//元註解retention宣告該註解在何時起作用(此處指明的是在執行時起作用)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
//註解中需宣告引數,格式為:引數型別 + 引數名();
String value() default "";
}
是一種在執行時可以改變其結構的語言,例如新的函數、物件甚至程式碼可以被引進,已有的函數可以被刪除或是進行其它結構上的變化。
主要的動態語言有:Object-C、C#、PHP、Python、JavaScript 等。
以 JavaScript 語言舉例:
/**
* 由於未指定var的具體型別,函數在執行時間可以改變var的型別
* */
function f(){
var x = "var a = 3; var b = 5; alert(a+b)";
eval(x)
}
Reflection(反射)是 Java 被視為準動態語言的關鍵:反射機制允許程式在執行期間藉助 Reflection API 獲取任何類的內部資訊,並能直接操作任意物件的內部屬性及方法。
Class c = Class.forName("java.lang.String")
載入完類後,在堆記憶體的方法區就產生了一個Class型別的物件(一個類只有一個Class物件),這個類就包含了完整的類的結構資訊。我沒可以通過這個物件,像鏡子一樣看到類的結構,這個過程形象地被稱之為反射。
通過程式碼更易於理解:
/**
* 反射的概念
* @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");
//一個類在記憶體中只有唯一個Class物件
System.out.println(c.hashCode());
}
}
/**
* 定義一個實體類entity
* */
@Data
class User{
private String userName;
private Long userId;
private Date loginTime;
}
由於該類繼承 Object,在 Object 類中有 getClass() 方法,該方法被所有子類繼承:
@HotSpotIntrinsicCandidate
public final native Class<?> getClass();
注:該方法的返回值型別是一個 Class 類,該類是 Java 反射的源頭。
反射的優點:執行期型別的判斷、動態載入類、提高程式碼靈活度。
java.lang.Class:代表一個類
java.lang.reflect.Field:代表類的成員變數
java.lang.reflect.Method:代表類的方法
java.lang.reflect.Constructor:代表類的構造器
前面提到,反射後可以得到某個類的屬性、方法和構造器、實現的介面。
有以下5種方式可以獲取Class類的範例:
若已知具體的類,可以通過類的class屬性獲取,該fang'shi最為安全可靠,且程式效能最高。
//類的class屬性
Class classOne = User.class;
2. 已知某個類的範例,通過呼叫該範例的getClass方法獲取Class物件。
```java
//已有類物件的getClass方法
Class collatz = user.getClass();
已知一個類的全類名,且該類在類路徑下,可以通過靜態方法forName()獲取。
Class c = Class.forName("com.dcone.zhuzqc.demo.User");
內建基本資料型別可以直接使用類名.Type獲取。
//內建物件才有的TYPE屬性,較大的侷限性
Class<Integer> type = Integer.TYPE;
利用ClassLoader(類載入器)獲取。
class:外部類、成員(成員內部類,靜態內部類),區域性內部類,匿名內部類;
//類可以反射
Class c1 = Person.class;
interface:所有介面;
//介面可以反射
Class c2 = Comparable.class;
[]:陣列;
//陣列可以反射
Class c3 = String[].class;
Class c4 = int[][].class;
enum:列舉;
//列舉可以反射
Class c6 = ElementType.class;
annotation:註解(@interface);
//註解可以反射
Class c5 = Data.class;
基本資料型別;
//基本資料型別(包裝類)可以反射
Class c7 = int.class;
Class c8 = Integer.class;
void。
//void可以反射
Class c9 = void.class;
當程式主動使用某個類時,如果該類還未被載入到記憶體中,則系統會通過如下3個步驟來對該類進行初始化。
類的載入(Load):將類的 class 檔案位元組碼內容讀入記憶體,並將這些靜態資料轉換成方法區執行時的資料結構,同時建立一個java.lang.Class物件,此過程由類載入器完成;
類的連結(Link):將類的二進位制資料合併到 JRE 中,確保載入的類資訊符合 JVM 規範,同時 JVM 將常數池內的參照替換為地址。
類的初始化(Initialize):JVM 負責對類進行初始化,分為類的主動參照和被動參照。
JVM支援兩種型別的類載入器,分別為引導類載入器(BootstrapClassLoader)和自定義類載入器(User-Defined ClassLoader)。
從概念上來講,自定義類載入器一般指的是程式中由開發人員自定義的一類,類載入器。
但是Java虛擬機器器規範卻沒有這麼定義,而是將所有派生於抽象類ClassLoader的類載入器都劃分為自定義類載入器。
無論類載入器的型別如何劃分,在程式中我們最常見的類載入器始終只有3個,具體如下圖所示:
所以具體為引導類載入器(BootstrapClassLoader)和自定義類載入器(包括ExtensionClassLoader、Application ClassLoader(也叫System ClassLoader)、User Defined ClassLoader)。
public class Test03 {
public static void main(String[] args) {
//獲取系統類的載入器
ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
System.out.println(sysLoader);
//獲取系統類的父類別載入器
ClassLoader parent = sysLoader.getParent();
System.out.println(parent);
}
}
通過反射獲取執行時類的完整結構:Field、Method、Constructor、Superless、Interface、Annotation等。
即:實現的全部介面、所繼承的父類別、全部的構造器、全部的方法、全部的成員變數(區域性變數)、註解等。
/**
* @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 中採用泛型擦除的機制來引入泛型,Java 中的泛型僅僅是給編譯器 javac 使用的,目的是確保資料的安全性以及免去強制型別轉換的問題。一旦編譯完成,所有和泛型相關的型別全部擦除。
在Java中可以通過反射獲取泛型資訊的場景有如下三個:
在Java中不可以通過反射獲取泛型資訊的場景有如下兩個:
要獲取泛型資訊,必須要注意ParameterizedType類,該類中的getActualTypeArguments()方法可以有效獲取泛型資訊。
下面以獲取成員方法引數的泛型型別資訊為例:
public class Demo {
public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
// 獲取成員方法引數的泛型型別資訊
getMethodParametricGeneric();
}
/**
* 獲取方法引數的泛型型別資訊
*
* @throws NoSuchMethodException
*/
public static void getMethodParametricGeneric() throws NoSuchMethodException {
// 獲取MyTestClass類中名為"setList"的方法
Method setListMethod = MyClass.class.getMethod("setList", List.class);
// 獲取該方法的引數型別資訊(帶有泛型)
Type[] genericParameterTypes = setListMethod.getGenericParameterTypes();
// 但我們實際上需要獲取返回值型別中的泛型資訊,所以要進一步判斷,即判斷獲取的返回值型別是否是引數化型別ParameterizedType
for (Type genericParameterType : genericParameterTypes) {
ParameterizedType parameterizedType = (ParameterizedType) genericParameterType;
// 獲取成員方法引數的泛型型別資訊
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
Class realType = (Class) actualTypeArgument;
System.out.println("成員方法引數的泛型資訊:" + realType);
}
}
}
在開發中可能會遇到這樣的場景:獲取類的屬性釋義,這些釋義定義在類屬性的註解中。
/**
* 定義一個實體類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:獲取方法引數上的註解
@Bean("sqlSessionFactory")
public String test(@RequestBody User user) throws ClassNotFoundException {
Class c2 = Class.forName("com.dcone.zhuzqc.demo.User");
// 獲取方法引數註解
Method[] methods2 = User.class.getDeclaredMethods();
for (Method m : methods2) {
// 獲取方法的所有引數
Parameter[] parameters = m.getParameters();
for (Parameter p : parameters) {
// 判斷是否存在註解
if (p.isAnnotationPresent(ApiModelProperty.class)) {
System.out.println(p.getAnnotation(ApiModelProperty.class).name());
}
}
}
return "test";
}