Java反射與內省(參考小米內部資料)

Java

反射

Class

​ Class類的實例表示在運行中的Java應用程序的類和接口。enum是一個類,annotation是一個接口。每一個數組都是一個類,這個類由相同元素的數組和維數所共享。對於基礎數據類型booleanbytecharshortintlongfloatdouble和關鍵字void都代表一個類。

​ 類沒有公共的構造函數,那麼Java虛擬機加載類的時候會調用defineClass方法來構造。

Bean.getClass.newInstance()方法默認調用無參構造函數初始化對象。如果沒有就拋出一個異常。

java.lang.reflect.Constructor.newInstance(Object... param)可以通過帶參構造函數初始化對象。

java.lang.reflect包下的三個類FieldMethodConstructor分別描述類的域、方法和構造器。

FieldgetType方法用於描述域所屬類型的Class對象。

Class類中的getFieldsgetMethodsgetConstructors方法將返回public的域、方法和構造器數組,其中包括超類的public成員。

Class類的getDeclaredFieldsgetDeclaredMethodsgetDeclaredConstructorsDeclared方法將返回類中所有的域、方法和構造器數組。包括privateprotected成員,但是不包括超類的成員。

setAccessible()方法是AccessibleObject類中的一個方法,它是FieldMethodConstructor的公共超類。

構造函數創建對象

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");
}

運行時發現一直無法拋出異常。查看源碼發現 PropertyChangeSupportVetoableChangeSupport 當新舊值相等時不會觸發監聽,於是修改測試代碼:

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 內省機制。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章