MyBatis源码解读(二)

二、MyBatis常用类

2.1、XPathParser

XPath可以很简单的使用路径表达式XML文档中选取节点元素。该工具的核心作用就是解析xml文件,包括我们的配置文件和mapper文件。这项技术也是爬虫的核心技术之一,下边我们了解一下xpath的基础语法,但这不是我们的重点,一切以了解为主。如下图:在浏览器中我们可以很轻松的获取一个标签的xpath。

2.1.1、基础语法

表达式 描述
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。

比如下面这个例子。

路径表达式 结果
properties 选取 properties元素的所有节点。
/properties 选取根元素 properties。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
properties/property 选取属于 properties的子元素的所有 property元素。
//property 选取所有 property 元素,而不管它们在文档中的位置。
properties//property 选择属于 properties元素的后代的所有 property元素,而不管它们位于 properties之下的什么位置。
//@lang 选取名为 lang 的所有属性。

2.1.2、谓语(Predicates)

谓语是用来查找某个【特定的节点或者包含某个指定的值】的节点。通常,谓语被嵌在方括号中。在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果。

路径表达式 结果
/properties/property[1] 选取属于 properties子元素的第一个 property元素。
/properties/property[last()] 选取属于 properties子元素的最后一个 property元素。
/properties/property[last()-1] 选取属于 properties子元素的倒数第二个 property元素。
/properties/property[position()<3] 选取最前面的两个属于 properties元素的子元素的 property元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang='eng'] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
/properties/property[value>10.00] 选取 properties 元素的所有 property元素,且其中的 value元素的值须大于 10.00。
/properties/property[value>10.00]/title 选取 properties元素中的 property元素的所有 value元素,且其中的 price 元素的值须大于 10.00。

2.1.3、选取未知节点

XPath 通配符可用来选取未知的 XML 元素。

通配符 描述
* 匹配任何元素节点。
@* 匹配任何属性节点。
node() 匹配任何类型的节点。

比如下面这样。

