《Mybatis 手擼專欄》第8章:把反射用到出神入化

作者:小傅哥
博客://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!😄

一、前言

為什麼,讀不懂框架源碼?

我們都知道作為一個程序員,如果想學習到更深層次的技術,就需要閱讀大量的框架源碼,學習這些框架源碼中的開發套路和設計思想,從而提升自己的編程能力。

事大家都清楚,但在實操上,很多碼農根本沒法閱讀框架源碼。首先一個非常大的問題是,面對如此龐大的框架源碼,不知道從哪下手。與平常的業務需求開發相比,框架源碼中運用了大量的設計原則和設計模式對系統功能進行解耦和實現,也使用了不少如反射、代理、位元組碼等相關技術。

當你還以為是平常的業務需求中的實例化對象調用方法,去找尋源碼中的流程時,可能根本就找不到它是何時發起調用的、怎麼進行傳參、在哪處理賦值的等一連串的問題,都把一個好碼農勸退在開始學習的路上。

二、目標

不知道大家在學習《手寫 Mybatis》的過程中,是否有對照 Mybatis 源碼一起學習,如果你有對照源碼,那麼大概率會發現我們在實現數據源池化時,對於屬性信息的獲取,採用的是硬編碼的方式。如圖 8-1 所示

圖 8-1 數據源池化配置獲取

  • 也就是 props.getProperty("driver")props.getProperty("url") 等屬性,都是通過手動編碼的方式獲取的。
  • 那其實像 driver、url、username、password 不都是標準的固定字段嗎,這樣獲取有什麼不對的。如果按照我們現在的理解來說,並沒有什麼不對,但其實除了這些字段以外,可能還有時候會配置一些擴展字段,那麼怎麼獲取呢,總不能每次都是硬編碼。
  • 所以如果你有閱讀 Mybatis 的源碼,會發現這裡使用了 Mybatis 自己實現的元對象反射工具類,可以完成一個對象的屬性的反射填充。這塊的工具類叫做 MetaObject 並提供相應的;元對象、對象包裝器、對象工廠、對象包裝工廠以及 Reflector 反射器的使用。那麼本章節我們就來實現一下反射工具包的內容,因為隨着我們後續的開發,也會有很多地方都需要使用反射器優雅的處理我們的屬性信息。這也能為你添加一些關於反射的強大的使用!

三、設計

如果說我們需要對一個對象的所提供的屬性進行統一的設置和獲取值的操作,那麼就需要把當前這個被處理的對象進行解耦,提取出它所有的屬性和方法,並按照不同的類型進行反射處理,從而包裝成一個工具包。如圖 8-2 所示

圖 8-2 對象屬性反射處理

  • 其實整個設計過程都以圍繞如何拆解對象並提供反射操作為主,那麼對於一個對象來說,它所包括的有對象的構造函數、對象的屬性、對象的方法。而對象的方法因為都是獲取和設置值的操作,所以基本都是get、set處理,所以需要把這些方法在對象拆解的過程中需要摘取出來進行保存。
  • 當真正的開始操作時,則會依賴於已經實例化的對象,對其進行屬性處理。而這些處理過程實際都是在使用 JDK 所提供的反射進行操作的,而反射過程中的方法名稱、入參類型都已經被我們拆解和處理了,最終使用的時候直接調用即可。

四、實現

1. 工程結構

