Spring實戰-第一次內作業-Java反射和註解
第一次內作業
詳細理解Java反射機制
反射是什麼
反射的作用用一句簡單的話來講就是可以對程式碼進行操作的程式碼,這個特性經常在被用於建立JavaBean中,通常造輪子的人會用到這個特性,而應用程式設計師用到這個特性的場景則較少。
能夠分析類能力的程式就叫做反射,簡單來說就是可以對程式碼進行操作的程式碼。反射機制的功能極為強大,可以用來:
- 在執行時分析類的能力
- 在執行時檢視物件
- 實現通用的陣列操作程式碼
利用Method物件來實現方法
從獲取Class類開始
在程式執行期間,Java執行時系統始終為所有的物件維護一個被稱為執行時的型別標識。這個資訊跟蹤著每個物件所屬的類。這個類的獲取方式有以下三種:
使用Object類中的
getClass()
方法來返回一個Class類的例項
:User user; Class userClass = user.getClass();
我們可以使用Class類的
getName()
方法來獲取包含包名在內的類名
。同樣的,在已知這個名字的情況下,我們可以使用靜態方法forName()獲得類名對應的Class物件
:
Random generator = new Random();
Class randomClass = generator.getClass();
//className = "java.util.Random"
String className = randomClass.getName();
//第二種方式獲取
Class newRandomClass = Class.forName(className);
- 獲得Class類物件的第三種方法很簡單,如果T是任意的Java型別(或者void關鍵字),T.class將代表匹配的類物件。例如:
Class randomClass = Random.class;
Class intClass = int.class;
Class doubleClass = Double[].class;
如果我們想要建立一個類的例項,可以使用newInstance()
方法來動態建立:
String s = "java.util.Random";
Object m = Class.forName(s).newInstance();
建構函式的反射
獲得建構函式的方法
//根據指定引數獲得public構造器
Constructor getConstructor(Class[] params);
//獲得public的所有構造器
Constructor[] getConstructors();
//根據指定引數獲得public和非public的構造器
Constructor getDeclaredConstructor(Class[] params);
//獲得public的所有構造器
Constructor[] getDeclaredConstructors();
看這些方法如何使用,先來個Student類供我們反射使用
public class Student {
private static String TAG = Student.class.getSimpleName();
public int age;
private String name;
public Student() {
age = 20;
name = "小明";
}
public Student(int age, String name) {
Log.e(TAG, "Student: " + "age " + age + " name " + name);
}
public void StudentA() {
Log.e(TAG, "StudentA: ");
}
public void StudentA(int age) {
Log.e(TAG, "StudentA: " + "age " + age);
}
public void StudentA(int age, String name) {
Log.e(TAG, "StudentA: " + "age " + age + " name " + name);
}
}
利用反射分析類的能力
在java.lang.reflect包(反射庫)中有三各類Field
,Method
和Constructor
分別用於描述類的域,方法和構造器。這三個類都有一個叫做getName()
的方法,用於返回專案的名稱。Filed類有一個getType()
方法,用於返回描述域所屬型別的Class物件。Method和Constructor類有能夠報告引數型別的方法,Method類還有一個可以報告返回型別的方法。
這三個類還有一個叫做getModifiers()
的方法,它將返回一個整型數值,用不同的位開關描述public和static這樣的修飾符使用情況。另外,還可以利用java.lang.reflect包中的Modifier類的靜態方法分析getModifiers()
返回的整型數值。例如,可以使用Modifier類中的isPublic()
,isPrivate()
或isFinal()
判斷方法或構造器是否是public,private或final。我們需要做的全部工作就是呼叫Modifier類的相應方法,並對返回的整數數值進行分析,另外,還可以利用Modifier.toString()
方法將修飾符打印出來。
Class類中的getFields()
,getMethods()
和getConstructors()
方法將分別返回類提供的public域、方法和構造器陣列,其中包括超類的公有成員。Class類的getDeclareFieds()
,getDeclareMethods()
和getDeclareConstructors()
方法將分別返回類中宣告的全部域、方法和構造器,其中包括私有和受保護成員,但不包括超類的成員。
下面我們來編寫一個程式可以做到輸入類名,然後打印出這個類的全部資訊的作用:
package com.reflect.test;
import com.sun.org.apache.xpath.internal.operations.Mod;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;
public class ReflectionTest {
public static void main(String[] args) {
String name;
if(args.length > 0){
name = args[0];
}else{
Scanner in = new Scanner(System.in);
System.out.println("請輸入類名:");
name = in.next();
}
try{
Class c1 = Class.forName(name);
Class superclass = c1.getSuperclass();
String modifiers = Modifier.toString(c1.getModifiers());
if(modifiers.length() > 0){
System.out.println(modifiers + " ");
}
System.out.println("class"+name);
if(superclass != null && superclass != Object.class){
System.out.println("extends"+superclass.getName());
}
System.out.println("\n{\n");
printConstructors(c1);
System.out.println();
printMethods(c1);
System.out.println();
printFields(c1);
System.out.println("}");
}catch (ClassNotFoundException e){
e.printStackTrace();
}
System.exit(0);
}
private static void printFields(Class c1) {
Field[] fields = c1.getDeclaredFields();
for(Field field : fields){
Class type = field.getType();
String name = field.getName();
System.out.println(" ");
String modifiers = Modifier.toString(field.getModifiers());
if(modifiers.length() > 0){
System.out.println(modifiers + " ");
}
System.out.println(type.getName() + " " + name + ";");
}
}
private static void printMethods(Class c1) {
Method[] methods = c1.getDeclaredMethods();
for(Method method : methods){
Class returnType = method.getReturnType();
String name = method.getName();
System.out.println(" ");
String modifiers = Modifier.toString(method.getModifiers());
if(modifiers.length() > 0){
System.out.println(modifiers + " ");
}
System.out.println(returnType.getName()+" "+name+"(");
Class[] paramTypes = method.getParameterTypes();
for(int j = 0; j < paramTypes.length; j++){
if(j > 0){
System.out.println(",");
}
System.out.println(paramTypes[j].getName());
}
System.out.println(");");
}
}
private static void printConstructors(Class c1) {
Constructor[] constructors = c1.getDeclaredConstructors();
for(Constructor constructor : constructors){
String name = constructor.getName();
System.out.println(" ");
String modifiers = Modifier.toString(constructor.getModifiers());
if(modifiers.length() > 0){
System.out.println(modifiers + " ");
}
System.out.println(name + "(");
Class[] paramTypes = constructor.getParameterTypes();
for(int j = 0; j < paramTypes.length; j++){
if(j > 0){
System.out.println(",");
}
System.out.println(paramTypes[j].getName());
}
System.out.println(");");
}
}
}
輸入java.long.Double
回顯:
請輸入類名:
java.lang.Double
public final
Disconnected from the target VM, address: '127.0.0.1:51190', transport: 'socket'
classjava.lang.Double
extendsjava.lang.Number
{
public
java.lang.Double(
double
);
public
java.lang.Double(
java.lang.String
);
public
boolean equals(
java.lang.Object
);
public static
java.lang.String toString(
double
);
public
java.lang.String toString(
);
public
int hashCode(
);
public static
int hashCode(
double
);
public static
double min(
double
,
double
);
public static
double max(
double
,
double
);
public static native
long doubleToRawLongBits(
double
);
public static
long doubleToLongBits(
double
);
public static native
double longBitsToDouble(
long
);
public volatile
int compareTo(
java.lang.Object
);
public
int compareTo(
java.lang.Double
);
public
byte byteValue(
);
public
short shortValue(
);
public
int intValue(
);
public
long longValue(
);
public
float floatValue(
);
public
double doubleValue(
);
public static
java.lang.Double valueOf(
java.lang.String
);
public static
java.lang.Double valueOf(
double
);
public static
java.lang.String toHexString(
double
);
public static
int compare(
double
,
double
);
public static
boolean isNaN(
double
);
public
boolean isNaN(
);
public static
boolean isFinite(
double
);
public static
boolean isInfinite(
double
);
public
boolean isInfinite(
);
public static
double sum(
double
,
double
);
public static
double parseDouble(
java.lang.String
);
public static final
double POSITIVE_INFINITY;
public static final
double NEGATIVE_INFINITY;
public static final
double NaN;
public static final
double MAX_VALUE;
public static final
double MIN_NORMAL;
public static final
double MIN_VALUE;
public static final
int MAX_EXPONENT;
public static final
int MIN_EXPONENT;
public static final
int SIZE;
public static final
int BYTES;
public static final
java.lang.Class TYPE;
private final
double value;
private static final
long serialVersionUID;
}
Process finished with exit code 0
不積跬步無以至千里
思維導圖
擴充套件閱讀
Java註解
概念及作用
概念
- 註解即元資料,就是原始碼的元資料
- 註解在程式碼中新增資訊提供了一種形式化的方法,可以在後續中更方便的 使用這些資料
- Annotation是一種應用於類、方法、引數、變數、構造器及包宣告中的特殊修飾符。它是一種由JSR-175標準選擇用來描述元資料的一種工具。
作用
- 生成文件
- 跟蹤程式碼依賴性,實現替代配置檔案功能,減少配置。如Spring中的一些註解
- 在編譯時進行格式檢查,如@Override等
- 每當你建立描述符性質的類或者介面時,一旦其中包含重複性的工作,就可以考慮使用註解來簡化與自動化該過程。
什麼是java註解?
在java語法中,使用@
符號作為開頭,並在@後面緊跟註解名。被運用於類,介面,方法和欄位之上,例如:
@Override
void myMethod() {
......
}
這其中@Override就是註解。這個註解的作用也就是告訴編譯器,myMethod()方法覆寫了父類中的myMethod()方法。
java中內建的註解
java中有三個內建的註解:
@Override:表示當前的方法定義將覆蓋超類中的方法,如果出現錯誤,編譯器就會報錯。
@Deprecated:如果使用此註解,編譯器會出現警告資訊。
@SuppressWarnings:忽略編譯器的警告資訊。
本文不在闡述三種內建註解的使用情節和方法,感興趣的請看這裡
元註解
自定義註解的時候用到的,也就是自定義註解的註解;(這句話我自己說的,不知道對不對)
元註解的作用就是負責註解其他註解。Java5.0
定義了4個標準的meta-annotation型別,它們被用來提供對其它 annotation型別作說明。
Java5.0
定義的4個元註解:
- @Target
- @Retention
- @Documented
- @Inherited
java8加了兩個新註解,後續我會講到。
這些型別和它們所支援的類在java.lang.annotation包中可以找到。
@Target
@Target說明了Annotation所修飾的物件範圍:Annotation可被用於 packages、types(類、介面、列舉、Annotation型別)、型別成員(方法、構造方法、成員變數、列舉值)、方法引數和本地變數(如迴圈變數、catch引數)。在Annotation型別的宣告中使用了target可更加明晰其修飾的目標。
作用:用於描述註解的使用範圍(即:被描述的註解可以用在什麼地方)
取值(ElementType)有:
型別 | 用途 |
---|---|
CONSTRUCTOR | 用於描述構造器 |
FIELD | 用於描述域 |
LOCAL_VARIABLE | 用於描述區域性變數 |
METHOD | 用於描述方法 |
PACKAGE | 用於描述包 |
PARAMETER | 用於描述引數 |
TYPE | 用於描述類、介面(包括註解型別) 或enum宣告 |
比如說這個註解表示只能在方法中使用:
@Target({ElementType.METHOD})
public @interface MyCustomAnnotation {
}
//使用
public class MyClass {
@MyCustomAnnotation
public void myMethod()
{
......
}
}
@Retention
@Retention定義了該Annotation被保留的時間長短:某些Annotation僅出現在原始碼中,而被編譯器丟棄;而另一些卻被編譯在class檔案中;編譯在class檔案中的Annotation可能會被虛擬機器忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因為Annotation與class在使用上是被分離的)。使用這個meta-Annotation可以對 Annotation的“生命週期”限制。
作用:表示需要在什麼級別儲存該註釋資訊,用於描述註解的生命週期(即:被描述的註解在什麼範圍內有效)
取值(RetentionPoicy)有:
型別 | 用途 | 說明 |
---|---|---|
SOURCE | 在原始檔中有效(即原始檔保留) | 僅出現在原始碼中,而被編譯器丟棄 |
CLASS | 在class檔案中有效(即class保留) | 被編譯在class檔案中 |
RUNTIME | 在執行時有效(即執行時保留) | 編譯在class檔案中 |
使用示例:
/***
* 欄位註解介面
*/
@Target(value = {ElementType.FIELD})//註解可以被新增在屬性上
@Retention(value = RetentionPolicy.RUNTIME)//註解儲存在JVM執行時刻,能夠在執行時刻通過反射API來獲取到註解的資訊
public @interface Column {
String name();//註解的name屬性
}
@Documented
@Documented用於描述其它型別的annotation應該被作為被標註的程式成員的公共API,因此可以被例如javadoc此類的工具文件化。Documented是一個標記註解,沒有成員。
作用:將註解包含在javadoc中
示例:
java.lang.annotation.Documented
@Documented
public @interface MyCustomAnnotation { //Annotation body}
@Inherited
是一個標記註解
闡述了某個被標註的型別是被繼承的
使用了@Inherited修飾的annotation型別被用於一個class,則這個annotation將被用於該class的子類 @Inherited annotation型別是被標註過的class的子類所繼承。類並不從實現的介面繼承annotation,方法不從它所過載的方法繼承annotation
當@Inherited annotation型別標註的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation型別的annotation時,反射程式碼檢查將展開工作:檢查class和其父類,直到發現指定的annotation型別被發現,或者到達類繼承結構的頂層。
作用:允許子類繼承父類中的註解
示例,這裡的MyParentClass 使用的註解標註了@Inherited,所以子類可以繼承這個註解資訊:
java.lang.annotation.Inherited
@Inherited
public @interface MyCustomAnnotation {
}
@MyCustomAnnotation
public class MyParentClass {
...
}
public class MyChildClass extends MyParentClass {
...
}
自定義註解
格式
public @interface 註解名{
定義體
}
註解引數的可支援資料型別:
- 所有基本資料型別(int,float,double,boolean,byte,char,long,short)
- String 型別
- Class型別
- enum型別
- Annotation型別
- 以上所有型別的陣列
規則
- 修飾符只能是public 或預設(default)
- 引數成員只能用基本型別byte,short,int,long,float,double,boolean八種基本型別和String,Enum,Class,annotations及這些型別的陣列
- 如果只有一個引數成員,最好將名稱設為”value”
- 註解元素必須有確定的值,可以在註解中定義預設值,也可以使用註解時指定,非基本型別的值不可為null,常使用空字串或0作預設值
- 在表現一個元素存在或缺失的狀態時,定義一下特殊值來表示,如空字串或負值
示例:
/**
* test註解
* @author ddk
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
/**
* id
* @return
*/
public int id() default -1;
/**
* name
* @return
*/
public String name() default "";
}
註解處理器類庫
java.lang.reflect.AnnotatedElement
Java使用Annotation介面來代表程式元素前面的註解,該介面是所有Annotation型別的父介面。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement介面,該介面代表程式中可以接受註解的程式元素,該介面主要有如下幾個實現類:
- Class:類定義
- Constructor:構造器定義
- Field:累的成員變數定義
- Method:類的方法定義
- Package:類的包定義
java.lang.reflect 包下主要包含一些實現反射功能的工具類,實際上,java.lang.reflect 包所有提供的反射API擴充了讀取執行時Annotation資訊的能力。當一個Annotation型別被定義為執行時的Annotation後,該註解才能是執行時可見,當class檔案被裝載時被儲存在class檔案中的Annotation才會被虛擬機器讀取。
AnnotatedElement 介面是所有程式元素(Class、Method和Constructor)的父介面,所以程式通過反射獲取了某個類的AnnotatedElement物件之後,程式就可以呼叫該物件的如下四個個方法來訪問Annotation資訊:
- 方法1: T getAnnotation(Class annotationClass): 返回改程式元素上存在的、指定型別的註解,如果該型別註解不存在,則返回null。
- 方法2:Annotation[] getAnnotations():返回該程式元素上存在的所有註解。
- 方法3:boolean is AnnotationPresent(Class
註解處理器示例:
/***********註解宣告***************/
/**
* 水果名稱註解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
/**
* 水果顏色註解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 顏色列舉
* @author peida
*
*/
public enum Color{ BULE,RED,GREEN};
/**
* 顏色屬性
* @return
*/
Color fruitColor() default Color.GREEN;
}
/**
* 水果供應者註解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供應商編號
* @return
*/
public int id() default -1;
/**
* 供應商名稱
* @return
*/
public String name() default "";
/**
* 供應商地址
* @return
*/
public String address() default "";
}
/***********註解使用***************/
public class Apple {
@FruitName("Apple")
private String appleName;
@FruitColor(fruitColor=Color.RED)
private String appleColor;
@FruitProvider(id=1,name="陝西紅富士集團",address="陝西省西安市延安路89號紅富士大廈")
private String appleProvider;
public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}
public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}
public void displayName(){
System.out.println("水果的名字是:蘋果");
}
}
/***********註解處理器***************/
//其實是用的反射
public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz){
String strFruitName=" 水果名稱:";
String strFruitColor=" 水果顏色:";
String strFruitProvicer="供應商資訊:";
Field[] fields = clazz.getDeclaredFields();
for(Field field :fields){
if(field.isAnnotationPresent(FruitName.class)){
FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
strFruitName=strFruitName+fruitName.value();
System.out.println(strFruitName);
}
else if(field.isAnnotationPresent(FruitColor.class)){
FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);
strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
System.out.println(strFruitColor);
}
else if(field.isAnnotationPresent(FruitProvider.class)){
FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);
strFruitProvicer=" 供應商編號:"+fruitProvider.id()+" 供應商名稱:"+fruitProvider.name()+" 供應商地址:"+fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}
/***********輸出結果***************/
public class FruitRun {
/**
* @param args
*/
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}
====================================
水果名稱:Apple
水果顏色:RED
供應商編號:1 供應商名稱:陝西紅富士集團 供應商地址:陝西省西安市延安路89號紅富士大廈
Java 8 中註解新特性
@Repeatable 元註解,表示被修飾的註解可以用在同一個宣告式或者型別加上多個相同的註解(包含不同的屬性值)
@Native 元註解,本地方法
java8 中Annotation 可以被用在任何使用 Type 的地方
//初始化物件時
String myString = new @NotNull String();
//物件型別轉化時
myString = (@NonNull String) str;
//使用 implements 表示式時
class MyList<T> implements @ReadOnly List<@ReadOnly T>{
...
}
//使用 throws 表示式時
public void validateValues() throws @Critical ValidationFailedException{
...
}