作者:小傅哥
博客:https://bugstack.cn - 系列内容

积淀、分享、成长,让本人和别人都能有所播种!

一、前言

为什么,读不懂框架源码?

咱们都晓得作为一个程序员,如果想学习到更深层次的技术,就须要浏览大量的框架源码,学习这些框架源码中的开发套路和设计思维,从而晋升本人的编程能力。

事大家都分明,但在实操上,很多码农基本没法浏览框架源码。首先一个十分大的问题是,面对如此宏大的框架源码,不晓得从哪下手。与平时的业务需要开发相比,框架源码中使用了大量的设计准则和设计模式对系统性能进行解耦和实现,也应用了不少如反射、代理、字节码等相干技术。

当你还认为是平时的业务需要中的实例化对象调用办法,去找寻源码中的流程时,可能基本就找不到它是何时发动调用的、怎么进行传参、在哪解决赋值的等一连串的问题,都把一个好码农劝退在开始学习的路上。

二、指标

不晓得大家在学习《手写 Mybatis》的过程中,是否有对照 Mybatis 源码一起学习,如果你有对照源码,那么大概率会发现咱们在实现数据源池化时,对于属性信息的获取,采纳的是硬编码的形式。如图 8-1 所示

  • 也就是 props.getProperty("driver")props.getProperty("url") 等属性,都是通过手动编码的形式获取的。
  • 那其实像 driver、url、username、password 不都是规范的固定字段吗,这样获取有什么不对的。如果依照咱们当初的了解来说,并没有什么不对,但其实除了这些字段以外,可能还有时候会配置一些扩大字段,那么怎么获取呢,总不能每次都是硬编码。
  • 所以如果你有浏览 Mybatis 的源码,会发现这里应用了 Mybatis 本人实现的元对象反射工具类,能够实现一个对象的属性的反射填充。这块的工具类叫做 MetaObject 并提供相应的;元对象、对象包装器、对象工厂、对象包装工厂以及 Reflector 反射器的应用。那么本章节咱们就来实现一下反射工具包的内容,因为随着咱们后续的开发,也会有很多中央都须要应用反射器优雅的解决咱们的属性信息。这也能为你增加一些对于反射的弱小的应用!

三、设计

如果说咱们须要对一个对象的所提供的属性进行对立的设置和获取值的操作,那么就须要把以后这个被解决的对象进行解耦,提取出它所有的属性和办法,并依照不同的类型进行反射解决,从而包装成一个工具包。如图 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 所示

  • 以 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 反射类测试

@Testpublic 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:true07:44:23.609 [main] INFO  c.b.mybatis.test.ReflectionTest - student.id(属性为对象)的hasGetter:true07: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 数据源测试

@Testpublic 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" 即可,这样就能测试咱们本人开发的应用了反射器设置属性的数据源类了。

测试后果

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 类中的属性、字段、办法的信息。那么再有了这些信息当前就能够依照性能流程进行解耦,把属性、反射、包装,都顺次拆分进去,并依照设计准则,逐渐包装让外接更少的晓得外部的解决。
  • 这里的反射也算是小天花板的应用级别了,封装的工具类形式,如果在咱们也有相似的场景中,就能够间接拿来应用。因为整个工具类并没有太多的额定关联,间接拿来封装成一个工具包进行应用,解决平时的业务逻辑中组件化的局部,也是十分不错的。技术迁徙、学以致用、升职加薪
  • 因为整个工具包中波及的类还是比拟多的,大家在学习的过程中尽可能的验证和调试,以及对某个不分明的办法进行独自开发和测试,这样能力滤清整个构造是如何实现的。当你把这块的内容全副拿下,当前再遇到反射就是小意思了