封裝BeanMap以及java的反射和內省

BeanMap

學習具體的技術工具的好辦法就是些Demo、造輪子。因此,我實現了一個稱爲BeanMap的類來應用java反射API。java

這個BeanMap的功能是將一個Bean包裝成Map來使用。對調用者來講,是以操做Map的方式來操做BeanMap,
可是,實際上的數據是存儲在被包裝的Bean對象中的。git

這種思路相似適配器模式,可讓你以Map的接口操做Bean對象。
但又有點像「視圖」思想,真正的數據是存儲在Bean對象中的,BeanMap只是對它進行操做的「視圖」。對BeanMap的全部操做都會反映在後面的Bean對象中。github

下面是BeanMap的一個使用例子。緩存

@Getter
    @Setter
    class Point {
        private Integer x = 2;
        private Integer y = 1;
    }
BeanMap map = new BeanMap(new Point());
map.put("x", 10);
map.get("y");

示例中的Point這個Bean類擁有屬性x和y。可是被BeanMap包裝後,它就變成了一個擁有鍵"x"和鍵"y"的Map了。oracle

那這個BeanMap類有什麼實際應用呢?哈哈這只是我爲了寫反射的DEMO本身設計出來的一個類。 app

java反射

稍微思考下不難發現,BeanMap實現的關鍵點在於,
BeanMap接受外部傳入的鍵,這是一個字符串。以後,它獲得找到Bean對象中對應的getter和setter,並操做Bean對象。函數

將這個要求向通用化的方向分析,也即提供一個與函數匹配的字符串,獲得對該函數的引用。工具

經過反射,就可以實現上面的要求。
如今流行的語言大都支持反射。反射可以在運行時獲得程序元數據,好比某類的信息。
還可以根據這些元信息來修改程序狀態或邏輯。
因爲反射是在 運行 時獲得的信息,那麼支持反射的語言也必然要在程序運行時將這些元信息存放在內存某處。性能

java語言提供了反射API,這裏是官方完整的文檔:https://docs.oracle.com/javas...
對於反射出來的信息,Java的反射API將其以類的形式包裝提供。
Java的反射機制提供了4個類:學習

  • Class 類
  • Constructor 構造器
  • Field 屬性
  • Method 方法

如今,試圖利用反射API,獲得一個POJO類的全部屬性名稱。以下:

private List<String> names() {
        Method[] methods = bean.getClass().getMethods();
        List<String> result = new ArrayList<>();
        for (Method getter : methods) {
            int mod = getter.getModifiers();
            if (getter.getName().startsWith("get") && !Modifier.isPublic(mod)) {
                String name = getter.getName().substring("key".length());
                name = name.substring(0, 1).toLowerCase() + key.substring(1);
                result.add(name);
            }
        }
        return result;
    }
  1. 上述代碼的思路很簡單,java提供的反射API,可以獲得該類的全部方法列表。按照約定,POJO類的屬性xxx的getter方法都命名爲GetXxx。那麼,遍歷這個表,找出全部getter,字符串處理下,就獲得了全部的屬性。
  2. 經過調用對象的getClass()方法獲得一個Class對象,也即反射出該對象的Class信息。Class::getMethods函數則是反射出該類的全部方法。
  3. Method::getModifiers獲得方法的修飾符信息。它是一個整數,我猜想是用位標記各個修飾符的,處理它須要用到位運算。不過可使用Modifier這個類的工具方法去處理它。

不過,這裏是想實現BeanMap。一種思路是,經過對反射出的列表進行一次處理,除了獲得每一個屬性的名稱外,還要獲得它們的getter和setter。下面是一種粗暴的實現:

private List<Item> fileds() {
        Method[] fields = bean.getClass().getMethods();
        List<Item> result = new ArrayList<>();
        for (Method getter : fields) {
            if (getter.getName().startsWith("get") && isVaildModifier(getter.getModifiers())) {
                String setterName = "set" + getter.getName().substring("get".length());
                for (Method setter : fields) {
                    if (setter.getName().equals(setterName) && isVaildModifier(setter.getModifiers())) {
                        result.add(new Item(this.bean, getter, setter));
                    }
                }
            }
        }
        return result;
    }

固然,上面的實現思路缺點不少。
首先,是性能問題。上面的代碼雖然能實現功能,可是太暴力了。
雖然一個POJO類的方法頂多幾十個,可是考慮到在具體的實踐中,這些都是做爲較爲底層的基礎設施,項目中可能會頻繁被業務代碼調用,所以對其進行性能上的分析是有必要的。

其次,上面這段邏輯考慮的也不全面。上面的邏輯是對公開的getter進行進一步的處理的,那麼,若是getter是static的呢?
若是getter被native修飾呢?若是是超類所擁有的屬性,那麼該如何處理這種狀況呢?

java內省API

爲了方便使用,java針對反射API進行了封裝,提供了一組內省API。
這組內省API主要是針對POJO類進行操做的,可以獲取POJO類的屬性信息。

那麼,有了jdk自帶的用於對Bean進行反射的工具後,上面的邏輯既能夠簡化了:

private List<Item> fileds() throws IntrospectionException {
        BeanInfo beanInfo = Introspector.getBeanInfo(List.class);
        return Stream.of(beanInfo.getPropertyDescriptors()).map((pd) -> {
            return new Item(this.bean, pd.getReadMethod(), pd.getWriteMethod());
        }).collect(Collectors.toList());
    }
  1. 使用getBeanInfo方法獲取某個Bean類的內省信息,這些信息封裝在BeanInfo對象中。
  2. PropertyDescriptor是對Bean類中的一個屬性的封裝,經過它能夠獲取該屬性的名稱、getter方法、setter方法等信息。
  3. beanInfo::getPropertyDescriptors獲取Bean類的全部的PropertyDescriptor

能夠看到,經過java的內省機制,解決了BeanMap的最關鍵的問題。並且,使用java自帶的內省機制比本身經過反射API處理有如下好處:

  1. 內省API基於反射API進行的封裝,使用更高層次的接口,固然更省心,開發效率更高。
  2. jdk在封裝反射API的時候,會充分考慮到各類狀況。如考慮到繼承這一問題,Introspector::getBeanInfo函數可接收第二個參數,只反射出繼承鏈中該類到第二個參數指定類之間類的屬性。
  3. 內省API也充分考慮到了性能,其中擁有緩存機制,以提高性能。

最後

解決了最關鍵的邏輯後,剩下的部分,就是對Map接口進行實現,填充一些封裝目的的代碼。在這裏,我將核心的邏輯放在BeanMapImpl類中,而BeanMap僅僅負責實現Map接口,相關操做轉發到BeanMapImpl相應的方法中實現。這樣顯得程序結構更爲清晰:
https://github.com/frapples/j...

相關文章
相關標籤/搜索