路径表达式 结果
/bookstore/* 选取 bookstore 元素的所有子元素。
//* 选取文档中的所有元素。
//title[@*] 选取所有带有属性的 title 元素。

2.1.4、选取若干路径

通过在路径表达式中使用"|"运算符,您可以选取若干个路径。在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果。

| 路径表达式 | 结果 |
|-----------------------|--------------|-----------------------------------------------------------|
| //book/title | //book/price | 选取 book 元素的所有 title 和 price 元素。 |
| //title | //price | 选取文档中的所有 title 和 price 元素。 |
| /bookstore/book/title | //price | 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。 |

2.1.5、测试用例

我们使用mybatis提供的XPathParser对mybatis.xml进行解析。

java 复制代码
@Test
public void testXpathParser() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
    XPathParser xPathParser = new XPathParser(inputStream, true, null, new XMLMapperEntityResolver());
    XNode xNode = xPathParser.evalNode("/configuration/environments/environment/dataSource");
    System.out.println(xNode);
}

2.2、OGNL表达式

在mybatis中的动态sql中存在很多表达式,如if标签中常见的(username != null && username != '')或者 #{id},为了解析这类标签,mybatis使用了OGNL技术,OGNL是 Object-Graph Navigation Language 的缩写,对象-图形导航语言,语法为:#{ }

2.2.1、OGNL三要素

2.2.1.1、表达式(Expression)

表达式是整个OGNL的核心,所有的OGNL操作都是针对表达式的解析结果进行处理的。表达式规定了此次OGNL操作到底要干什么。因此,表达式其实是一个带有语法含义的字符串,这个字符串将规定操作的类型和操作的内容。

OGNL支持大量的表达式语法,不仅支持"链式"描述对象访问路径,还支持在表达式中进行简单的计算,甚至还能够支持复杂的Lambda表达式等。

2.2.1.2、Root对象(Root Object)

OGNL的Root对象可以理解为OGNL的操作对象。当OGNL表达式规定了"干什么"以后,我们还需要指定对谁干 。OGNL的Root对象实际上是一个Java对象,是所有OGNL操作的实际载体。这就意味着,如果我们有一个OGNL的表达式,那么我们实际上需要针对Root对象去进行OGNL表达式的计算并返回结果。

2.2.1.3、上下文环境(Context)

有了表达式和Root对象,我们已经可以使用OGNL的基本功能。例如,根据表达式针对OGNL中的Root对象进行"取值"或者"写值"操作。

不过,事实上,在OGNL的内部,所有的操作都会在一个特定的数据环境中运行,这个数据环境就是OGNL的上下文环境(Context)。说得再明白一些,就是这个上下文环境(Context)将规定OGNL的操作在哪里干

OGNL的上下文环境是一个Map结构,称之为OgnlContext。之前我们所提到的Root对象(Root Object),事实上也会被添加到上下文环境中去,并且将被作为一个特殊的变量进行处理。

2.2.2、OGNL的基本操作

使用OGNL需要如下的依赖,但事实上我们并不需要显示引入该依赖,因为mybatis已经通过依赖传递的方式将其引入。

xml 复制代码
<dependency>
    <groupId>ognl</groupId>
    <artifactId>ognl</artifactId>
    <version>3.3.3</version>
</dependency>

2.2.2.1、对Root对象(Root Object)的访问

针对OGNL的Root对象的对象树的访问是通过使用"点号"将对象的引用串联起来实现的。通过这种方式,OGNL实际上将一个树形的对象结构转化成了一个链式结构的字符串结构来表达语义。

java 复制代码
@Test
public void testOgnlContext1() throws OgnlException {
    // 定义一个参数
    Account account = new Account();
    account.setUsername("XiaoLin");
    // 解析表达式,这种表达式在<if>标签中的test中使用经常使用
    Object value = Ognl.getValue("username == null && username == ''", account);
    System.out.println("value = " + value);
}

2.2.2.2、对上下文环境(Context)的访问

由于OGNL的上下文是一个Map结构,在OGNL进行计算时可以事先在上下文环境中设置一些参数,并让OGNL将这些参数带入进行计算。有时候也需要对这些上下文环境中的参数进行访问,访问这些参数时,需要通过#符号加上链式表达式来进行,从而表示与访问Root对象(Root Object)的区别。

typescript 复制代码
@Test
public void testOgnlContext2() throws OgnlException {
    // 定义一个map上下文
    Account account = new Account();
    account.setUsername("tom");
    Map<String,Object> map = new HashMap<>(4);
    map.put("account",account);
​
    Object username = Ognl.getValue("#account.username",map,new Object() );
    System.out.println(username);
}

2.2.2.3、对静态变量的访问

在OGNL中,对于静态变量或者静态方法的访问,需要通过@[class]@[field|method]的表达式语法来进行访问。

java 复制代码
// 访问@com.ydlclass.mybatissource.util.DBUtilBindThread@threadLocal类中名为ENABLE的属性值  
@Test
public void testOgnlContext3() throws OgnlException {
    Object enable = Ognl.getValue("@com.ydlclass.mybatissource.util.DBUtilBindThread@threadLocal",new Object() );
    System.out.println(enable);
}

2.2.2.4、方法调用

在OGNL中调用方法,可以直接通过类似Java的方法调用方式进行,也就是通过点号加方法名称完成方法调用,甚至可以传递参数。这里我们就不做测试了。

typescript 复制代码
@Test
public void testOgnlContext4() throws OgnlException {
    // 定义一个map上下文
    Account account = new Account();
    account.setUsername("Maloney");
    Map<String,Object> map = new HashMap<>(4);
    map.put("account",account);
​
    Object username = Ognl.getValue("#account.username.substring(1,5)",map,new Object() );
    System.out.println(username);
}

2.3、别名注册器

mybatis提供了TypeAliasRegistry作为别名注册器,同时默认注入了大量的基础类型的别名,他是配置类的一个成员变量。

java 复制代码
 protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

他的实现如下。

scss 复制代码
public class TypeAliasRegistry {
​
    private final Map<String, Class<?>> typeAliases = new HashMap<>();
​
    // 构造方法中默认注册了大量的别名
    public TypeAliasRegistry() {
        registerAlias("string", String.class);
​
        registerAlias("byte", Byte.class);
        registerAlias("char", Character.class);
        registerAlias("character", Character.class);
        registerAlias("long", Long.class);
        registerAlias("short", Short.class);
        registerAlias("int", Integer.class);
        registerAlias("integer", Integer.class);
        registerAlias("double", Double.class);
        registerAlias("float", Float.class);
        registerAlias("boolean", Boolean.class);
​
        registerAlias("byte[]", Byte[].class);
        registerAlias("char[]", Character[].class);
        registerAlias("character[]", Character[].class);
        registerAlias("long[]", Long[].class);
        registerAlias("short[]", Short[].class);
        registerAlias("int[]", Integer[].class);
        registerAlias("integer[]", Integer[].class);
        registerAlias("double[]", Double[].class);
        registerAlias("float[]", Float[].class);
        registerAlias("boolean[]", Boolean[].class);
​
        registerAlias("_byte", byte.class);
        registerAlias("_char", char.class);
        registerAlias("_character", char.class);
        registerAlias("_long", long.class);
        registerAlias("_short", short.class);
        registerAlias("_int", int.class);
        registerAlias("_integer", int.class);
        registerAlias("_double", double.class);
        registerAlias("_float", float.class);
        registerAlias("_boolean", boolean.class);
​
        registerAlias("_byte[]", byte[].class);
        registerAlias("_char[]", char[].class);
        registerAlias("_character[]", char[].class);
        registerAlias("_long[]", long[].class);
        registerAlias("_short[]", short[].class);
        registerAlias("_int[]", int[].class);
        registerAlias("_integer[]", int[].class);
        registerAlias("_double[]", double[].class);
        registerAlias("_float[]", float[].class);
        registerAlias("_boolean[]", boolean[].class);
​
        registerAlias("date", Date.class);
        registerAlias("decimal", BigDecimal.class);
        registerAlias("bigdecimal", BigDecimal.class);
        registerAlias("biginteger", BigInteger.class);
        registerAlias("object", Object.class);
​
        registerAlias("date[]", Date[].class);
        registerAlias("decimal[]", BigDecimal[].class);
        registerAlias("bigdecimal[]", BigDecimal[].class);
        registerAlias("biginteger[]", BigInteger[].class);
        registerAlias("object[]", Object[].class);
​
        registerAlias("map", Map.class);
        registerAlias("hashmap", HashMap.class);
        registerAlias("list", List.class);
        registerAlias("arraylist", ArrayList.class);
        registerAlias("collection", Collection.class);
        registerAlias("iterator", Iterator.class);
​
        registerAlias("ResultSet", ResultSet.class);
    }
    //.....
}

关于别名注册器的使用及其简单。

csharp 复制代码
@Test
public void testTypeAliasRegistry(){
    TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
    typeAliasRegistry.registerAlias("employee", Employee.class);
    typeAliasRegistry.registerAliases("com.ydlclass.mybatissource.entity");
}

当然我们还可以进行批量注册。

typescript 复制代码
public void registerAliases(String packageName) {
    registerAliases(packageName, Object.class);
}
​
public void registerAliases(String packageName, Class<?> superType) {
    // ResolverUtil找到所有Object的子类
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    for (Class<?> type : typeSet) {
        // 不是匿名类 | 不是接口 | 不是内部类
        if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
            registerAlias(type);
        }
    }
}

我们还可以进行单个别名的注册。

vbnet 复制代码
public void registerAlias(Class<?> type) {
    //
    String alias = type.getSimpleName();
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
        alias = aliasAnnotation.value();
    }
    registerAlias(alias, type);
}
​
public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
        throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
        throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
    typeAliases.put(key, value);
}
​

2.4、类型转化器

2.4.1、TypeHandler

TypeHandler负责对查询结果进b行类型转换,我们当然可以使用ResultSet的原生方法对结果进行类型转化,抽象模型从来的面向对象最难也是最灵活的内容,我们当然可以使用switch-case语法通过判断类型选择调用方法的版本如下。

typescript 复制代码
public Object getResult(JDBCType jdbcType,ResultSet rs,String columnName) throws SQLException {
    Object object = null;
    switch(jdbcType){
        case INTEGER -> object = rs.getInt(columnName);
        case BIGINT -> object = rs.getLong(columnName);
        case VARCHAR -> object = rs.getString(columnName);
        //.... 省略其他众多的情况
    }
    return object;
}

很明显,以上这种代码的编写风格是一种面向过程的编码风格,对今后的扩展极为不利,以后每次新增类型都需要修改代码。

所以mybatis抽象出一个TypeHandler作为顶层接口,对转化工作做了模型抽离。

java 复制代码
public interface TypeHandler<T> {
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
    T getResult(ResultSet rs, String columnName) throws SQLException;
    T getResult(ResultSet rs, int columnIndex) throws SQLException;
    T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}

同时提供了大量的实现策略,(这样的实现本质就是策略设计模式),核心策略在构造的时候注册,新增的通过配置文件添加,极大的降低了系统的耦合性。

我们来写一个测试用例。

ini 复制代码
@Test
public void testTypeHandler(){
    Connection connection = DBUtilBindThread.getConnection();
    String sql = "select id,username,money from account";
​
    try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()){
            TypeHandler<Integer> typeHandler = new IntegerTypeHandler();
            // 下边的代码本质就是resultSet.getInt("money")
            Integer money = typeHandler.getResult(resultSet, "money");
            System.out.println(money);
​
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

同样我们看看IntegerTypeHandler的底层实现,这是策略模式的典型案例。

sql 复制代码
@Override
public Integer getNullableResult(ResultSet rs, String columnName)
    throws SQLException {
    int result = rs.getInt(columnName);
    return result == 0 && rs.wasNull() ? null : result;
}
​

2.4.2、TypeHandlerRegistry

mybatis当然有提供一个专门的注册器TypeHandlerRegistry用来注册TypeHandler。TypeHandlerRegistry也是配置类的一个成员变量。

java 复制代码
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);

TypeHandlerRegistry的作用有两个:

  1. 为了注册存储类型转换器。
  2. 为了根据javaType或者JdbcType查询合适的TypeHandler。

我们来瞄一眼TypeHandlerRegistry的源码,他有这些成员变量。

我们会发现有一个老朋友------JdbcType。

我们点进去看看,里面有什么?我们可以发现mysql里面有的类型这里面都有。

我们根据一个jdbctype可以选定唯一一个jdbcTypeHandlerMap。事实上,我们一般情况会根据BaseTypeHandler的泛型来确定该处理器的javaType,从而确定使用的rs的getxxx和setxxx方法。JdbcType本质上没有什么具体的作用,更多的是作为一种标识,用来确定使用一个唯一的handler,在定义TypeHandler时需要指定泛型,也就意味着javaType一定会被指定,那么我们选取TypeHandler时就会有以下两种情况:

  1. 即指定了javaType,也指定了JdbcType,我们可以匹配一个确定的TypeHandler。
  2. 指定了javaType,未指定JdbcType,我们可以匹配很多TypeHandler,但我们只选一个就选默认。

对于java的String类型,就有九个与之匹配的Typehandler。

我们来看一下BaseTypeHandler的实现。

scala 复制代码
public class ClobTypeHandler extends BaseTypeHandler<String> {
​
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
        throws SQLException {
        StringReader reader = new StringReader(parameter);
        ps.setCharacterStream(i, reader, parameter.length());
    }
​
    @Override
    public String getNullableResult(ResultSet rs, String columnName)
        throws SQLException {
        Clob clob = rs.getClob(columnName);
        return toString(clob);
    }
}

我们其实可以去瞄一眼TypeHandlerRegistry的构造方法。他一上来就给了我们兜底的对象。

他有一个默认实现ObjectTypeHandler。

点进去之后我们可以发现全部都是getObject。这就是兜底解决方案。

看完了构造器以后,我们可以开始往下看看register方法。

我们来逐行解析一下这段代码。

scss 复制代码
 private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
     // 看看这个类上面是否有MappedJdbcTypes这个注解
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
     // 如果这个类上有MappedJdbcTypes这个注解的话
    if (mappedJdbcTypes != null) {
      for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
          // 就将javaType、handledJdbcType、typeHandler三者一起拿去注册。
        register(javaType, handledJdbcType, typeHandler);
      }
        // 如果你没有这个注解的话,我们就以javaType的形式去注册。
      if (mappedJdbcTypes.includeNullJdbcType()) {
        register(javaType, null, typeHandler);
      }
    } else {
      register(javaType, null, typeHandler);
    }
  }

我们再来看看这个注册方法。

ini 复制代码
  private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
      Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
      if (map == null || map == NULL_TYPE_HANDLER_MAP) {
        map = new HashMap<>();
      }
      map.put(jdbcType, handler);
      typeHandlerMap.put(javaType, map);
    }
    allTypeHandlersMap.put(handler.getClass(), handler);
  }

2.5、反射

2.5.1、MetaObject

MyBatis有一个MetaObject专门用于封装反射,我们来看看MetaObject的源码。

kotlin 复制代码
public class MetaObject {
    // 原始对象
    private final Object originalObject;
    // 包装后的对象
    private final ObjectWrapper objectWrapper;
    // 以下的三个工厂我们使用默认使用就可以了,当然我们可以自由设定
    // 用来实例化对象的工厂
    private final ObjectFactory objectFactory;
    // 用来获取一个包装的对象的工厂,给我们扩展使用
    private final ObjectWrapperFactory objectWrapperFactory;
    // reflectorFactory可以为每一个类生成Reflector,他提供了反射的基本能力。
    private final ReflectorFactory reflectorFactory;
​
    private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
        this.originalObject = object;
        this.objectFactory = objectFactory;
        this.objectWrapperFactory = objectWrapperFactory;
        this.reflectorFactory = reflectorFactory;
​
        // 1、不包装
        if (object instanceof ObjectWrapper) {
            this.objectWrapper = (ObjectWrapper) object;
            // 2、由工厂进行包装
        } else if (objectWrapperFactory.hasWrapperFor(object)) {
            this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
            // 3、框架自行包装
        } else if (object instanceof Map) {
            this.objectWrapper = new MapWrapper(this, (Map) object);
        } else if (object instanceof Collection) {
            this.objectWrapper = new CollectionWrapper(this, (Collection) object);
        } else {
            this.objectWrapper = new BeanWrapper(this, object);
        }
    }
​
    // 对外公开的构建方法
    public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
        if (object == null) {
            return SystemMetaObject.NULL_META_OBJECT;
        } else {
            return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
        }
    }
​
    // ... 省略了其他的方法
​
}
​

2.5.2、ObjectWrapperFactory

mybatis提供了objectWrapperFactory可以对object的包装过程实现自定义,其默认实现如下,默认的实现不会做任何的事情。

typescript 复制代码
public class DefaultObjectWrapperFactory implements ObjectWrapperFactory {
​
    @Override
    public boolean hasWrapperFor(Object object) {
        return false;
    }
​
    @Override
    public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
        // 永远不应该调用DefaultObjectWrapperFactory来提供ObjectWrapper。
        throw new ReflectionException("The DefaultObjectWrapperFactory should never be called to provide an ObjectWrapper.");
    }
}

2.5.3、ObjectWapper

操作对象大多是通过ObjectWapper进行的,他的继承结构如下,我们以BeanWrapper为例,一起分析这个实现的原理,当然MetaObject中也是这样构建的。

从源码中我们分析得知,BeanWrapper维护了一个object实例本身,一个metaClass(用来进行反射操作)。

scala 复制代码
public class BeanWrapper extends BaseWrapper {
​
    private final Object object;
    private final MetaClass metaClass;
​
    public BeanWrapper(MetaObject metaObject, Object object) {
        super(metaObject);
        this.object = object;
        this.metaClass = MetaClass.forClass(object.getClass(), metaObject.getReflectorFactory());
    }
}

2.5.4、ReflectorFactory

ReflectorFactory维护了我们需要的Reflector,并提供了创建方式,其默认实现长这样。

typescript 复制代码
public class DefaultReflectorFactory implements ReflectorFactory {
    private boolean classCacheEnabled = true;
    private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
​
    public DefaultReflectorFactory() {
    }
​
    @Override
    public Reflector findForClass(Class<?> type) {
        if (classCacheEnabled) {
            // synchronized (type) removed see issue #461
            return MapUtil.computeIfAbsent(reflectorMap, type, Reflector::new);
        } else {
            return new Reflector(type);
        }
    }
​
}

ReflectorFactory可以进行自定义。

2.5.5、Reflector

Reflector是一个反射工具,里边缓存了一个类的类型,setter方法,getter方法,默认构造器。

ini 复制代码
public class Reflector {
​
    private final Class<?> type;
    private final String[] readablePropertyNames;
    private final String[] writablePropertyNames;
    private final Map<String, Invoker> setMethods = new HashMap<>();
    private final Map<String, Invoker> getMethods = new HashMap<>();
    private final Map<String, Class<?>> setTypes = new HashMap<>();
    private final Map<String, Class<?>> getTypes = new HashMap<>();
    private Constructor<?> defaultConstructor;
​
    private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
​
    // 构建过程
    public Reflector(Class<?> clazz) {
        type = clazz;
        addDefaultConstructor(clazz);
        Method[] classMethods = getClassMethods(clazz);
        addGetMethods(classMethods);
        addSetMethods(classMethods);
        addFields(clazz);
        readablePropertyNames = getMethods.keySet().toArray(new String[0]);
        writablePropertyNames = setMethods.keySet().toArray(new String[0]);
        // 处理大小写不敏感
        for (String propName : readablePropertyNames) {
            caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
        }
        for (String propName : writablePropertyNames) {
            caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
        }
    }
​
    private void addDefaultConstructor(Class<?> clazz) {
        Constructor<?>[] constructors = clazz.getDeclaredConstructors();
        Arrays.stream(constructors).filter(constructor -> constructor.getParameterTypes().length == 0)
            .findAny().ifPresent(constructor -> this.defaultConstructor = constructor);
    }
​
    private void addGetMethods(Method[] methods) {
        Map<String, List<Method>> conflictingGetters = new HashMap<>();
        Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
            .forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
        resolveGetterConflicts(conflictingGetters);
    }
​
​
    private void addSetMethods(Method[] methods) {
        Map<String, List<Method>> conflictingSetters = new HashMap<>();
        Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 1 && PropertyNamer.isSetter(m.getName()))
            .forEach(m -> addMethodConflict(conflictingSetters, PropertyNamer.methodToProperty(m.getName()), m));
        resolveSetterConflicts(conflictingSetters);
    }
​
​
    public Class<?> getType() {
        return type;
    }
​
    // ... 省略了大量的反射代码
​
}

2.5.6、setValue

我们一般使用metaObject的setValue("dept.id", 1)方法来赋值,我们来看一看这个MetaObject类的方法。

scss 复制代码
// setter,赋值操作
public void setValue(String name, Object value) { // name为需要赋值的属性,value为需要赋值的值
  // 构造一个属性分析解析器
    PropertyTokenizer prop = new PropertyTokenizer(name);
    // 如果是非简单的属性,如:dept.id 
    if (prop.hasNext()) { // 看到hasNext,想到迭代器设计模式
        // 如果dept没有值,需要先实例化dept
        MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
        if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
            if (value == null) {
                // 如果value为null,不要实例化子属性
                return;
            } else {
                // 使用objectFactory构建一个employee的实例,
                // objectFactory的默认实现就是使用反射创建一个实例
                metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
            }
        }
        // metaValue -> dept  ,  prop.getChildren() -> id
        metaValue.setValue(prop.getChildren(), value);
    } else {
        // 普通属性的赋值,如name
        objectWrapper.set(prop, value);
    }
}
​

2.5.7、getValue

看完了setValue以后,我们肯定还需要再来看看getValue方法。我们可以发现很明显这是一个递归调用。

typescript 复制代码
// 此处传入dept
public Object getValue(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
      if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
        return null;
      } else {
          // 此处使用了递归调用
        return metaValue.getValue(prop.getChildren());
      }
    } else {
        // 从实例中直接获取
      return objectWrapper.get(prop);
    }
  }
​

在往上一级的调用是。

typescript 复制代码
public MetaObject metaObjectForProperty(String name) {
    Object value = getValue(name);
    return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
}

我们可以看看这个forObject方法,因为我们传进来的是一个字符串,也就是dept他,他没有实例化对象,所以此时会返回SystemMetaObject.NULL_META_OBJECT。

typescript 复制代码
public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    // 如果获取的值为空
    if (object == null) {
        return SystemMetaObject.NULL_META_OBJECT;
    } else {
        return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
    }
}

2.5.8、属性分析器

属性分析器是PropertyTokenizer。我们来看看他的构造方法,这个属性分析器使用了迭代器设计模式。

ini 复制代码
public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
    private String name;
    private final String indexedName;
    private String index;
    private final String children;
​
    public PropertyTokenizer(String fullname) {
        // 用.去进行切割传进来的属性名
        int delim = fullname.indexOf('.');
        // 说明这个属性中有.,如dept.id,说明是复杂属性
        if (delim > -1) {
            // 截取.之前的内容作为name,此处是dept
            name = fullname.substring(0, delim);
            // .之后的作为children,此处是id
            children = fullname.substring(delim + 1);
        } else {
            // 如果没有.,说明是简单属性,那么name就是fullname
            name = fullname;
            children = null;
        }
        indexedName = name;
        // employees[1]
        delim = name.indexOf('[');
        if (delim > -1) {
            // 1
            index = name.substring(delim + 1, name.length() - 1);
            // employee
            name = name.substring(0, delim);
        }
    }
​
​
    // 继续分析子属性
    @Override
    public PropertyTokenizer next() {
        return new PropertyTokenizer(children);
    }
​
    // ...省略其他的setter和getter
​
​
}

2.5.9、构建成员变量实例

如果我们传进来一个dept字符串的话,那么并未实例化,但我们试图为其复制,所有必须通过反射进行实例化。

ini 复制代码
metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);

objectWrapper是一个接口,我们以他的实现BeanWapper的实现为例。

typescript 复制代码
@Override
public MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory) {
    MetaObject metaValue;
    // 通过属性的名字获得属性的类型,com.xxx.Dept  setDept(Dept dept) Dept.class
    Class<?> type = getSetterType(prop.getName());
    try {
        // 根据类型创建一个实例
        Object newObject = objectFactory.create(type);
        // 构建一个MetaObject
        metaValue = MetaObject.forObject(newObject, metaObject.getObjectFactory(), metaObject.getObjectWrapperFactory(), metaObject.getReflectorFactory());
        // 赋值
        set(prop, newObject);
    } catch (Exception e) {
        throw new ReflectionException("Cannot set value of property '" + name + "' because '" + name + "' is null and cannot be instantiated on instance of " + type.getName() + ". Cause:" + e.toString(), e);
    }
    return metaValue;
}

我们进去getSetterType这个方法。

ini 复制代码
public Class<?> getSetterType(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
        MetaObject metaValue = metaObject.metaObjectForProperty(prop.getIndexedName());
        if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
            return metaClass.getSetterType(name);
        } else {
            return metaValue.getSetterType(prop.getChildren());
        }
    } else {
        return metaClass.getSetterType(name);
    }
}

2.6、ErrorContext

在mybatis中为了更好的定位异常或错误,特封装了ErrorContext类,用于描述错误信息,他比传统异常机制要好很多,传统的异常打印出来都是一片一片的,内容特别多,但是ErrorContext输出的内容可以自由定制,它使用了ThreadLocal,可以非常好的将问题定位到某个线程,同时保证了线程安全。

我们来看一下ErrorContext打印出来的异常。随便把sql语句写错即可。他的错误提示更加精准,而且他也保留了原来的错误消息的提示。

我们来看一下源码。

arduino 复制代码
public class ErrorContext {
​
    private static final String LINE_SEPARATOR = System.lineSeparator();
    private static final ThreadLocal<ErrorContext> LOCAL = ThreadLocal.withInitial(ErrorContext::new);
​
    // 所有可能发生问题的地方都给你保存下来
    private ErrorContext stored;
    
    // 错误将来可能发生在哪个资源文件里面
    private String resource;
    
    // 错误是发生在哪个行为上(增删改查
    private String activity;
    
    // 哪个对象发生了错误
    private String object;
    
    // 你的异常消息是什么
    private String message;
    
    // 哪条sql语句发生了异常
    private String sql;
    private Throwable cause;
​
    private ErrorContext() {
    }
​
    public static ErrorContext instance() {
        return LOCAL.get();
    }
    // 该方法实现十分简单,内容可再源码中查看
​
​
}
​

里面有一个很重要的ThreadLocal变量LOCAL,他维护了一个当前线程的ErrorContext对象

ErrorContext负责封装异常上下文,而ExceptionFactory则负责输出异常上下文。

arduino 复制代码
public class ExceptionFactory {
​
    private ExceptionFactory() {
        // Prevent Instantiation
    }
​
    public static RuntimeException wrapException(String message, Exception e) {
        return new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e);
    }
}
​

我们可以使用源码来编写一个测试用例。

scss 复制代码
@Test
public void testErrorContext() throws InterruptedException {
​
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    CountDownLatch countDownLatch = new CountDownLatch(10);
    for (int i = 0; i < 10; i++) {
        executorService.execute(() -> {
            try {
                // mybatis对异常信息抽象了object、activity、sql等信心
                ErrorContext.instance().object(this.getClass().getName());
                ErrorContext.instance().activity("处理事件A中");
                ErrorContext.instance().message("正在执行sql语句");
                ErrorContext.instance().sql("select * * from user");
                countDownLatch.countDown();
                if (new Random().nextInt(10) > 6) {
                    int j = 1 / 0;
                }
            } catch (Exception e) {
                throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
            }
        });
    }
    countDownLatch.await();
}
​
相关推荐
Long_poem9 分钟前
【自学笔记】Spring Boot框架技术基础知识点总览-持续更新
spring boot·笔记·后端
圆内~搁浅25 分钟前
langchain本地知识库问答机器人集成本地知识库
数据库·langchain·机器人
hong_zc1 小时前
SpringBoot 配置文件
java·spring boot·后端
早起的年轻人1 小时前
Docket Desktop 安装redis 并设置密码
数据库·redis·缓存
神马都会亿点点的毛毛张1 小时前
【Docker教程】万字长文详解Docker命令
java·运维·后端·docker·容器
xlxxy_1 小时前
ABAP数据库表的增改查
开发语言·前端·数据库·sql·oracle·excel
朗迹 - 张伟1 小时前
Golang连接使用SqlCipher
开发语言·后端·golang
m0_748257462 小时前
创建一个简单的spring boot+vue前后端分离项目
vue.js·spring boot·后端
清水加冰2 小时前
【MySQL】索引
数据库·mysql