作者:小傅哥
博客:https://bugstack.cn
沉淀、分享、成长,让自己和他人都能有所收获!😄
为什么,读不懂框架源码?
我们都知道作为一个程序员,如果想学习到更深层次的技术,就需要阅读大量的框架源码,学习这些框架源码中的开发套路和设计思想,从而提升自己的编程能力。
事大家都清楚,但在实操上,很多码农根本没法阅读框架源码。首先一个非常大的问题是,面对如此庞大的框架源码,不知道从哪下手。与平常的业务需求开发相比,框架源码中运用了大量的设计原则和设计模式对系统功能进行解耦和实现,也使用了不少如反射、代理、字节码等相关技术。
当你还以为是平常的业务需求中的实例化对象调用方法,去找寻源码中的流程时,可能根本就找不到它是何时发起调用的、怎么进行传参、在哪处理赋值的等一连串的问题,都把一个好码农劝退在开始学习的路上。
不知道大家在学习《手写 Mybatis》的过程中,是否有对照 Mybatis 源码一起学习,如果你有对照源码,那么大概率会发现我们在实现数据源池化时,对于属性信息的获取,采用的是硬编码的方式。如图 8-1 所示
props.getProperty("driver")
、props.getProperty("url")
等属性,都是通过手动编码的方式获取的。如果说我们需要对一个对象的所提供的属性进行统一的设置和获取值的操作,那么就需要把当前这个被处理的对象进行解耦,提取出它所有的属性和方法,并按照不同的类型进行反射处理,从而包装成一个工具包。如图 8-2 所示
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 所示
getGetInvoker(String propertyName)
。班级[0].学生.成绩
这样一个类中的关联类的属性,则需要进行递归的方式拆解处理后,才能设置和获取属性值。关于对象类中的属性值获取和设置可以分为 Field 字段的 get/set 还有普通的 Method 的调用,为了减少使用方的过多的处理,这里可以把集中调用者的实现包装成调用策略,统一接口不同策略不同的实现类。
定义接口
public interface Invoker { Object invoke(Object target, Object[] args) throws Exception; Class<?> getType(); }
源码详见: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); } }
源码详见: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); } }
源码详见: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; } }
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 反射器类提供的是最基础的核心功能,很多方法也都是私有的,为了更加方便的使用,还需要做一层元类的包装。在元类 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); } // ... 方法包装 }
对象包装器相当于是更加进一步反射调用包装处理,同时也为不同的对象类型提供不同的包装策略。框架源码都喜欢使用设计模式,从来不是一行行ifelse的代码
在对象包装器接口中定义了更加明确的需要使用的方法,包括定义出了 get/set 标准的通用方法、获取getset属性名称和属性类型,以及添加属性等操作。
对象包装器接口
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); // ... 省略 }
在有了反射器、元类、对象包装器以后,在使用对象工厂和包装工厂,就可以组合出一个完整的元对象操作类了。因为所有的不同方式的使用,包括:包装器策略、包装工程、统一的方法处理,这些都需要一个统一的处理方,也就是我们的元对象进行管理。
源码详见: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); } } // ... 省略 }
班级[0].学生.成绩
还需要被拆解后才能获取到对应的对象和属性值。SystemMetaObject#forObject
提供元对象的获取了。好了,现在有了我们实现的属性反射操作工具包,那么对于数据源中属性信息的设置,就可以更加优雅的操作了。
源码详见: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; } }
本章节的测试会分为2部分,一部分是我们这个章节实现的反射器工具类的测试,另外一方面是我们把反射器工具类接入到数据源的使用中,验证使用是否顺利。
创建一个数据库名称为 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', '小傅哥');
<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<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>
@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)); }
测试结果
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
@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"
即可,这样就能测试我们自己开发的使用了反射器设置属性的数据源类了。测试结果
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":"小傅哥"}