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() | 返回一個描述構造器、方法或域的修飾符的整型數值,使用java.lang.reflect.Modifier 類中的方法分析這個返回值 |
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 | 屬性編輯器管理器 |
Java Bean
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 | 屬性(否決)變化監聽器管理器 |
Java Bean
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 Demo
@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 Demo
@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 Demo
@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);
}
Result
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 內省機制。