mybatis-step-07
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.mybatis
    │           ├── binding
    │           ├── builder
    │           ├── datasource
    │           │   ├── druid
    │           │   │   └── DruidDataSourceFactory.java
    │           │   ├── pooled
    │           │   │   ├── PooledConnection.java
    │           │   │   ├── PooledDataSource.java
    │           │   │   ├── PooledDataSourceFactory.java
    │           │   │   └── PoolState.java
    │           │   ├── unpooled
    │           │   │   ├── UnpooledDataSource.java
    │           │   │   └── UnpooledDataSourceFactory.java
    │           │   └── DataSourceFactory.java
    │           ├── executor
    │           ├── io
    │           ├── mapping
    │           ├── reflection
    │           │   ├── factory
    │           │   │   ├── DefaultObjectFactory.java
    │           │   │   └── ObjectFactory.java
    │           │   ├── invoker
    │           │   │   ├── GetFieldInvoker.java
    │           │   │   ├── Invoker.java
    │           │   │   ├── MethodInvoker.java
    │           │   │   └── SetFieldInvoker.java
    │           │   ├── property
    │           │   │   ├── PropertyNamer.java
    │           │   │   └── PropertyTokenizer.java
    │           │   ├── wrapper
    │           │   │   ├── BaseWrapper.java
    │           │   │   ├── BeanWrapper.java
    │           │   │   ├── CollectionWrapper.java
    │           │   │   ├── DefaultObjectWrapperFactory.java
    │           │   │   ├── MapWrapper.java
    │           │   │   ├── ObjectWrapper.java
    │           │   │   └── ObjectWrapperFactory.java
    │           │   ├── MetaClass.java
    │           │   ├── MetaObject.java
    │           │   ├── Reflector.java
    │           │   └── SystemMetaObject.java
    │           ├── session
    │           ├── transaction
    │           └── type
    └── test
        ├── java
        │   └── cn.bugstack.mybatis.test.dao
        │       ├── dao
        │       │   └── IUserDao.java
        │       ├── po
        │       │   └── User.java
        │       ├── ApiTest.java
        │       └── ReflectionTest.java
        └── resources
            ├── mapper
            │   └──User_Mapper.xml
            └── mybatis-config-datasource.xml

工程源碼//github.com/fuzhengwei/small-mybatis

元對象反射工具類,處理對象的屬性設置和獲取操作核心類,如圖 8-3 所示

圖 8-3 所示 元對象反射工具類,處理對象的屬性設置和獲取操作核心類

  • 以 Reflector 反射器類處理對象類中的 get/set 屬性,包裝為可調用的 Invoker 反射類,這樣在對 get/set 方法反射調用的時候,使用方法名稱獲取對應的 Invoker 即可 getGetInvoker(String propertyName)
  • 有了反射器的處理,之後就是對原對象的包裝了,由 SystemMetaObject 提供創建 MetaObject 元對象的方法,將我們需要處理的對象進行拆解和 ObjectWrapper 對象包裝處理。因為一個對象的類型還需要進行一條細節的處理,以及屬性信息的拆解,例如:班級[0].學生.成績 這樣一個類中的關聯類的屬性,則需要進行遞歸的方式拆解處理後,才能設置和獲取屬性值。
  • 最終在 Mybatis 其他的地方就可以,有需要屬性值設定時,就可以使用到反射工具包進行處理了。這裡首當其衝的我們會把數據源池化中關於 Properties 屬性的處理使用反射工具類進行改造。參考本章節中對應的源碼類

2. 反射調用者

關於對象類中的屬性值獲取和設置可以分為 Field 字段的 get/set 還有普通的 Method 的調用,為了減少使用方的過多的處理,這裡可以把集中調用者的實現包裝成調用策略,統一接口不同策略不同的實現類。

定義接口

public interface Invoker {

    Object invoke(Object target, Object[] args) throws Exception;

    Class<?> getType();

}
  • 無論任何類型的反射調用,都離不開對象和入參,只要我們把這兩個字段和返回結果定義的通用,就可以包住不同策略的實現類了。

2.1 MethodInvoker

源碼詳見cn.bugstack.mybatis.reflection.invoker.MethodInvoker

public class MethodInvoker implements Invoker {

    private Class<?> type;
    private Method method;

    @Override
    public Object invoke(Object target, Object[] args) throws Exception {
        return method.invoke(target, args);
    }

}
  • 提供方法反射調用處理,構造函數會傳入對應的方法類型。

2.2 GetFieldInvoker

源碼詳見cn.bugstack.mybatis.reflection.invoker.GetFieldInvoker

public class GetFieldInvoker implements Invoker {

    private Field field;

    @Override
    public Object invoke(Object target, Object[] args) throws Exception {
        return field.get(target);
    }

}
  • getter 方法的調用者處理,因為get是有返回值的,所以直接對 Field 字段操作完後直接返回結果。

2.3 SetFieldInvoker

源碼詳見cn.bugstack.mybatis.reflection.invoker.SetFieldInvoker

public class SetFieldInvoker implements Invoker {

    private Field field;

