Java
反射
Class
Class類的執行個體表示在運作中的Java應用程式的類和接口。
enum
是一個類,
annotation
是一個接口。每一個數組都是一個類,這個類由相同元素的數組和維數所共享。對于基礎資料類型
boolean
、
byte
char
short
int
long
float
double
和關鍵字
void
都代表一個類。
類沒有公共的構造函數,那麼Java虛拟機加載類的時候會調用defineClass方法來構造。
Bean.getClass.newInstance()
方法預設調用無參構造函數初始化對象。如果沒有就抛出一個異常。
java.lang.reflect.Constructor.newInstance(Object... param)
可以通過帶參構造函數初始化對象。
java.lang.reflect
包下的三個類
Field
Method
Constructor
分别描述類的域、方法和構造器。
Field
的
getType
方法用于描述域所屬類型的Class對象。
Class
類中的
getFields
getMethods
getConstructors
方法将傳回
public
的域、方法和構造器數組,其中包括超類的
public
成員。
Class
類的
getDeclaredFields
getDeclaredMethods
getDeclaredConstructors
等
Declared
方法将傳回類中所有的域、方法和構造器數組。包括
private
和
protected
成員,但是不包括超類的成員。
setAccessible()
方法是
AccessibleObject
類中的一個方法,它是
Field
Method
Constructor
的公共超類。
構造函數建立對象
Class<?> aClass = Class.forName("com.lang.pojo.User");
// 擷取所有public的構造函數
Constructor<?>[] constructors = aClass.getConstructors();
// 擷取所有的構造函數,包括private的
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
// 設定暴力通路,如果不設定那麼private方法就不可以通路
declaredConstructor.setAccessible(true);
int parameterCount = declaredConstructor.getParameterCount();
if (parameterCount == 0) {
// 沒有參數,調用無參構造函數
Object o = declaredConstructor.newInstance();
System.out.println(o);
} else {
// 可以建構對象,參數為可變參數
Object o = declaredConstructor.newInstance("jack", 10, "美國加州");
System.out.println(o);
}
}
擷取傳回值類型
Class<?> aClass = Class.forName("com.lang.pojo.User");
// 擷取所有public的構造函數
Constructor<?>[] constructors = aClass.getConstructors();
// 擷取所有的構造函數,包括private的
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
// 設定暴力通路,如果不設定那麼private方法就不可以通路
declaredConstructor.setAccessible(true);
// 擷取方法的傳回值類型
AnnotatedType type = declaredConstructor.getAnnotatedReturnType();
Type type1 = type.getType();
// 擷取傳回值的名稱:com.lang.pojo.User
String typeName = type1.getTypeName();
System.out.println(typeName);
}
擷取參數類型
Class<?> aClass = Class.forName("com.lang.pojo.User");
// 擷取所有public的構造函數
Constructor<?>[] constructors = aClass.getConstructors();
// 擷取所有的構造函數,包括private的
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
// 設定暴力通路,如果不設定那麼private方法就不可以通路
declaredConstructor.setAccessible(true);
Type[] parameterTypes = declaredConstructor.getGenericParameterTypes();
for (Type type : parameterTypes) {
System.out.println(type.getTypeName());
}
}
核心方法彙集
java.lang.Class
類對象
方法 | 作用 |
static Class<?> forName(String className) | 傳回描述類名為className的Class對象 |
T newInstance() | 傳回這個類的一個新的執行個體 |
Field[] getFields() | 傳回public的域,包括超類 |
Field getField(String name) | 傳回指定名稱的public的域 |
Field[] getDeclaredFields() | 傳回所有的域,不包括超類 |
Field getDeclaredField(String name) | 傳回指定名稱的域,私有也可以傳回 |
Method[] getMethods() | 傳回public的方法,包括超類 |
Method[] getDeclaredMethods() | 傳回所有的方法,不包括超類 |
Constructor<?>[] getConstructors() | 傳回public的構造器,不包括超類 |
Constructor<?>[] getDeclaredConstructors() | 傳回所有的構造器,不包括超類 |
java.lang.reflect.Constructor
構造函數
T newInstance(Object… initargs) | 構造一個這個構造器所屬類的新執行個體 |
Class getDeclaringClass() | 傳回一個用于描述類中定義的構造器、方法或域的Class對象 |
Class<?>[] getExceptionTypes() | 傳回一個描述方法抛出的異常類型的Class對象數組 |
int getModifiers() | 傳回一個描述構造器、方法或域的修飾符的整型數值,使用 類中的方法分析這個傳回值 |
String getName() | 傳回一個描述構造器、方法或域的名稱字元串 |
Class<?>[] getParameterTypes() | 傳回一個描述參數類型的Class對象數組 |
java.lang.reflect.Field
屬性
Object get(Object obj) | 傳回obj對象中用Field對象表示的域值 |
void set(Object obj, Object value) | 用一個新值value設定obj對象中Field對象表示的域 |
java.lang.reflect.Method
Object invoke(Object obj, Object… args) | 執行這個對象的方法 |
注意
java.lang.reflect
的類中很多方法都是通用的,這裡列舉出來的隻是工作使用比較頻繁的。如果對于Java Bean的操作可以使用内省技術更加便捷。
提示
在啟動時,包含main方法的類被加載。那麼它就會加載所有需要的類。這些被加載的類又會繼續加載它們需要的類,以此類推。對于一個大型的應用程式來說,這樣程式啟動就需要消耗很多的時間。不過可以確定main方法包含的類沒有顯式的引用其他類,等啟動後調用
Class.forName
手動加載其他類。
内省
Java官方對Java Beans内省的定義:
At runtime and in the builder environment we need to be able to figure out which properties, events, and methods a Java Bean supports. We call this process introspection.
從 Java Bean 的角度來看,這裡的對象就是 Bean 對象,主要關注點是屬性、方法和事件等,也就是說在運作時可以擷取相應的資訊進行一些處理,這就是 Java Beans 的内省機制。
與反射的差別
By default we will use a low level reflection mechanism to study the methods supported by a target bean and then apply simple design patterns to deduce from those methods what properties, events, and public methods are supported.
Java Beans 内省其實就是對反射的一種封裝 。
Java Beans内省機制
核心類庫
Java Beans 内省機制的核心類是
Introspector
:
The Introspector class provides a standard way for tools to learn about the properties, events, and methods supported by a target Java Bean.
這個内省工具類提供了标準的工具方法對于了解Java Bean的屬性、方法和事件提供了支援。
核心對象
對象 | 描述 |
BeanInfo | Java Bean 資訊類 |
PropertyDescriptor | 屬性描述類 |
MethodDescriptor | 方法描述類 |
EventSetDescriptor | 事件描述集合 |
快速入門
Java Bean
public class User {
private String username;
private Integer age;
// getter/setter
// toString
}
Test Demo
@Test
public void test1() throws IntrospectionException {
//擷取 User Bean 資訊
BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class);
//屬性描述
PropertyDescriptor[] propertyDescriptors = userBeanInfo.getPropertyDescriptors();
System.out.println("屬性描述:");
Stream.of(propertyDescriptors).forEach(System.out::println);
//方法描述
System.out.println("方法描述:");
MethodDescriptor[] methodDescriptors = userBeanInfo.getMethodDescriptors();
Stream.of(methodDescriptors).forEach(System.out::println);
//事件描述
System.out.println("事件描述:");
EventSetDescriptor[] eventSetDescriptors = userBeanInfo.getEventSetDescriptors();
Stream.of(eventSetDescriptors).forEach(System.out::println);
}
Result Info
屬性描述:
java.beans.PropertyDescriptor[name=age; propertyType=class java.lang.Integer; readMethod=public java.lang.Integer introspector.bean.User.getAge(); writeMethod=public void introspector.bean.User.setAge(java.lang.Integer)]
java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()]
java.beans.PropertyDescriptor[name=username; propertyType=class java.lang.String; readMethod=public java.lang.String introspector.bean.User.getUsername(); writeMethod=public void introspector.bean.User.setUsername(java.lang.String)]
方法描述:
java.beans.MethodDescriptor[name=getClass; method=public final native java.lang.Class java.lang.Object.getClass()]
java.beans.MethodDescriptor[name=setAge; method=public void introspector.bean.User.setAge(java.lang.Integer)]
java.beans.MethodDescriptor[name=getAge; method=public java.lang.Integer introspector.bean.User.getAge()]
java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=notifyAll; method=public final native void java.lang.Object.notifyAll()]
java.beans.MethodDescriptor[name=notify; method=public final native void java.lang.Object.notify()]
java.beans.MethodDescriptor[name=getUsername; method=public java.lang.String introspector.bean.User.getUsername()]
java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait() throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=hashCode; method=public native int java.lang.Object.hashCode()]
java.beans.MethodDescriptor[name=setUsername; method=public void introspector.bean.User.setUsername(java.lang.String)]
java.beans.MethodDescriptor[name=wait; method=public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=equals; method=public boolean java.lang.Object.equals(java.lang.Object)]
java.beans.MethodDescriptor[name=toString; method=public java.lang.String introspector.bean.User.toString()]
事件描述:
可以看出通過内省機制可以擷取 Java Bean 的屬性、方法描述,這裡事件描述是空的(關于事件相關會在後面介紹)。由于 Java 類都會繼承
Object
類,可以看到這裡将
Object
類相關的屬性和方法描述也輸出了,如果想将某個類的描述資訊排除可以使用
java.beans.Introspector#getBeanInfo(java.lang.Class, java.lang.Class)
這個方法。
類型轉換
PropertyEditor | 屬性編輯器頂層接口 |
PropertyEditorSupport | 屬性編輯器實作類 |
PropertyEditorManager | 屬性編輯器管理器 |
public class User {
private String username;
private Integer age;
private Date createTime;
// getter/setter
// toString
}
日期類型轉換器
/**
* 日期屬性編輯器
*/
public class DatPropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) {
try {
setValue((text == null) ? null : new SimpleDateFormat("yyyy-MM-dd").parse(text));
} catch (ParseException e) {
e.printStackTrace();
}
}
}
在之前的例子中内省設定屬性值都是直接通過
PropertyDescriptor
擷取屬性的寫方法通過反射去指派,而如果需要對值進行類型轉換,則需要通過
PropertyEditorSupport#setAsText
調用
setValue
方法,然後
setValue
方法觸發屬性屬性修改事件:
public class PropertyEditorSupport implements PropertyEditor {
public void setValue(Object value) {
this.value = value;
firePropertyChange();
}
}
要注意這裡的
value
實際上是臨時存儲在
PropertyEditorSupport
中,
PropertyEditorSupport
則作為事件源,進而得到類型轉換後的
value
,再通過
PropertyDescriptor
擷取屬性的寫方法通過反射去指派。
@Test
public void test6() throws IntrospectionException, FileNotFoundException {
Map<String,Object> properties = ImmutableMap.of("age",1,"username","zhangsan","createTime","2020-01-01");
User user = new User();
//擷取 User Bean 資訊,排除 Object
BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class, Object.class);
//屬性描述
PropertyDescriptor[] propertyDescriptors = userBeanInfo.getPropertyDescriptors();
Stream.of(propertyDescriptors).forEach(propertyDescriptor -> {
//擷取屬性名稱
String property = propertyDescriptor.getName();
//值
Object value = properties.get(property);
if (Objects.equals("createTime", property)) {
//設定屬性編輯器
propertyDescriptor.setPropertyEditorClass(DatPropertyEditor.class);
//建立屬性編輯器
PropertyEditor propertyEditor = propertyDescriptor.createPropertyEditor(user);
//添加監聽器
propertyEditor.addPropertyChangeListener(evt -> {
//擷取轉換後的value
Object value1 = propertyEditor.getValue();
setPropertyValue(user, propertyDescriptor, value1);
});
propertyEditor.setAsText(String.valueOf(value));
return;
}
setPropertyValue(user, propertyDescriptor, value);
});
System.out.println(user);
}
/**
* 設定屬性值
*/
private void setPropertyValue(User user, PropertyDescriptor propertyDescriptor, Object value1) {
try {
propertyDescriptor.getWriteMethod().invoke(user, value1);
} catch (IllegalAccessException | InvocationTargetException ignored) {
}
}
事件監聽
PropertyChangeEvent | 屬性變化事件 |
PropertyChangeListener | 屬性(生效)變化監聽器 |
PropertyChangeSupport | 屬性(生效)變化監聽器管理器 |
VetoableChangeListener | 屬性(否決)變化監聽器 |
VetoableChangeSupport | 屬性(否決)變化監聽器管理器 |
public class User {
private String username;
private Integer age;
/**
* 屬性(生效)變化監聽器管理器
*/
private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
/**
* 啟動屬性(生效)變化
* @param propertyName
* @param oldValue
* @param newValue
*/
private void firePropertyChange(String propertyName, String oldValue, String newValue) {
PropertyChangeEvent event = new PropertyChangeEvent(this, propertyName, oldValue, newValue);
propertyChangeSupport.firePropertyChange(event);
}
/**
* 添加屬性(生效)變化監聽器
*/
public void addPropertyChangeListener(PropertyChangeListener listener){
propertyChangeSupport.addPropertyChangeListener(listener);
}
/**
* 删除屬性(生效)變化監聽器
*/
public void removePropertyChangeListener(PropertyChangeListener listener){
propertyChangeSupport.removePropertyChangeListener(listener);
}
/**
* 擷取屬性(生效)變化監聽器
*/
public PropertyChangeListener[] getPropertyChangeListeners() {
return propertyChangeSupport.getPropertyChangeListeners();
}
public void setUsername(String username) {
String oldValue = this.username;
this.username = username;
firePropertyChange("username", oldValue, username);
}
// getter/setter
// toString
}
@Test
public void test3(){
User user = new User();
user.setAge(1);
user.setUsername("zhangsan");
user.addPropertyChangeListener(System.out::println);
user.setUsername("lisi");
user.setUsername("wangwu");
}
Result
java.beans.PropertyChangeEvent[propertyName=name; oldValue=zhangsan; newValue=lisi; propagationId=null; source=User{username='lisi', age=1}]
java.beans.PropertyChangeEvent[propertyName=name; oldValue=lisi; newValue=wangwu; propagationId=null; source=User{username='wangwu', age=1}]
可以看到在添加了監聽器後,當 username 屬性發生變化的時候會出發監聽事件。
再看看另外一種監聽器
VetoableChangeListener
。在
User
中添加監聽器:
Java Bean(User)
/**
* 屬性(否決)變化監聽器
*/
private VetoableChangeSupport vetoableChangeSupport = new VetoableChangeSupport(this);
/**
* 啟動屬性(否決)變化
* @param propertyName
* @param oldValue
* @param newValue
*/
private void fireVetoableChange(String propertyName, String oldValue, String newValue) throws PropertyVetoException {
PropertyChangeEvent event = new PropertyChangeEvent(this, propertyName, oldValue, newValue);
vetoableChangeSupport.fireVetoableChange(event);
}
/**
* 添加屬性(否決)變化監聽器
*/
public void addVetoableChangeListener(VetoableChangeListener listener){
vetoableChangeSupport.addVetoableChangeListener(listener);
}
/**
* 删除屬性(否決)變化監聽器
*/
public void removeVetoableChangeListener(VetoableChangeListener listener){
vetoableChangeSupport.removeVetoableChangeListener(listener);
}
public void setUsername(String username) throws PropertyVetoException {
String oldValue = this.username;
fireVetoableChange("username",oldValue,username);
this.username = username;
firePropertyChange("username", oldValue, username);
}
@Test
public void test3() throws PropertyVetoException {
User user = new User();
user.setAge(1);
user.addVetoableChangeListener(evt -> {
System.out.println(evt.getNewValue()+",,"+evt.getOldValue());
if (Objects.equals(evt.getNewValue(), evt.getOldValue())) {
throw new PropertyVetoException("目前屬性值未發生任何變化", evt);
}
});
user.addPropertyChangeListener(System.out::println);
user.setUsername("lisi");
user.setUsername("zhangsan");
user.setUsername("zhangsan");
}
運作時發現一直無法抛出異常。檢視源碼發現
PropertyChangeSupport
VetoableChangeSupport
當新舊值相等時不會觸發監聽,于是修改測試代碼:
@Test
public void test3() throws PropertyVetoException {
User user = new User();
user.setAge(1);
user.addVetoableChangeListener(evt -> {
System.out.println(evt.getNewValue()+",,"+evt.getOldValue());
if (Objects.isNull(evt.getNewValue())) {
throw new PropertyVetoException("username 不能為null", evt);
}
});
user.addPropertyChangeListener(System.out::println);
user.setUsername("lisi");
user.setUsername(null);
}
lisi,,null
java.beans.PropertyChangeEvent[propertyName=username; oldValue=null; newValue=lisi; propagationId=null; source=User{username='lisi', age=1}]
null,,lisi
java.beans.PropertyVetoException: username 不能為null
at introspector.test.IntrospectorTest.lambda$test3$1(IntrospectorTest.java:78)
at java.beans.VetoableChangeSupport.fireVetoableChange(VetoableChangeSupport.java:375)
可以發現當符合“否決”屬性變化的條件時,會抛出
PropertyVetoException
異常阻斷屬性的變化。
在之前的示例中
userBeanInfo
輸出的
EventSetDescriptor
為空,這是因為并未到
User
類中增加事件。現在再測試一下擷取
EventSetDescriptor
@Test
public void test1() throws IntrospectionException {
BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class, Object.class);
EventSetDescriptor[] eventSetDescriptors = userBeanInfo.getEventSetDescriptors();
Stream.of(eventSetDescriptors).forEach(System.out::println);
}
java.beans.EventSetDescriptor[name=propertyChange; inDefaultEventSet; listenerType=interface java.beans.PropertyChangeListener; getListenerMethod=public java.beans.PropertyChangeListener[] introspector.bean.User.getPropertyChangeListeners(); addListenerMethod=public void introspector.bean.User.addPropertyChangeListener(java.beans.PropertyChangeListener); removeListenerMethod=public void introspector.bean.User.removePropertyChangeListener(java.beans.PropertyChangeListener)]
java.beans.EventSetDescriptor[name=vetoableChange; inDefaultEventSet; listenerType=interface java.beans.VetoableChangeListener; addListenerMethod=public void introspector.bean.User.addVetoableChangeListener(java.beans.VetoableChangeListener); removeListenerMethod=public void introspector.bean.User.removeVetoableChangeListener(java.beans.VetoableChangeListener)]
在 Java 生态飛速發展的今天,很多底層技術細節都被進階架構所屏蔽,而 Java Beans 就是其中一種。也許平時根本就用不到,但是其代碼設計和思想理念不應該被忽視。Dubbo 2.7 之後提出了“服務自省”的概念,其靈感就來源于 Java Beans 内省機制。