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

小傅哥發表於2022-05-16

作者:小傅哥
部落格:https://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

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

相關文章