    @Override
    public Object invoke(Object target, Object[] args) throws Exception {
        field.set(target, args[0]);
        return null;
    }

}
  • setter 方法的調用者處理,因為set只是設置值,所以這裡就只返回一個 null 就可以了。

3. 反射器解耦對象

Reflector 反射器專門用於解耦對象信息的,只有把一個對象信息所含帶的屬性、方法以及關聯的類都以此解析出來,才能滿足後續對屬性值的設置和獲取。

源碼詳見cn.bugstack.mybatis.reflection.Reflector

public class Reflector {

    private static boolean classCacheEnabled = true;

    private static final String[] EMPTY_STRING_ARRAY = new String[0];
    // 線程安全的緩存
    private static final Map<Class<?>, Reflector> REFLECTOR_MAP = new ConcurrentHashMap<>();

    private Class<?> type;
    // get 屬性列表
    private String[] readablePropertyNames = EMPTY_STRING_ARRAY;
    // set 屬性列表
    private String[] writeablePropertyNames = EMPTY_STRING_ARRAY;
    // set 方法列表
    private Map<String, Invoker> setMethods = new HashMap<>();
    // get 方法列表
    private Map<String, Invoker> getMethods = new HashMap<>();
    // set 類型列表
    private Map<String, Class<?>> setTypes = new HashMap<>();
    // get 類型列表
    private Map<String, Class<?>> getTypes = new HashMap<>();
    // 構造函數
    private Constructor<?> defaultConstructor;

    private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();

    public Reflector(Class<?> clazz) {
        this.type = clazz;
        // 加入構造函數
        addDefaultConstructor(clazz);
        // 加入 getter
        addGetMethods(clazz);
        // 加入 setter
        addSetMethods(clazz);
        // 加入字段
        addFields(clazz);
        readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
        writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
        for (String propName : readablePropertyNames) {
            caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
        }
        for (String propName : writeablePropertyNames) {
            caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
        }
    }
    
    // ... 省略處理方法
}
  • Reflector 反射器類中提供了各類屬性、方法、類型以及構造函數的保存操作,當調用反射器時會通過構造函數的處理,逐步從對象類中拆解出這些屬性信息,便於後續反射使用。
  • 讀者在對這部分源碼學習時,可以參考對應的類和這裡的處理方法,這些方法都是一些對反射的操作,獲取出基本的類型、方法信息,並進行整理存放。

4. 元類包裝反射器

Reflector 反射器類提供的是最基礎的核心功能,很多方法也都是私有的,為了更加方便的使用,還需要做一層元類的包裝。在元類 MetaClass 提供必要的創建反射器以及使用反射器獲取 get/set 的 Invoker 反射方法。

源碼詳見cn.bugstack.mybatis.reflection.MetaClass

public class MetaClass {

    private Reflector reflector;

    private MetaClass(Class<?> type) {
        this.reflector = Reflector.forClass(type);
    }

    public static MetaClass forClass(Class<?> type) {
        return new MetaClass(type);
    }

    public String[] getGetterNames() {
        return reflector.getGetablePropertyNames();
    }

    public String[] getSetterNames() {
        return reflector.getSetablePropertyNames();
    }

    public Invoker getGetInvoker(String name) {
        return reflector.getGetInvoker(name);
    }

    public Invoker getSetInvoker(String name) {
        return reflector.getSetInvoker(name);
    }

		// ... 方法包裝
}
  • MetaClass 元類相當於是對我們需要處理對象的包裝,解耦一個原對象,包裝出一個元類。而這些元類、對象包裝器以及對象工廠等,再組合出一個元對象。相當於說這些元類和元對象都是對我們需要操作的原對象解耦後的封裝。有了這樣的操作,就可以讓我們處理每一個屬性或者方法了。

5. 對象包裝器Wrapper

對象包裝器相當於是更加進一步反射調用包裝處理,同時也為不同的對象類型提供不同的包裝策略。框架源碼都喜歡使用設計模式,從來不是一行行ifelse的代碼

在對象包裝器接口中定義了更加明確的需要使用的方法,包括定義出了 get/set 標準的通用方法、獲取get\set屬性名稱和屬性類型,以及添加屬性等操作。

對象包裝器接口

public interface ObjectWrapper {

    // get
    Object get(PropertyTokenizer prop);

