关于java:mybatis源码分析一-配置文件的解析过程

39次阅读

共计 15418 个字符,预计需要花费 39 分钟才能阅读完成。

mybatis 的源码有人曾经做过一个中文的正文,代码 github 上有 mybatis 中文正文源码

mybatis 框架有两个十分重要的 xml 文件,一个是 mybatis 的 config 文件,一个就是 mapper 文件,mybatis 会依据 config 的 xml 文件去生成一个 Configuration 类,在这个过程中也会依据配置的 mapper 文件生成 MappedStatement,这篇博客探索的就是这样一个过程,往下看

如果单单应用 mybatis,咱们的做法是导包,配置,而后如下

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}

所以从 SqlSessionFactoryBuilder().build 说起,点击进入 build 办法, 新建了一个 XMLConfigBuilder,而后 build(parser.parse()),

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());

先看 parser.parse() 办法,这办法中将之前的 mybatis 的 xml 文件进行解析,生成了 Configration 类返回,

// 解析配置
  private void parseConfiguration(XNode root) {
    try {
      // 分步骤解析
      //issue #117 read properties first
      //1.properties
      propertiesElement(root.evalNode("properties"));
      //2. 类型别名
      typeAliasesElement(root.evalNode("typeAliases"));
      //3. 插件
      pluginElement(root.evalNode("plugins"));
      //4. 对象工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      //5. 对象包装工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6. 设置
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      //7. 环境
      environmentsElement(root.evalNode("environments"));
      //8.databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //9. 类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      //10. 映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause:" + e, e);
    }
  }

仔细分析这几行代码,首先看第一个 properties 解析

//1.properties
  //<properties resource="org/mybatis/example/config.properties">
  //    <property name="username" value="dev_user"/>
  //    <property name="password" value="F2Fa3!33TYyg"/>
  //</properties>
  private void propertiesElement(XNode context) throws Exception {if (context != null) {
      // 如果在这些中央, 属性多于一个的话,MyBatis 依照如下的程序加载它们:

      //1. 在 properties 元素体内指定的属性首先被读取。//2. 从类门路下资源或 properties 元素的 url 属性中加载的属性第二被读取, 它会笼罩曾经存在的齐全一样的属性。//3. 作为办法参数传递的属性最初被读取, 它也会笼罩任一曾经存在的齐全一样的属性, 这些属性可能是从 properties 元素体内和资源 /url 属性中加载的。// 传入形式是调用构造函数时传入,public XMLConfigBuilder(Reader reader, String environment, Properties props)

      //1.XNode.getChildrenAsProperties 函数不便失去孩子所有 Properties
      Properties defaults = context.getChildrenAsProperties();
      //2. 而后查找 resource 或者 url, 退出后面的 Properties
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) {throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      if (resource != null) {defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {defaults.putAll(Resources.getUrlAsProperties(url));
      }
      //3.Variables 也全副退出 Properties
      Properties vars = configuration.getVariables();
      if (vars != null) {defaults.putAll(vars);
      }
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }

具体的 xml 解析过程就没必要具体看了,最初能够看到所有的 properties 都被存入了 Configuration 的 variables 变量中,

而后往下看类型别名的解析,对于别名,首先 Configuration 类中定义了一个 TypeAliasRegistry

// 类型别名注册机
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

这个 TypeAliasRegistry 中有一个 Map 寄存了别名和别名的类

private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

所以 typeAliasesElement(root.evalNode(“typeAliases”)) 这个办法中的操作就是解析出别名放入这个 map 中,定义别名的两种形式具体能够看官网。

再往下看,插件的解析

//3. 插件
  //MyBatis 容许你在某一点拦挡已映射语句执行的调用。默认状况下,MyBatis 容许应用插件来拦挡办法调用
//<plugins>
//  <plugin interceptor="org.mybatis.example.ExamplePlugin">
//    <property name="someProperty" value="100"/>
//  </plugin>
//</plugins>  
  private void pluginElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        interceptorInstance.setProperties(properties);
        // 调用 InterceptorChain.addInterceptor
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

插件尽管比较复杂,然而解析的局部却很简略,次要是 resolveClass 办法

// 依据别名解析 Class, 其实是去查看 类型别名注册 / 事务管理器别名
  protected Class<?> resolveClass(String alias) {if (alias == null) {return null;}
    try {return resolveAlias(alias);
    } catch (Exception e) {throw new BuilderException("Error resolving class. Cause:" + e, e);
    }
  }

这个别名的解析过程其实就是去之前说的那个别名的 map 中查问,有的话就返回,没的话就间接转成 Class,所以 mybatis 外面很多配置属性 type=”xxx” 的,例如 datasource 的 type=”POOLED”, 这个 POOLED 其实就是类型的别名。最初获取到 Class 之后 newInstance 创立一个对象,放入 Interceptor 拦截器链中,这个拦截器链和 SpringMvc 相似,其实就是一个拦截器链对象 InterceptorChain 外面放了一个 List 汇合,调用的时候 for 循环顺次调用, 去看看代码

protected final InterceptorChain interceptorChain = new InterceptorChain();

Configuration 类中定义了这样一个过滤器链,前面某个中央必定会执行 pluginAll 办法

public Object pluginAll(Object target) {
    // 循环调用每个 Interceptor.plugin 办法
    for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);
    }
    return target;
  }

