Author: Xiao Fu Ge Blog: https://bugstack.cn - 系列内容
Precipitate, share, grow, and let yourself and others gain something! 😄
I. Introduction
为什么,读不懂框架源码?
We all know that as a programmer, if you want to learn deeper technologies, you need to read a lot of framework source code, learn the development routines and design ideas in these framework source code, so as to improve your programming ability.
Everyone knows this, but in practice, many coders can't read the framework source code at all. First of all, a very big problem is that in the face of such a huge source code of the framework, I don't know where to start. Compared with the usual development of business requirements, a large number of design principles and design patterns are used in the framework source code to decouple and implement system functions, and many related technologies such as reflection, proxy, and bytecode are also used.
When you think it is an instantiated object invocation method in ordinary business requirements, when you look for the process in the source code, you may not find when it initiates the call, how to pass parameters, and where to process assignments, etc. A series of problems have persuaded a good coder to quit on the way to start learning.
2. Goals
I don’t know if you have studied with the Mybatis source code in the process of learning “Handwritten Mybatis” . If you have the source code, you will most likely find that when we implement data source pooling, we use the method to obtain attribute information. hardcoded way. As shown in Figure 8-1
- That is, attributes such as
props.getProperty("driver")
andprops.getProperty("url")
are obtained by manual coding. - In fact, aren't drivers, url, username, and password all standard fixed fields? What's wrong with getting them in this way? According to our current understanding, there is nothing wrong, but in fact, in addition to these fields, there may be some extension fields configured sometimes, so how to obtain it, it cannot be hard-coded every time.
- So if you have read the source code of Mybatis, you will find that the meta-object reflection tool class implemented by Mybatis is used here, which can complete the reflection filling of the attributes of an object. The utility class for this piece is called MetaObject and provides the corresponding; Meta-Object, Object Wrapper, Object Factory, Object Wrapper Factory and the use of Reflector. Then in this chapter, we will implement the content of the reflection toolkit, because with our subsequent development, there will be many places where we need to use reflectors to gracefully process our attribute information. This also adds some powerful uses of reflection for you!
3. Design
If we need to uniformly set and obtain the value of the provided properties of an object, then we need to decouple the currently processed object, extract all its properties and methods, and follow different types. Reflection processing, thus packaged into a toolkit. As shown in Figure 8-2
- In fact, the entire design process revolves around how to disassemble objects and provide reflection operations. For an object, it includes object constructors, object properties, and object methods. Because the methods of objects are all operations of getting and setting values, they are basically get and set processing, so these methods need to be extracted and saved during the process of object disassembly.
- When the actual operation starts, it will rely on the object that has been instantiated to perform attribute processing. These processing processes are actually operated using the reflection provided by JDK, and the method names and input parameter types in the reflection process have been disassembled and processed by us, and they can be called directly when they are finally used.
4. Realization
1. Engineering structure
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
Project source code : https://github.com/fuzhengwei/small-mybatis
Meta object reflection tool class, the core class for processing object property settings and obtaining operations, as shown in Figure 8-3
- Use the Reflector reflector class to process the get/set attributes in the object class and wrap it as a callable Invoker reflection class, so that when the get/set method is called by reflection, you can use the method name to get the corresponding Invoker
getGetInvoker(String propertyName)
. - With the processing of the reflector, the packaging of the original object is followed by the method of creating the MetaObject meta-object provided by SystemMetaObject, dismantling the object we need to process and packaging the ObjectWrapper object. Because the type of an object also needs to be processed in detail, as well as the dismantling of attribute information, for example:
班级[0].学生.成绩
The attribute of the associated class in such a class needs to be disassembled recursively. To set and get property values. - In the end, it can be used elsewhere in Mybatis. When attribute value setting is required, the reflection toolkit can be used for processing. Here, we will use the reflection tool class to transform the processing of the Properties attribute in the data source pooling. Refer to the corresponding source code class in this chapter
2. Reflect the caller
Regarding the property value acquisition and setting in the object class, it can be divided into the get/set of the Field field and the invocation of the ordinary Method. In order to reduce the excessive processing of the user, the implementation of the centralized caller can be packaged into a calling strategy. Unified interface and different implementation classes with different strategies.
define interface
public interface Invoker {
Object invoke(Object target, Object[] args) throws Exception;
Class<?> getType();
}
- Any type of reflection call is inseparable from the object and the input parameters. As long as we define these two fields and the returned result generically, we can wrap the implementation classes of different strategies.
2.1 MethodInvoker
See the source code for details : 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);
}
}
- Provides method reflection invocation processing, and the constructor will pass in the corresponding method type.
2.2 GetFieldInvoker
See the source code for details : 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);
}
}
- The caller of the getter method handles, because get has a return value, so the result is returned directly after the operation of the Field field is completed.
2.3 SetFieldInvoker
See the source code for details : 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;
}
}
- The caller of the setter method handles it, because set only sets the value, so just return a null here.
3. Reflector Decoupled Objects
Reflector is specially used for decoupling object information. Only by parsing the attributes, methods and associated classes contained in an object information can it satisfy the subsequent setting and acquisition of attribute values.
See the source code : 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);
}
}
// ... 省略处理方法
}
- The reflector class provides various attributes, methods, types, and constructors for saving operations. When the reflector is called, it will gradually disassemble these attribute information from the object class through the processing of the constructor, which is convenient for subsequent reflection.
- When readers learn this part of the source code, they can refer to the corresponding classes and the processing methods here. These methods are some operations on reflection to obtain basic type and method information, and organize and store them.
4. Metaclass wrapping reflector
Reflector The reflector class provides the most basic core functions, and many methods are also private. For more convenient use, a layer of metaclass packaging is required. In the metaclass MetaClass provides the necessary Invoker reflection methods to create reflectors and use reflectors to get get/set.
See the source code for details : 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 metaclass is equivalent to wrapping the object we need to process, decoupling an original object, and wrapping a metaclass. These metaclasses, object wrappers, and object factories, etc., combine to form a metaobject. It is equivalent to saying that these metaclasses and metaobjects are decoupled packages of the original objects we need to operate. With such an operation, we can handle each property or method.
5. Object Wrapper
The object wrapper is equivalent to further reflective call wrapping processing, and also provides different wrapping strategies for different object types. Framework source code likes to use design patterns, never line-by-line ifelse code
More explicit methods to be used are defined in the object wrapper interface, including the definition of get/set standard general methods, getting get/set property names and property types, and adding properties.
Object Wrapper Interface
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);
// ... 省略
}
- All subsequent implementation classes that implement the object wrapper interface need to provide these method implementations. Basically, with these methods, it is very easy to handle the reflection operation of an object.
- Whether you set properties, get properties, get the corresponding field list or type, it can be satisfied.
6. Meta Object Encapsulation
After having reflectors, metaclasses, and object wrappers, a complete metaobject operation class can be combined by using object factories and wrapper factories. Because of the use of all different ways, including: wrapper strategy, packaging engineering, unified method processing, these all require a unified handler, that is, our meta object to manage.
See the source code for details : 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 is a wrapper for the entire service, providing the creation of wrapper types for various objects in the constructor. After that, some basic operation packages are provided, and this time the package is closer to actual use.
- Including the getValue(String name), setValue(String name, Object value) provided here, among them, when the attribute information in some objects is not a level, it is
班级[0].学生.成绩
It needs to be disassembled before it can be obtained. The corresponding object and property value. - When all this content is provided, you can use
SystemMetaObject#forObject
to provide access to the meta object.
7. Data source property settings
Well, now that we have the attribute reflection operation toolkit we implemented, the setting of attribute information in the data source can be operated more elegantly.
See the source code for details : 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;
}
}
- In the past, we used hard coding for the acquisition of property information in the data source, so this time in the setProperties method, we can use SystemMetaObject.forObject(dataSource) to get the meta object of the DataSource, that is, we can use reflection to get what we need. property value is set into it.
- In this way, the corresponding attribute value information can be obtained in the data sources UnpooledDataSource and PooledDataSource, instead of the hard-coded operation in the implementation of the two data sources.
5. Test
The test in this chapter will be divided into two parts, one is the test of the reflector tool class implemented in this chapter, and the other is that we connect the reflector tool class to the use of the data source to verify whether the use is smooth.
1. Prepare in advance
1.1 Create library table
Create a database named mybatis and create a table user and add test data in the library, as follows:
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 Configure the data source
<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>
- Configure data source information through
mybatis-config-datasource.xml
, including: driver, url, username, password - Here the dataSource test verifies UNPOOLED and POOLED, because these two are handled by the reflection tool class
1.3 Configure 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>
- This part does not need to be adjusted for the time being. At present, it is only a parameter of the type of input parameter. After we complete this part of the content, we will provide more other parameters for verification.
2. Unit testing
2.1 Reflection test
@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));
}
- This is a group of common test classes used to test MetaObject in Mybatis source code. We apply this unit test to our own reflection tool class to see if it can run normally.
Test Results
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
- Well, then in this test, we can see that we have obtained the corresponding attribute information, and can set and modify the attribute value, whether it is a single attribute or an object attribute, it can be operated.
2.2 Data source test
@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));
}
- Calling the test class of our handwritten framework does not require any changes, as long as the data source configuration uses
type="POOLED/UNPOOLED"
, we can test the data source class that we developed by ourselves using the reflector to set properties .
Test Results
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":"小傅哥"}
- According to the screenshots of unit testing and debugging, it can be seen that the property value is set to the object through reflection, which also satisfies our use when creating the data source. In this way, the data source can be successfully called to complete the data query operation.
7. Summary
- In the implementation of the reflection tool class in this chapter, a large number of reflection processing operations provided by the JDK are used, and it also includes the information that can obtain the attributes, fields, and methods of a Class class. Then, after having this information, you can decouple according to the functional process, separate the attributes, reflections, and packaging in turn, and gradually package them according to the design principles, so that the external part knows less about the internal processing.
- The reflection here can be regarded as the usage level of the small ceiling. The encapsulated tool type method can be used directly if we have a similar scene. Because the entire tool class does not have too many additional associations, it is also very good to directly encapsulate it into a toolkit for use, and deal with the componentized part of the usual business logic. Technology transfer, application of knowledge, promotion and salary increase
- Since there are still many classes involved in the entire toolkit, everyone should try their best to verify and debug during the learning process, and develop and test an unclear method individually, so as to filter out how the entire structure is implemented . When you take down all the content of this piece, it will be trivial to encounter reflections in the future.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。