    // set
    void set(PropertyTokenizer prop, Object value);

    // 查找屬性
    String findProperty(String name, boolean useCamelCaseMapping);

    // 取得getter的名字列表
    String[] getGetterNames();

    // 取得setter的名字列表
    String[] getSetterNames();

    //取得setter的類型
    Class<?> getSetterType(String name);

    // 取得getter的類型
    Class<?> getGetterType(String name);

    // ... 省略

}
  • 後續所有實現了對象包裝器接口的實現類,都需要提供這些方法實現,基本有了這些方法,也就能非常容易的處理一個對象的反射操作了。
  • 無論你是設置屬性、獲取屬性、拿到對應的字段列表還是類型都是可以滿足的。

6. 元對象封裝

在有了反射器、元類、對象包裝器以後,在使用對象工廠和包裝工廠,就可以組合出一個完整的元對象操作類了。因為所有的不同方式的使用,包括:包裝器策略、包裝工程、統一的方法處理,這些都需要一個統一的處理方,也就是我們的元對象進行管理。

源碼詳見cn.bugstack.mybatis.reflection.MetaObject

public class MetaObject {
    // 原對象
    private Object originalObject;
    // 對象包裝器
    private ObjectWrapper objectWrapper;
    // 對象工廠
    private ObjectFactory objectFactory;
    // 對象包裝工廠
    private ObjectWrapperFactory objectWrapperFactory;

    private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory) {
        this.originalObject = object;
        this.objectFactory = objectFactory;
        this.objectWrapperFactory = objectWrapperFactory;

        if (object instanceof ObjectWrapper) {
            // 如果對象本身已經是ObjectWrapper型,則直接賦給objectWrapper
            this.objectWrapper = (ObjectWrapper) object;
        } else if (objectWrapperFactory.hasWrapperFor(object)) {
            // 如果有包裝器,調用ObjectWrapperFactory.getWrapperFor
            this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
        } else if (object instanceof Map) {
            // 如果是Map型,返回MapWrapper
            this.objectWrapper = new MapWrapper(this, (Map) object);
        } else if (object instanceof Collection) {
            // 如果是Collection型,返回CollectionWrapper
            this.objectWrapper = new CollectionWrapper(this, (Collection) object);
        } else {
            // 除此以外,返回BeanWrapper
            this.objectWrapper = new BeanWrapper(this, object);
        }
    }

    public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory) {
        if (object == null) {
            // 處理一下null,將null包裝起來
            return SystemMetaObject.NULL_META_OBJECT;
        } else {
            return new MetaObject(object, objectFactory, objectWrapperFactory);
        }
    }
    
    // 取得值
    // 如 班級[0].學生.成績
    public Object getValue(String name) {
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
            MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
            if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
                // 如果上層就是null了,那就結束,返回null
                return null;
            } else {
                // 否則繼續看下一層,遞歸調用getValue
                return metaValue.getValue(prop.getChildren());
            }
        } else {
            return objectWrapper.get(prop);
        }
    }

    // 設置值
    // 如 班級[0].學生.成績
    public void setValue(String name, Object value) {
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
            MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
            if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
                if (value == null && prop.getChildren() != null) {
                    // don't instantiate child path if value is null
                    // 如果上層就是 null 了,還得看有沒有兒子,沒有那就結束
                    return;
                } else {
                    // 否則還得 new 一個,委派給 ObjectWrapper.instantiatePropertyValue
                    metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
                }
            }
            // 遞歸調用setValue
            metaValue.setValue(prop.getChildren(), value);
        } else {
            // 到了最後一層了,所以委派給 ObjectWrapper.set
            objectWrapper.set(prop, value);
        }
    }
    
    // ... 省略
}    
  • MetaObject 元對象算是整個服務的包裝,在構造函數中提供各類對象的包裝器類型的創建。之後提供了一些基本的操作封裝,這回封裝後就更貼近實際的使用了。
  • 包括這裡提供的 getValue(String name) 、setValue(String name, Object value) 等,其中當一些對象的中的屬性信息不是一個層次,是 班級[0].學生.成績 還需要被拆解後才能獲取到對應的對象和屬性值。
  • 當所有的這些內容提供完成以後,就可以使用 SystemMetaObject#forObject 提供元對象的獲取了。

