天天看點

Java反射與内省Java

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 屬性編輯器管理器
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 内省機制。