这中央用过插件就很相熟了,plugin 办法中咱们根本都这样写,而这个办法就是创立了一个代理对象

return Plugin.wrap(target, this);
public static Object wrap(Object target, Interceptor interceptor) {
    // 获得签名 Map
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    // 获得要扭转行为的类 (ParameterHandler|ResultSetHandler|StatementHandler|Executor)
    Class<?> type = target.getClass();
    // 获得接口
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    // 产生代理
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

先看获取签名 getSignatureMap 这个办法

// 获得签名 Map
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    // 取 Intercepts 注解,例子可参见 ExamplePlugin.java
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    // 必须得有 Intercepts 注解,没有报错
    if (interceptsAnnotation == null) {throw new PluginException("No @Intercepts annotation was found in interceptor" + interceptor.getClass().getName());      
    }
    //value 是数组型,Signature 的数组
    Signature[] sigs = interceptsAnnotation.value();
    // 每个 class 里有多个 Method 须要被拦挡, 所以这么定义
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
    for (Signature sig : sigs) {Set<Method> methods = signatureMap.get(sig.type());
      if (methods == null) {methods = new HashSet<Method>();
        signatureMap.put(sig.type(), methods);
      }
      try {Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {throw new PluginException("Could not find method on" + sig.type() + "named" + sig.method() + ". Cause:" + e, e);
      }
    }
    return signatureMap;
  }

这里从咱们正文在拦截器插件的类注解 Intercepts 上获取 Signature 数组,循环数组,解析后果放入 signatureMap 中,signatureMap 是一个 Class 为键,Method 的 Set 列表为 Value 的 Map, 说白了这个解析后果就是一个对象中须要拦挡的哪几个办法。

再回头往下看,

很相熟的动静代理办法,因为传入的 InvocationHandler 也是 Plugin 这个类,所以 invoke 办法也在这个类中

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 看看如何拦挡
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      // 看哪些办法须要拦挡
      if (methods != null && methods.contains(method)) {
        // 调用 Interceptor.intercept,也即插入了咱们本人的逻辑
        return interceptor.intercept(new Invocation(target, method, args));
      }
      // 最初还是执行原来逻辑
      return method.invoke(target, args);
    } catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);
    }
  }

剖析一下这段代码,这就是从方才解析的须要拦挡的办法的 Map 中取出该类的拦挡列表办法,看看是不是包含以后的办法,是的话就执行 intercept 也就是咱们写的那些拦挡办法。再最初执行办法自身的逻辑。规范老套娃!

再回到 XMLConfigBuilder 中,接着往下

//4. 对象工厂
  objectFactoryElement(root.evalNode("objectFactory"));