7. 數據源屬性設置

好了,現在有了我們實現的屬性反射操作工具包,那麼對於數據源中屬性信息的設置,就可以更加優雅的操作了。

源碼詳見cn.bugstack.mybatis.datasource.unpooled.UnpooledDataSourceFactory

public class UnpooledDataSourceFactory implements DataSourceFactory {

    protected DataSource dataSource;

    public UnpooledDataSourceFactory() {
        this.dataSource = new UnpooledDataSource();
    }

    @Override
    public void setProperties(Properties props) {
        MetaObject metaObject = SystemMetaObject.forObject(dataSource);
        for (Object key : props.keySet()) {
            String propertyName = (String) key;
            if (metaObject.hasSetter(propertyName)) {
                String value = (String) props.get(propertyName);
                Object convertedValue = convertValue(metaObject, propertyName, value);
                metaObject.setValue(propertyName, convertedValue);
            }
        }
    }

    @Override
    public DataSource getDataSource() {
        return dataSource;
    }
    
}
  • 在之前我們對於數據源中屬性信息的獲取都是採用的硬編碼,那麼這回在 setProperties 方法中則可以使用 SystemMetaObject.forObject(dataSource) 獲取 DataSource 的元對象了,也就是通過反射就能把我們需要的屬性值設置進去。
  • 這樣在數據源 UnpooledDataSource、PooledDataSource 中就可以拿到對應的屬性值信息了,而不是我們那種在2個數據源的實現中硬編碼操作。

五、測試

本章節的測試會分為2部分,一部分是我們這個章節實現的反射器工具類的測試,另外一方面是我們把反射器工具類接入到數據源的使用中,驗證使用是否順利。

1. 事先準備

1.1 創建庫表

創建一個數據庫名稱為 mybatis 並在庫中創建表 user 以及添加測試數據,如下:

CREATE TABLE
    USER
    (
        id bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID',
        userId VARCHAR(9) COMMENT '用戶ID',
        userHead VARCHAR(16) COMMENT '用戶頭像',
        createTime TIMESTAMP NULL COMMENT '創建時間',
        updateTime TIMESTAMP NULL COMMENT '更新時間',
        userName VARCHAR(64),
        PRIMARY KEY (id)
    )
    ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
insert into user (id, userId, userHead, createTime, updateTime, userName) values (1, '10001', '1_04', '2022-04-13 00:00:00', '2022-04-13 00:00:00', '小傅哥');    

1.2 配置數據源

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </dataSource>
    </environment>
</environments>
  • 通過 mybatis-config-datasource.xml 配置數據源信息,包括:driver、url、username、password
  • 在這裡 dataSource 測試驗證 UNPOOLED 和 POOLED,因為這2個都屬於被反射工具類處理

1.3 配置Mapper

<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="cn.bugstack.mybatis.test.po.User">
    SELECT id, userId, userName, userHead
    FROM user
    where id = #{id}
</select>
  • 這部分暫時不需要調整,目前還只是一個入參的類型的參數,後續我們全部完善這部分內容以後,則再提供更多的其他參數進行驗證。

2. 單元測試

2.1 反射類測試

@Test
public void test_reflection() {
    Teacher teacher = new Teacher();
    List<Teacher.Student> list = new ArrayList<>();
    list.add(new Teacher.Student());
    teacher.setName("小傅哥");
    teacher.setStudents(list);

    MetaObject metaObject = SystemMetaObject.forObject(teacher);

    logger.info("getGetterNames:{}", JSON.toJSONString(metaObject.getGetterNames()));
    logger.info("getSetterNames:{}", JSON.toJSONString(metaObject.getSetterNames()));
    logger.info("name的get方法返回值:{}", JSON.toJSONString(metaObject.getGetterType("name")));
    logger.info("students的set方法參數值:{}", JSON.toJSONString(metaObject.getGetterType("students")));
    logger.info("name的hasGetter:{}", metaObject.hasGetter("name"));
    logger.info("student.id(屬性為對象)的hasGetter:{}", metaObject.hasGetter("student.id"));
    logger.info("獲取name的屬性值:{}", metaObject.getValue("name"));
    // 重新設置屬性值
    metaObject.setValue("name", "小白");
    logger.info("設置name的屬性值:{}", metaObject.getValue("name"));
    // 設置屬性(集合)的元素值
    metaObject.setValue("students[0].id", "001");
    logger.info("獲取students集合的第一個元素的屬性值:{}", JSON.toJSONString(metaObject.getValue("students[0].id")));
    logger.info("對象的序列化:{}", JSON.toJSONString(teacher));
}
  • 這是一組比較常見的用於測試 Mybatis 源碼中 MetaObject 的測試類,我們把這個單元測試用到我們自己實現的反射工具類上,看看是否可以正常運行。

