写点什么

MyBatis3 源码解析 (7)TypeHandler 注册与获取

作者:
  • 2022 年 2 月 16 日
  • 本文字数:5146 字

    阅读完需:约 17 分钟

简介

在上篇文章中,我们介绍了 TypeHandler 的简单使用和解析了 TypeHandler 的处理核心,这篇文章中我们接着看到 TypeHandler 是如注册和获取使用的

源码解析

TypeHandler 注册

typeHandler 注册的函数代码如下:


  • 根据 JavaType 放入第一层 Map

  • 根据 jdbcType 放入第二层 Map


  configuration.getTypeHandlerRegistry().register(String[].class, JdbcType.VARCHAR, StringArrayTypeHandler.class);
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); }
复制代码

TypeHandler 获取

在探索中,我们发现是在 Mapper 初始化的过程就会去获取 TypeHandler 了,在示例代码中我们调用了函数:addMapper


在该函数中,我们看到了我们熟悉的(前面文章分析过,Sql 执行其实就是从 Mapper 代理类开始的)MapperProxy 相关的东西


  public <T> void addMapper(Class<T> type) {    if (type.isInterface()) {      if (hasMapper(type)) {        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");      }      boolean loadCompleted = false;      try {        // 生成我们熟悉的Mapper代理类        knownMappers.put(type, new MapperProxyFactory<>(type));        // It's important that the type is added before the parser is run        // otherwise the binding may automatically be attempted by the        // mapper parser. If the type is already known, it won't try.        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);  // 进行解析        parser.parse();        loadCompleted = true;      } finally {        if (!loadCompleted) {          knownMappers.remove(type);        }      }    }  }
复制代码


跟踪下面,我们看到 SQLSource 生成的相关代码,ParameterMapper 就是从 SQLSource 带下去的


  void parseStatement(Method method) {    ......    getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {      final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);      ......    });  }
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) { // issue #3 if (script.startsWith("<script>")) { XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver()); return createSqlSource(configuration, parser.evalNode("/script"), parameterType); } else { // issue #127 script = PropertyParser.parse(script, configuration.getVariables()); TextSqlNode textSqlNode = new TextSqlNode(script); if (textSqlNode.isDynamic()) { // 这个暂时不理解 return new DynamicSqlSource(configuration, textSqlNode); } else { // 目前走的这个 return new RawSqlSource(configuration, script, parameterType); } } }
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) { ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters); GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); String sql; if (configuration.isShrinkWhitespacesInSql()) { sql = parser.parse(removeExtraWhitespaces(originalSql)); } else { // 走的这个 sql = parser.parse(originalSql); } return new StaticSqlSource(configuration, sql, handler.getParameterMappings()); }
复制代码


下面是对 SQL 语句进行了解析,最终的效果是遍历了我们示例 SQL 中的 id 和 name,对其找到对应的 TypeHandler


细节部分没怎么看到,暂时跳过


public class GenericTokenParser {  public String parse(String text) {    if (text == null || text.isEmpty()) {      return "";    }    // search open token    int start = text.indexOf(openToken);    if (start == -1) {      return text;    }    char[] src = text.toCharArray();    int offset = 0;    final StringBuilder builder = new StringBuilder();    StringBuilder expression = null;    do {      if (start > 0 && src[start - 1] == '\\') {        // this open token is escaped. remove the backslash and continue.        builder.append(src, offset, start - offset - 1).append(openToken);        offset = start + openToken.length();      } else {        // found open token. let's search close token.        if (expression == null) {          expression = new StringBuilder();        } else {          expression.setLength(0);        }        builder.append(src, offset, start - offset);        offset = start + openToken.length();        int end = text.indexOf(closeToken, offset);        while (end > -1) {          if (end > offset && src[end - 1] == '\\') {            // this close token is escaped. remove the backslash and continue.            expression.append(src, offset, end - offset - 1).append(closeToken);            offset = end + closeToken.length();            end = text.indexOf(closeToken, offset);          } else {            expression.append(src, offset, end - offset);            break;          }        }        if (end == -1) {          // close token was not found.          builder.append(src, start, src.length - start);          offset = src.length;        } else {          builder.append(handler.handleToken(expression.toString()));          offset = end + closeToken.length();        }      }      start = text.indexOf(openToken, offset);    } while (start > -1);    if (offset < src.length) {      builder.append(src, offset, src.length - offset);    }    return builder.toString();  }}
复制代码


下面就是根据对应的 JavaType 和 jdbcType,获取对应的 TypeHandler


    public String handleToken(String content) {      parameterMappings.add(buildParameterMapping(content));      return "?";    }
// 在其中设置JavaType和jdbcType private ParameterMapping buildParameterMapping(String content) { Map<String, String> propertiesMap = parseParameterMapping(content); String property = propertiesMap.get("property"); Class<?> propertyType; if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params propertyType = metaParameters.getGetterType(property); } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) { propertyType = parameterType; } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) { propertyType = java.sql.ResultSet.class; } else if (property == null || Map.class.isAssignableFrom(parameterType)) { propertyType = Object.class; } else { MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory()); if (metaClass.hasGetter(property)) { propertyType = metaClass.getGetterType(property); } else { propertyType = Object.class; } } ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType); Class<?> javaType = propertyType; String typeHandlerAlias = null; for (Map.Entry<String, String> entry : propertiesMap.entrySet()) { String name = entry.getKey(); String value = entry.getValue(); if ("javaType".equals(name)) { javaType = resolveClass(value); builder.javaType(javaType); } else if ("jdbcType".equals(name)) { builder.jdbcType(resolveJdbcType(value)); } else if ("mode".equals(name)) { builder.mode(resolveParameterMode(value)); } else if ("numericScale".equals(name)) { builder.numericScale(Integer.valueOf(value)); } else if ("resultMap".equals(name)) { builder.resultMapId(value); } else if ("typeHandler".equals(name)) { typeHandlerAlias = value; } else if ("jdbcTypeName".equals(name)) { builder.jdbcTypeName(value); } else if ("property".equals(name)) { // Do Nothing } else if ("expression".equals(name)) { throw new BuilderException("Expression based parameters are not supported yet"); } else { throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}. Valid properties are " + PARAMETER_PROPERTIES); } } if (typeHandlerAlias != null) { builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias)); } return builder.build(); }
public ParameterMapping build() { resolveTypeHandler(); validate(); return parameterMapping; }
private void resolveTypeHandler() { if (parameterMapping.typeHandler == null && parameterMapping.javaType != null) { Configuration configuration = parameterMapping.configuration; TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); // 从Map中获取对应的TypeHandler,以javaType和jdbcType为标识 parameterMapping.typeHandler = typeHandlerRegistry.getTypeHandler(parameterMapping.javaType, parameterMapping.jdbcType); } }
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) { if (ParamMap.class.equals(type)) { return null; } // 根据javaType获取Map Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type); TypeHandler<?> handler = null; if (jdbcHandlerMap != null) { // 根据jdbcType获取 handler = jdbcHandlerMap.get(jdbcType); if (handler == null) { handler = jdbcHandlerMap.get(null); } if (handler == null) { // #591 handler = pickSoleHandler(jdbcHandlerMap); } } // type drives generics here return (TypeHandler<T>) handler; }
复制代码

总结

在本篇文章中,我们初步探索了 Mapper 初始化的一部分,着重解析了 TypeHandler 相关的注册和 MapperProxy 生成时 TypeHandler 对应的获取


核心流程如下:


  • 1.注册 TypeHandler:放入 Map 中

  • 2.MapperProxy 生成,字段的 TypeHandler 是在 SQLSource 中,生成 SQLSource

  • 3.解析 SQL 语句,得到对应参数和返回字段的 TypeHandler:这部分没细研究,但后面如果遇到问题,可以回来参考参考

  • 4.根据 JavaType 和 jdbcType 得到对应的 TypeHandler


这部分还有很多的细节没来得及去细看,但如果遇到问题,还是能提供方向性的指导

发布于: 刚刚阅读数: 2
用户头像

关注

还未添加个人签名 2018.09.09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
MyBatis3源码解析(7)TypeHandler注册与获取