这个就是解析出一个类办法放到 Configuration 的 objectFactory 中,笼罩它默认的对象工厂

而后是解析对象包装工厂,反射器工厂,settings,environments 等等原理和之前都差不多,所以跳过,

看重点最初一个 mapperElement 办法

//10. 映射器
//    10.1 应用类门路
//    <mappers>
//      <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
//      <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
//      <mapper resource="org/mybatis/builder/PostMapper.xml"/>
//    </mappers>
//
//    10.2 应用相对 url 门路
//    <mappers>
//      <mapper url="file:///var/mappers/AuthorMapper.xml"/>
//      <mapper url="file:///var/mappers/BlogMapper.xml"/>
//      <mapper url="file:///var/mappers/PostMapper.xml"/>
//    </mappers>
//
//    10.3 应用 java 类名
//    <mappers>
//      <mapper class="org.mybatis.builder.AuthorMapper"/>
//      <mapper class="org.mybatis.builder.BlogMapper"/>
//      <mapper class="org.mybatis.builder.PostMapper"/>
//    </mappers>
//
//    10.4 主动扫描包下所有映射器
//    <mappers>
//      <package name="org.mybatis.builder"/>
//    </mappers>
  private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {
          //10.4 主动扫描包下所有映射器
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            //10.1 应用类门路
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 映射器比较复杂,调用 XMLMapperBuilder
            // 留神在 for 循环里每个 mapper 都从新 new 一个 XMLMapperBuilder,来解析
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {
            //10.2 应用相对 url 门路
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            // 映射器比较复杂,调用 XMLMapperBuilder
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {
            //10.3 应用 java 类名
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            // 间接把这个映射退出配置
            configuration.addMapper(mapperInterface);
          } else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

间接看 package 的解析,其实 <mapper xxx/> 这种模式解析过程也相似,要害都是调用了 configuration.addMapper 这个办法,所以间接看这个办法,这个办法在 Configuration 类的 mapperRegistry 中

// 看一下如何增加一个映射
  public <T> void addMapper(Class<T> type) {
    //mapper 必须是接口!才会增加
    if (type.isInterface()) {if (hasMapper(type)) {
        // 如果反复增加了,报错
        throw new BindingException("Type" + type + "is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {knownMappers.put(type, new MapperProxyFactory<T>(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 {
        // 如果加载过程中出现异常须要再将这个 mapper 从 mybatis 中删除, 这种形式比拟俊俏吧,难道是不得已而为之?if (!loadCompleted) {knownMappers.remove(type);
        }
      }
    }
  }

重点就是 new MapperProxyFactory<T>(type),这里将存入一个 Mapper 的代理工厂类。

再往下看,创立了一个 MapperAnnotationBuilder,而后再看 parse 办法。

public void parse() {String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {parseStatement(method);
          }
        } catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();}

首先 configuration.isResourceLoaded 会判断是否加载了 mapper 的 xml,很显然,如果用 package 形式的,走到这一步,就只是找到了接口,将代理工厂存入 map 中,并没有去加载 xml,所以会 loadXmlResource()

private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {String xmlResource = type.getName().replace('.', '/') + ".xml";
      InputStream inputStream = null;
      try {inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {// ignore, resource is not required}
      if (inputStream != null) {XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();}
    }
  }

这里将接口全面的. 替换成了 /,所以如果接口是 a.test, 那 xml 就肯定得是 a /test.xml,而后会新建一个 XMLMapperBuilder,这里能够回去 mapperElement 办法中看 <mapper resource=”xxx”/> 的解析,也是通过 XMLMapperBuilder,所以这些解析形式其实大同小异,而后再看 XMLMapperBuilder 的 parse 办法

// 解析
  public void parse() {
    // 如果没有加载过再加载,避免反复加载
    if (!configuration.isResourceLoaded(resource)) {
      // 配置 mapper
      configurationElement(parser.evalNode("/mapper"));
      // 标记一下,曾经加载过了
      configuration.addLoadedResource(resource);
      // 绑定映射器到 namespace
      bindMapperForNamespace();}

    // 还有没解析完的东东这里接着解析?parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();}

先看 configurationElement 办法

// 配置 mapper 元素
//    <mapper namespace="org.mybatis.example.BlogMapper">
//      <select id="selectBlog" parameterType="int" resultType="Blog">
//        select * from Blog where id = #{id}
//      </select>
//    </mapper>
  private void configurationElement(XNode context) {
    try {
      //1. 配置 namespace
      String namespace = context.getStringAttribute("namespace");
      if (namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      //2. 配置 cache-ref
      cacheRefElement(context.evalNode("cache-ref"));
      //3. 配置 cache
      cacheElement(context.evalNode("cache"));
      //4. 配置 parameterMap(曾经废除, 老式格调的参数映射)
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //5. 配置 resultMap(高级性能)
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //6. 配置 sql(定义可重用的 SQL 代码段)
      sqlElement(context.evalNodes("/mapper/sql"));
      //7. 配置 select|insert|update|delete TODO
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. Cause:" + e, e);
    }
  }

首先看 cache-ref 的解析

//2. 配置 cache-ref, 在这样的 状况下你能够应用 cache-ref 元素来援用另外一个缓存。//<cache-ref namespace="com.someone.application.data.SomeMapper"/>
  private void cacheRefElement(XNode context) {if (context != null) {
      // 减少 cache-ref
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }

先往 configuration 中寄存 cache-ref 的 map 中增加以后解析的 cache-ref 的 namespace,而后创立一个 cache-ref 解析器解析,

public Cache resolveCacheRef() {
      // 反调 MapperBuilderAssistant 解析
    return assistant.useCacheRef(cacheRefNamespace);
  }
public Cache useCacheRef(String namespace) {if (namespace == null) {throw new BuilderException("cache-ref element requires a namespace attribute.");
    }
    try {
      unresolvedCacheRef = true;
      Cache cache = configuration.getCache(namespace);
      if (cache == null) {throw new IncompleteElementException("No cache for namespace'" + namespace + "'could be found.");
      }
      currentCache = cache;
      unresolvedCacheRef = false;
      return cache;
    } catch (IllegalArgumentException e) {throw new IncompleteElementException("No cache for namespace'" + namespace + "'could be found.", e);
    }
  }

这里调用的是 MapperBuilderAssistant 这个助手的办法,而在这个助手类中,逻辑是这样的,去 configuration 的 cache 的 map 中获取 cache,如果 cache 曾经创立了,就返回。如果还没有创立,那么就抛出一个 IncompleteElementException 异样,异样被内部捕捉,将以后 cache-ref 的解析器放入一个用来寄存未实现 cache-ref 解析的列表中。

而后接下来解析 cache,

//3. 配置 cache
  cacheElement(context.evalNode("cache"));

办法中仍旧是调用助手类的办法

// 调用 builderAssistant.useNewCache
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);

接下来的几个 resultmap,sql 等解析的过程根本相似。

以后解析实现之后,再往下看,会去解析之前未齐全解析的各类对象,进入第一个办法

private void parsePendingResultMaps() {Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
    synchronized (incompleteResultMaps) {Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
      while (iter.hasNext()) {
        try {iter.next().resolve();
          iter.remove();} catch (IncompleteElementException e) {// ResultMap is still missing a resource...}
      }
    }
  }

之前存入 map 中的未齐全解析的解析器取出循环调用之前同样的办法,而在此刻,之前须要期待创立的对象当初都曾经创立实现,所以能够实现创立 (我想了一下,这外面如同没有 a 须要 b,b 须要 c 的这种,被依赖的如同都是没有须要依赖的)。

再回到 MapperAnnotationBuilder 中,接下去是办法的注解解析,和之前 xml 的区别就是解析的办法,跳过。

最终 SqlSessionFactoryBuilder 会执行到这行代码,生成一个 DefaultSqlSessionFactory

public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);
  }

到此解析完结。

关注公众号:java 宝典

正文完
 0