Mybatis之SqlSessionFactoryBean源码初步解析(一)

首先先道歉一下, 我之前并没有研究太深入,然后发现有些东西写错了。然后7月7 号,忙到夜里2点改这篇文章。改完之后太累了就关了电脑。。。然后没保存发布,又重写了一遍。

第二篇的地址

Mybatis 的两个入口一个是SqlSessionFactoryBean, 另外一个是MapperScannerConfigurer。这个可以在spring.xml 配置中找到。SqlSessionFactoryBean实现了InitializingBean接口,InitializingBean接口必须得实现afterpropertiesset()方法,此方法将配置文件中的各种属性列如 mapperlocation 、typealias、plugins等等放入configuration中,configuration就相当于mybatis的大管家, 各种解析封装后的属性都会放到这里。 然后您会看到

else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    

xmlConfigBuilder这个东西是解析xml 的一个类 

 xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
这里就是装配配置文件
if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
        }
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }
继续往下运行的时候会发现
xmlConfigBuilder.parse();
这个就是开始解析xml了 这边是专门解析下图中configLocation 对应的xml
<configuration>
 <!-- 全局的setting配置 根据需要添加  需要二级缓存 延迟加载 就可以配置-->


 <!-- 配置别名 -->
   <typeAliases>
      <!-- 批量扫描别名 -->
       <package name="com.fuchanghai.mybatis.pojo"/>
        <!--<package name="cn.zx.ssm.po"/>  -->
   </typeAliases>
<!-- 使用自动扫描器时,mapper.xml文件如果和mapper.java接口在一个目录则此处不用定义mappers 
   -->
 <mappers>
      <package name="com.fuchanghai.mybatis.mapper" />
   </mappers>  
</configuration>

这个xml 里面的最外层标签是configuration;所以下面最先解析的是configuration

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
有configuration标签的话就开始解析了。然后我们主讲一下mappers 标签。
 mapperElement(root.evalNode("mappers"));

这句话就是解析mappers的子元素了

如果子元素是package 那么调用configuration的addMappers()将包路径扔给configuration.

如果mappers标签的子元素标签不是package 。那么得到他子元素3个属性的值,分别是resource、url、class 。三者只能同时存在一个。同时存在多以一个则报异常。因为configuration.addMappers()方法最终也是调到XMLMapperBuilder.

configuration.addMappers() 里面是调用mapperRegistry.addMappers()。 

 private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          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) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            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.");
          }
        }
      }
    }
  }

我对于mapperRegistry是哪里来的 很感兴趣。于是我看到了下面代码所示

  protected MapperRegistry mapperRegistry = new MapperRegistry(this);
public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }

这里的this 代表的就是Configuration这个类。

进入mapperRegistry.addMappers() 方法会进入下图。 先调用下面代码中最后一个方法, 再调用上一个方法。

public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

  /**
   * @since 3.2.2
   */
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }

这边再道个歉。 先不上图了,本来有图的 我忘了保存。 我就嘴说说吧

这边的mapperSet 呢解析出来是什么格式呢:

[interface com.fuchanghai.mybatis.mapper.DailyMapper, interface com.fuchanghai.mybatis.mapper.EmployeeMapper]

意思就是interface+全路径。 很有意思的是:比如我这样配置的

<package name="com.fuchanghai.mybatis.mapper" />
那么只会解析com.fuchanghai.mybatis.mapper下的。 如果你的xml放在com.fuchanghai.mybatis.mapper.test。这个包下, 那么是不会在mapperSet中的。注意看图下的size.



我们继续看调到addMapper()方法:

 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 {
        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 {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

hasMapper(type)方法调用的是什么呢?看下面代码

public <T> boolean hasMapper(Class<T> type) {
    return knownMappers.containsKey(type);
  }

意思就是看看knownMappers 里面有没有,即是缓存里面存在不存在。knownMappers 这个属性很重要 再之后的代码中会出现正到。

如果没有的话就存到knownMappers 中

这个 newMapperProxyFactory()重中之重 干了什么 呢? 没错动态代理, 掉个胃口,下次再说。

然后要看MapperAnnotationBuild这个方法了。

type传进去只为了再后面拼接一个.java 然后赋值给resource属性

我们主要看parse.parse();

下面代码 中主要的是loadXmlResource(), 和parseStatement(method);


 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();
  }

loadXmlResource()就是解析xml 文件了 最终调用了xmlMapperBuilder。 这里的!configuration.isResourceLoaded("namespace:" + type.getName())也很有深意的。各位试试debug 看看。

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();
      }
    }
  }

这个是运用反射渠道对应的接口类放到大管家里面(configuration):

 Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);

  XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();

这边就是解析mapper标签。继续点进去是这样的:

 private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

 private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }
上图一定会进入
 buildStatementFromContext(List<XNode> list)

这个方法是得到 select|updata等等东西的contant。

然后进

buildStatementFromContext(List<XNode> list, String requiredDatabaseId)
requiredDatabaseId这个配置的是musql 或者oracle 判断是哪个数据库源

statementParser.parseStatementNode();

这个里面进去找找到langDriver.createSqlSource 然后继续找builder.parseScriptNode(), 进去继续找 List<SqlNode> contents = parseDynamicTags(context);。 这个是个递归的方法,然后就是sqlnode staticsqlnode dynamicsqlnode sqlsource之前的封装,不去细说了。


至于为什么 mapper接口 明明是一个接口怎么 能进行动态代理这个问提。 我不想一步步跟了,简单的说一下就是在mapperlocation那边进去找到MapperRegistry。

最后可以看到

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值