我在實際專案當中有經常用到反射機制,故而將學會的反射用法做一些彙總筆記,當做以後覆盤所用。
存在這樣一個類:
package com.example.demo;
import com.alibaba.fastjson.annotation.JSONField;
public class User {
private String name;
@Value( value ="age_a")
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
一、建立Class的三種方式
1 - Class clazz = Class.forName("com.example.demo.User");
注意一點,這裡的forName("xxx")的類名需要全名,且為介面或類,否則載入不了。
2 - User user = new User();
Class clazz2 = user.getClass();
3 - Class clazz3 = User.class;
以上三種方式,都可以獲取到類User的Class物件,通過Class,即可以開始玩反射了。
二、反射獲取類的所有屬性和屬性型別
Class clazz = User.class;
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println("屬性名:"+field.getName());
System.out.println("屬性的型別:"+field.getGenericType().getTypeName());
}
列印輸出User的屬性和屬性型別——
屬性名:name
屬性的型別:java.lang.String
屬性名:age
屬性的型別:java.lang.String
利用反射獲取到類的欄位屬性後,是不是可以利用反射來建立一個物件呢?答案是肯定的。
例如,可以類似下面程式碼,通過反射得到的欄位屬性,進而建立一個物件。
Map<String,Object> fileds = new HashMap<>();
fileds.put("name","張三");
fileds.put("age","10");
Object o = User.class.newInstance();
Field[] fields = o.getClass().getDeclaredFields();
for (Field field : fields) {
//設定後可用反射訪問訪問私有變數
field.setAccessible(true);
//通過反射給屬性賦值
field.set(o,fileds.get(field.getName()));
}
User user1 = (User) o;
System.out.println(user1.toString());
什麼場景下可能需要這樣做的呢?像一些內部資料與外部資料欄位的對映,就可以通過類似的欄位反射方式,將源資料對映給目標資料,進而得到可以插入資料庫的目標物件。
三、反射動態修改類屬性的註解值
注意一點,我們在設定User類時,對其中一個欄位加了註解:@Value( value ="age_a")。這是一種設定值的註解,既然是設定值,是否還可以在程式碼執行過程中,根據不同情況來動態修改呢?
欄位上的註解,其實都存放在一個memberValues屬性裡,這是一個map,可以這樣來獲取——
Field[] fields = User.class.getDeclaredFields();
for (Field field : fields) {
//設定後可用反射訪問訪問私有變數
if ("age".equals(field.getName() )){
field.setAccessible(true);
//獲取 annotation 這個代理例項所持有的 InvocationHandler
InvocationHandler invocationHandler = Proxy.getInvocationHandler(field.getAnnotation(Value.class));
// 獲取 InvocationHandler 的 memberValues 欄位
Field memberValues = invocationHandler.getClass().getDeclaredField("memberValues");
memberValues.setAccessible(true);
Map<String, Object> values = (Map<String, Object>) memberValues.get(invocationHandler);
System.out.println(values);
}
}
debug打斷點,可以看到——
這個Map<String,Object>儲存的是該@註解裡的所有屬性值,這裡,@Value只有一個value屬性——
public @interface Value {
String value();
}
若把它換成類似@JSONField(name="age_a"),把上邊的程式碼稍微修改下,如:
Field[] fields = User.class.getDeclaredFields();
for (Field field : fields) {
if ("age".equals(field.getName() )){
field.setAccessible(true);
InvocationHandler invocationHandler = Proxy.getInvocationHandler(field.getAnnotation(JSONField.class));
......
}
}
@JSONField註解的內部屬性有如下方式——
再執行剛剛的程式碼,可以看到,這裡Map<String,Object>獲取儲存到的,便是這個註解裡所有的屬性與對應的屬性值。
到了這一步,回到先前上邊的問題,若要動態改變這個註解的值,怎麼處理呢?
其實,很簡單,只需要直接進行值設定就可以了,例如——
InvocationHandler invocationHandler = Proxy.getInvocationHandler(field.getAnnotation(Value.class));
Field memberValues = invocationHandler.getClass().getDeclaredField("memberValues");
memberValues.setAccessible(true);
Map<String, Object> values = (Map<String, Object>) memberValues.get(invocationHandler);
values.put("value","new_age");
memberValues.setAccessible(false);
只是,注意一點是,這裡的key需要對應上註解裡是屬性值。
四、反射獲取類的方法及呼叫方式
Object o=User.class.newInstance();
//通過反射獲取到User的setAge方法,後面的String.class表示這個setAge方法的引數型別,若有多個,則按順序列出
//同時,若為其他型別,如List,Long,則為List.class,Long.class
Method m = (Method) o.getClass().getMethod("setAge",String.class);
m.invoke(o,"name");
User user = (User) o;
System.out.println(user);
列印可見,age已為name,說明setAge呼叫成功了。
這類使用場景,在代理當中出現比較多。
最後,通過反射實現一個Map轉成物件的封裝工具——
public Object MapToObject(Object object,Map<String, Object> map) throws IllegalAccessException {
Class cla = object.getClass();
Field[] fields = cla.getDeclaredFields();
for(Field field:fields){
field.setAccessible(true);
if("serialVersionUID".equals(field.getName()))continue;
if(map.get(field.getName())!=null) {
Object value=map.get(field.getName());
value=convertValType(value,field.getType());
field.set(object, value);
}
}
return object;
}
private static Object convertValType(Object value, Class<?> fieldTypeClass) {
Object o = null;
if (Long.class.getName().equals(fieldTypeClass.getName())
|| long.class.getName().equals(fieldTypeClass.getName())) {
o = Long.parseLong(value.toString());
} else if (Integer.class.getName().equals(fieldTypeClass.getName())
|| int.class.getName().equals(fieldTypeClass.getName())) {
o = Integer.parseInt(value.toString());
} else if (Float.class.getName().equals(fieldTypeClass.getName())
|| float.class.getName().equals(fieldTypeClass.getName())) {
o = Float.parseFloat(value.toString());
} else if (Double.class.getName().equals(fieldTypeClass.getName())
|| double.class.getName().equals(fieldTypeClass.getName())) {
o = Double.parseDouble(value.toString());
} else {
retVal = o;
}
return retVal;
}