測試結果

07:44:23.601 [main] INFO  c.b.mybatis.test.ReflectionTest - getGetterNames:["student","price","name","students"]
07:44:23.608 [main] INFO  c.b.mybatis.test.ReflectionTest - getSetterNames:["student","price","name","students"]
07:44:23.609 [main] INFO  c.b.mybatis.test.ReflectionTest - name的get方法返回值:"java.lang.String"
07:44:23.609 [main] INFO  c.b.mybatis.test.ReflectionTest - students的set方法參數值:"java.util.List"
07:44:23.609 [main] INFO  c.b.mybatis.test.ReflectionTest - name的hasGetter:true
07:44:23.609 [main] INFO  c.b.mybatis.test.ReflectionTest - student.id(屬性為對象)的hasGetter:true
07:44:23.610 [main] INFO  c.b.mybatis.test.ReflectionTest - 獲取name的屬性值:小傅哥
07:44:23.610 [main] INFO  c.b.mybatis.test.ReflectionTest - 設置name的屬性值:小白
07:44:23.610 [main] INFO  c.b.mybatis.test.ReflectionTest - 獲取students集合的第一個元素的屬性值:"001"
07:44:23.665 [main] INFO  c.b.mybatis.test.ReflectionTest - 對象的序列化:{"name":"小白","price":0.0,"students":[{"id":"001"}]}

Process finished with exit code 0
  • 好了,那麼這個測試中可以看到,我們拿到了對應的屬性信息,並可以設置以及修改屬性值,無論是單個屬性還是對象屬性,都可以操作。

2.2 數據源測試

@Test
public void test_SqlSessionFactory() throws IOException {
    // 1. 從SqlSessionFactory中獲取SqlSession
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 2. 獲取映射器對象
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);

    // 3. 測試驗證
    User user = userDao.queryUserInfoById(1L);
    logger.info("測試結果:{}", JSON.toJSONString(user));
}
  • 這塊的調用我們手寫框架的測試類到不需要什麼改變,只要數據源配置上使用 type="POOLED/UNPOOLED" 即可,這樣就能測試我們自己開發的使用了反射器設置屬性的數據源類了。

測試結果

圖 8-4 使用MetaObject 設置屬性值

07:51:54.898 [main] INFO  c.b.m.d.pooled.PooledDataSource - Created connection 212683148.
07:51:55.006 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 測試結果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
  • 根據單元測試和調試的截圖,可以看到屬性值通過反射的方式設置到對象中,也滿足了我們在創建數據源時候的使用。這樣就可以順利的調用數據源完成數據的查詢操作了。

七、總結

  • 本章節關於反射工具類的實現中,使用了大量的 JDK 所提供的關於反射一些處理操作,也包括可以獲取一個 Class 類中的屬性、字段、方法的信息。那麼再有了這些信息以後就可以按照功能流程進行解耦,把屬性、反射、包裝,都依次拆分出來,並按照設計原則,逐步包裝讓外接更少的知道內部的處理。
  • 這裡的反射也算是小天花板的使用級別了,封裝的工具類方式,如果在我們也有類似的場景中,就可以直接拿來使用。因為整個工具類並沒有太多的額外關聯,直接拿來封裝成一個工具包進行使用,處理平常的業務邏輯中組件化的部分,也是非常不錯的。技術遷移、學以致用、升職加薪
  • 由於整個工具包中涉及的類還是比較多的,大家在學習的過程中儘可能的驗證和調試,以及對某個不清楚的方法進行單獨開發和測試,這樣才能濾清整個結構是如何實現的。當你把這塊的內容全部拿下,以後再遇到反射就是小意思了