Java反射使用總結
最近公司招了幾名剛畢業的大學生,在給他們培訓的過程中,講到反射,他們有些人聽不懂,對反射的概念雲裡霧裡的,不知道反射有什麼用。
因此就有了本文的誕生。
反射是java提供的一個重要功能,可以在執行時檢查類、介面、方法和變數等資訊,無需知道類的名字,方法名等。還可以在執行時例項化新物件,呼叫方法以及設定和獲取變數值。
反射非常強大和有用,很多java框架中都有反射的影子,例如spring、mybatis等等,
JDBC利用反射將資料庫的表字段對映到java物件的getter/setter方法。
Jackson, GSON, Boon等類庫也是利用反射將JSON檔案的屬性對映到java對的象getter/setter方法。
可見,只要使用java,反射就無處不在。
Class物件
檢查一個類之前,必須獲取到java.lang.Class物件,java中的所有型別,包括long,int,陣列等基本資料型別,都和Class物件有關係。
我們很多人去醫院參加體檢的時候,都做過B超檢查,醫生只需把一個探頭在我們身上滑動就可以將我們體內的肝、膽、腎等器官反射到B超裝置上顯示。
Class類物件就相當於B超的探頭,將一個類的方法、變數、介面、類名、類修飾符等資訊告訴執行的程式。
Java提供了兩種方式獲取Class物件,一種是使用.class,另外一種是使用Class.forName()。
.class方式適用於在編譯時已經知道具體的類。
Class alunbarClass = Alunbar.class;
Class.forName()方式適用於執行時動態獲取Class物件,只需將類名作為forName方法的引數:
try{
Class alunbarClass1 = Class.forName("Alunbar");
}
catch(ClassNotFoundException e){
System.out.println("找不到Alunbar類");
}
這個方法會出現類找不到的情況,因此使用這個方法獲取Class物件時,必須捕獲ClassNotFoundException異常。
獲取類名
package cn.alunbar;
public class Alunbar {
public static void main(String arts[]){
Class alunbarClass = Alunbar.class;
System.out.println(alunbarClass.getName());
System.out.println(alunbarClass.getSimpleName());
}
}
上面程式碼執行結果如下:
cn.alunbar.Alunbar
Alunbar
getName()方法獲取的類名包含包資訊。getSimpleName()方法只是獲取類名,不包含包資訊。
獲取類修飾符
public class Alunbar {
public static void main(String arts[]){
Class alunbarClass = Alunbar.class;
System.out.println(alunbarClass.getModifiers());
System.out.println(Modifier.isPublic(alunbarClass.getModifiers()));
Class birdClass = Bird.class;
System.out.println(birdClass.getModifiers());
System.out.println(Modifier.isPublic(birdClass.getModifiers()));
}
private class Bird{
}
}
類修飾符有public、private等型別,getModifiers()可以獲取一個類的修飾符,但是返回的結果是int,結合Modifier提供的方法,就可以確認修飾符的型別。
Modifier.isAbstract(int modifiers)
Modifier.isFinal(int modifiers)
Modifier.isInterface(int modifiers)
Modifier.isNative(int modifiers)
Modifier.isPrivate(int modifiers)
Modifier.isProtected(int modifiers)
Modifier.isPublic(int modifiers)
Modifier.isStatic(int modifiers)
Modifier.isStrict(int modifiers)
Modifier.isSynchronized(int modifiers)
Modifier.isTransient(int modifiers)
Modifier.isVolatile(int modifiers)
獲取包資訊
package cn.alunbar;
public class Alunbar {
public static void main(String arts[]){
Class birdClass = Bird.class;
System.out.println(birdClass.getPackage());
}
private class Bird{
}
}
getPackage()方法獲取包資訊。上面程式碼執行的結果:
package cn.alunbar
獲取父類的Class物件
public class Alunbar {
public static void main(String arts[]){
Class birdClass = Bird.class;
Class superclass = birdClass.getSuperclass();
System.out.println(superclass.getSimpleName());
}
private class Bird extends Animal{
}
private class Animal{
}
}
上面程式碼執行的結果:
Animal
getSuperclass()方法返回的父類的Class物件。
獲取介面資訊
獲取介面資訊的方法:
Class[] interfaces = birdClass.getInterfaces();
一個類可以實現多個介面,所以getInterfaces()方法返回的是Class[]陣列。
注意:getInterfaces()只返回指定類實現的介面,不會返父類實現的介面。
獲取建構函式Constructor
獲取建構函式的方法:
Class birdClass = Bird.class;
Constructor[] constructors = birdClass.getConstructors();
一個類會有多個建構函式,getConstructors()返回的是Constructor[]陣列,包含了所有宣告的用public修飾的建構函式。
如果你已經知道了某個構造的引數,可以通過下面的方法獲取到迴應的建構函式物件:
public class Alunbar {
public static void main(String arts[]){
Class birdClass = Bird.class;
try{
Constructor constructors = birdClass.getConstructor(new Class[]{String.class});
}catch(NoSuchMethodException e){
}
}
private class Bird {
public Bird(){
}
public Bird(String eat){
}
}
}
上面獲取建構函式的方式有2點需要注意:
1、只能獲取到public修飾的建構函式。
2、需要捕獲NoSuchMethodException異常。
獲取建構函式的引數
獲取到建構函式的物件之後,可以通過getParameterTypes()獲取到建構函式的引數。
Constructor constructors = birdClass.getConstructor(new Class[]{String.class});
Class[] parameterTypes = constructors.getParameterTypes();
初始化物件
通過反射獲取到構造器之後,通過newInstance()方法就可以生成類物件。
public class Alunbar {
public static void main(String arts[]){
Class birdClass = Bird.class;
try{
Constructor constructors = birdClass.getConstructor(new Class[]{String.class});
Bird bird = (Bird)constructors.newInstance("eat tea");
}catch(Exception e){
System.out.println("沒有對應的建構函式");
}
}
class Bird {
public Bird(){
}
protected Bird(String eat){
}
}
}
newinstance()方法接受可選數量的引數,必須為所呼叫的建構函式提供準確的引數。如果建構函式要求String的引數,在呼叫newinstance()方法是,必須提供String型別的引數。
獲取Methods方法資訊
下面程式碼是通過反射可以獲取到該類的宣告的成員方法資訊:
Method[] metchods = birdClass.getMethods();
Method[] metchods1 = birdClass.getDeclaredMethods();
Method eatMetchod = birdClass.getMethod("eat", new Class[]{int.class});
Method eatMetchod1 = birdClass.getDeclaredMethod("eat", new Class[]{int.class});
無參的getMethods()獲取到所有public修飾的方法,返回的是Method[]陣列。
無參的getDeclaredMethods()方法到的是所有的成員方法,和修飾符無關。
對於有參的getMethods()方法,必須提供要獲取的方法名以及方法名的引數。如果要獲取的方法沒有引數,則用null替代:
Method eatMetchod = birdClass.getMethod("eat", null);
無參的getMethods()和getDeclaredMethods()都只能獲取到類宣告的成員方法,不能獲取到繼承父類的方法。
獲取成員方法引數
Class birdClass = Bird.class;
Class[] parameterTypes = eatMetchod1.getParameterTypes();
獲取成員方法返回型別
Class birdClass = Bird.class;
Class returnType = eatMetchod1.getReturnType();
invoke()方法
java反射提供invoke()方法,在執行時根據業務需要呼叫相應的方法,這種情況在執行時非常常見,只要通過反射獲取到方法名之後,就可以呼叫對應的方法:
Class birdClass = Bird.class;
Constructor constructors1 = birdClass.getConstructor();
Method eatMetchod = birdClass.getMethod("eat", new Class[]{int.class});
System.out.println(eatMetchod.invoke(constructors1.newInstance(), 2));
invoke方法有兩個引數,第一個引數是要呼叫方法的物件,上面的程式碼中就是Bird的物件,第二個引數是呼叫方法要傳入的引數。如果有多個引數,則用陣列。
如果呼叫的是static方法,invoke()方法第一個引數就用null代替:
public class Alunbar {
public static void main(String arts[]){
try{
Class birdClass = Bird.class;
Constructor constructors1 = birdClass.getConstructor();
Method eatMetchod = birdClass.getMethod("eat", new Class[]{int.class});
System.out.println(eatMetchod.invoke(null, 2));
}catch(Exception e){
e.printStackTrace();
System.out.println("沒有對應的建構函式");
}
}
}
class Bird{
public static int eat(int eat){
return eat;
}
public Bird(){
}
public Bird(String eat){
}
private void talk(){}
}
class Animal{
public void run(){
}
}
使用反射可以在執行時檢查和呼叫類宣告的成員方法,可以用來檢測某個類是否有getter和setter方法。getter和setter是java bean必須有的方法。
getter和setter方法有下面的一些規律:
getter方法以get為字首,無參,有返回值
setter方法以set為字首,有一個引數,返回值可有可無,
下面的程式碼提供了檢測一個類是否有getter和setter方法:
public static void printGettersSetters(Class aClass){
Method[] methods = aClass.getMethods();
for(Method method : methods){
if(isGetter(method)) System.out.println("getter: " + method);
if(isSetter(method)) System.out.println("setter: " + method);
}
}
public static boolean isGetter(Method method){
if(!method.getName().startsWith("get")) return false;
if(method.getParameterTypes().length != 0) return false;
if(void.class.equals(method.getReturnType()) return false;
return true;
}
public static boolean isSetter(Method method){
if(!method.getName().startsWith("set")) return false;
if(method.getParameterTypes().length != 1) return false;
return true;
}
獲取成員變數
通過反射可以在執行時獲取到類的所有成員變數,還可以給成員變數賦值和獲取成員變數的值。
Class birdClass = Bird.class;
Field[] fields1 = birdClass.getFields();
Field[] fields2 = birdClass.getDeclaredFields();
Field fields3 = birdClass.getField("age");
Field fields4 = birdClass.getDeclaredField("age");
getFields()方法獲取所有public修飾的成員變數,getField()方法需要傳入變數名,並且變數必須是public修飾符修飾。
getDeclaredFields方法獲取所有生命的成員變數,不管是public還是private。
獲取成員變數型別
Field fields4 = birdClass.getDeclaredField("age");
Object fieldType = fields4.getType();
成員變數賦值和取值
一旦獲取到成員變數的Field引用,就可以獲取通過get()方法獲取變數值,通過set()方法給變數賦值:
Class birdClass = Bird.class;
Field fields3 = birdClass.getField("age");
Bird bird = new Bird();
Object value = fields3.get(bird);
fields3.set(bird, value);
訪問私有變數
有很多文章討論禁止通過反射訪問一個物件的私有變數,但是到目前為止所有的jdk還是允許通過反射訪問私有變數。
使用 Class.getDeclaredField(String name)或者Class.getDeclaredFields()才能獲取到私有變數。
package field;
import java.lang.reflect.Field;
public class PrivateField {
protected String name;
public PrivateField(String name){
this.name = name;
}
}
public class PrivateFieldTest {
public static void main(String args[])throws Exception{
Class privateFieldClass = PrivateField.class;
Field privateName = privateFieldClass.getDeclaredField("name");
privateName.setAccessible(false);
PrivateField privateField = new PrivateField("Alunbar");
String privateFieldValue = (String) privateName.get(privateField);
System.out.println("私有變數值:" + privateFieldValue);
}
}
上面的程式碼有點需要注意:必須呼叫setAccessible(true)方法,這是針對私有變數而言,public和protected等都不需要。這個方法是允許通過反射訪問類的私有變數。
訪問私有方法
和私有變數一樣,私有方法也是不允許其他的類隨意呼叫的,但是通過反射可以饒過這一限制。
使用Class.getDeclaredMethod(String name, Class[] parameterTypes)或者Class.getDeclaredMethods()方法獲取到私有方法。
public class PrivateMethod {
private String accesPrivateMethod(){
return "成功訪問私有方法";
}
}
public class PrivateMethodTest {
public static void main(String args[])throws Exception{
Class privateMethodClass = PrivateMethod.class;
Method privateStringMethod = privateMethodClass.getDeclaredMethod("accesPrivateMethod", null);
privateStringMethod.setAccessible(true);
String returnValue = (String)privateStringMethod.invoke(new PrivateMethod(), null);
System.out.println("returnValue = " + returnValue);
}
}
和訪問私有變數一樣,也要呼叫setAccessible(true)方法,允許通過反射訪問類的私有方法。
訪問類註解資訊
通過反射可以在執行時獲取到類、方法、變數和引數的註解資訊。
訪問類的所有註解資訊:
Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
訪問類特定的註解資訊:
Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
訪問方法註解資訊:
Method method = ... //obtain method object
Annotation[] annotations = method.getDeclaredAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
訪問特定方法註解資訊:
Method method = ... // obtain method object
Annotation annotation = method.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
訪問引數註解資訊:
Method method = ... //obtain method object
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();
int i=0;
for(Annotation[] annotations : parameterAnnotations){
Class parameterType = parameterTypes[i++];
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("param: " + parameterType.getName());
System.out.println("name : " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
}
Method.getParameterAnnotations()方法返回的是一個二維的Annotation陣列,其中包含每個方法引數的註解陣列。
訪問類所有變數註解資訊:
Field field = ... //obtain field object
Annotation[] annotations = field.getDeclaredAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
訪問類某個特定變數的註解資訊:
Field field = ... // obtain method object
Annotation annotation = field.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
獲取泛型資訊
很多人認為java類在編譯的時候會把泛型資訊給擦除掉,所以在執行時是無法獲取到泛型資訊的。其實在某些情況下,還是可以通過反射在執行時獲取到泛型資訊的。
獲取到java.lang.reflect.Method
物件,就有可能獲取到某個方法的泛型返回資訊。
泛型方法返回型別
下面的類中定義了一個返回值中有泛型的方法:
public class MyClass {
protected List<String> stringList = ...;
public List<String> getStringList(){
return this.stringList;
}
}
下面的程式碼使用反射檢測getStringList()方法返回的是List<String>
而不是List
Method method = MyClass.class.getMethod("getStringList", null);
Type returnType = method.getGenericReturnType();
if(returnType instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType) returnType;
Type[] typeArguments = type.getActualTypeArguments();
for(Type typeArgument : typeArguments){
Class typeArgClass = (Class) typeArgument;
System.out.println("typeArgClass = " + typeArgClass);
}
}
上面這段程式碼會列印:typeArgClass = java.lang.String
泛型方法引數型別
下面的類定義了一個有泛型引數的方法setStringList():
public class MyClass {
protected List<String> stringList = ...;
public void setStringList(List<String> list){
this.stringList = list;
}
}
Method類提供了getGenericParameterTypes()方法獲取方法的泛型引數。
method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
上面的程式碼會打印出parameterArgType = java.lang.String
泛型變數型別
通過反射也可以獲取到類的成員泛型變數資訊——靜態變數或例項變數。下面的類定義了一個泛型變數:
public class MyClass {
public List<String> stringList = ...;
}
通過反射的Filed物件獲取到泛型變數的型別資訊:
Field field = MyClass.class.getField("stringList");
Type genericFieldType = field.getGenericType();
if(genericFieldType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericFieldType;
Type[] fieldArgTypes = aType.getActualTypeArguments();
for(Type fieldArgType : fieldArgTypes){
Class fieldArgClass = (Class) fieldArgType;
System.out.println("fieldArgClass = " + fieldArgClass);
}
}
Field物件提供了getGenericType()方法獲取到泛型變數。
上面的程式碼會打印出:fieldArgClass = java.lang.String
動態代理
使用反射可以在執行時建立介面的動態實現,java.lang.reflect.Proxy類提供了建立動態實現的功能。我們把執行時建立介面的動態實現稱為動態代理。
動態代理可以用於許多不同的目的,例如資料庫連線和事務管理、用於單元測試的動態模擬物件以及其他類似aop的方法攔截等。
建立代理
呼叫java.lang.reflect.Proxy類的newProxyInstance()方法就可以常見動態代理,newProxyInstance()方法有三個引數:
1、用於“載入”動態代理類的類載入器。
2、要實現的介面陣列。
3、將代理上的所有方法呼叫轉發到InvocationHandler的物件。
程式碼如下:
InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[] { MyInterface.class },
handler);
執行上面程式碼後,proxy變數包含了MyInterface介面的動態實現。
對代理的所有呼叫都將由到實現了InvocationHandler介面的handler 物件來處理。
InvocationHandler
如上面說的一樣,必須將InvocationHandler的實現傳遞給Proxy.newProxyInstance()方法。對動態代理的所有方法呼叫都轉發到實現介面的InvocationHandler物件。
InvocationHandler程式碼:
public interface InvocationHandler{
Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
實現InvocationHandler介面的類:
public class MyInvocationHandler implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//do something "dynamic"
}
}
下面詳細介紹傳遞給invoke方法的三個引數。
Object proxy引數,實現介面的動態代理物件。通常不需要這個物件。
Method method引數,表示在動態代理實現的介面上呼叫的方法。通過Method物件,可以獲取到方法名,引數型別,返回型別等資訊。
Object[] args引數,包含呼叫介面中實現的方法時傳遞給代理的引數值。注意:如果介面中的引數是int、long等基本資料時,這裡的args必須使用Integer, Long等包裝型別。
上面程式碼中會生成一個MyInterface介面的物件proxy,通過proxy物件呼叫的方法都會由MyInvocationHandler類的invoke方法處理。
動態代理使用場景:
1、資料庫連線和事務管理。例如Spring框架有一個事務代理,可以啟動和提交/回滾事務
2、用於單元測試的動態模擬物件
3、類似AOP的方法攔截。
本文重點介紹瞭如何通過反射獲取到某個類的方法、成員變數、建構函式等資訊,同時也介紹動態代理的用法,這些都是反射的基礎功能,反射的其他功能裡就不一一介紹了