Mybatis初始化源码解析

Mybatis初始化源码解析

Mybatis的初始化,主要做一件事情,构建DefaultSqlSessionFactory。那么要构建它,其实只需要将Configuration对象构建起来即可,事实上整个初始化的过程就是在解析Mybatis的配置,装换成Configuration对象。然而Configuration对象比较复杂,有差不多50个属性,虽然不需要全部搞清楚,但我们必须知道一些核心属性含义,以及是如何构建出来的。

下图为Mybatis配置和Configuation属性对照关系,先有个映像,看源码的过程中少晕一下。

在这里插入图片描述

Mybatis主配置文件的解析

通常,我们使用SqlSessionFactoryBuilder来构建一个SqlSessionFactory出来,而SqlSessionFactoryBuilder的build方法需要传入一个mybaits的主配置文件,比如如下初始化代码:

//主配置文件的Reader
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
//通过SqlSessionFactoryBuilder来构建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
reader.close();

SqlSessionFactoryBuilder的build方法逻辑:

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    ...
    	//通过XMLConfigBuilder来解析配置,生成Configuration对象
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      //调用重载的build方法,构建SqlSessionFactory
      return build(parser.parse());
    ...
  }
  
  //通过传入的Configuration对象,直接new一个DefaultSqlSessionFactory返回。
 public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

XMLConfigBuilder的构造方法中,会new一个Configuration出来,

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
  //new一个Configuration,它的构造方法中会注册常用的别名(typeAlias)
  super(new Configuration());
  ErrorContext.instance().resource("SQL Mapper Configuration");
  //设置setVariables属性,为传入的props 
  this.configuration.setVariables(props);
  this.parsed = false;
  this.environment = environment;
  this.parser = parser;
}

整个解析位于XMLConfigBuilder.parse方法中:

public Configuration parse() {
	//只能解析一次。
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  //解析主配置(主配置根元素是configuration节点)
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

继续parseConfiguration方法,它一步一步完成了整个配置文件的解析:

private void parseConfiguration(XNode root) {
  try {
  	//解析properties的配置
    //issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    //解析settings的配置
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    //解析typeAliases的配置
    typeAliasesElement(root.evalNode("typeAliases"));
    //解析plugins的配置
    pluginElement(root.evalNode("plugins"));
    //解析plugins的配置
    objectFactoryElement(root.evalNode("objectFactory"));
    //解析plugins的配置
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    //解析plugins的配置
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    //设置settings配置
    settingsElement(settings);
    //解析environments的配置
    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    //解析databaseIdProvider的配置
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    //解析typeHandlers的配置
    typeHandlerElement(root.evalNode("typeHandlers"));
    //解析mappers的配置
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

除了mappers的配置解析之外,其他的配置解析都比较简单,我们挑几个看看。

  • properties配置解析

先观察下properties配置的xml结构:

<properties resource="" url="">
    <property name="" value=""/>
</properties>

解析properties的配置逻辑:

private void propertiesElement(XNode context) throws Exception {
  if (context != null) { //配置了propertiess
  	//获取所有的property配置
    Properties defaults = context.getChildrenAsProperties();
    //获取resource属性配置
    String resource = context.getStringAttribute("resource");
    //获取url属性配置
    String url = context.getStringAttribute("url");
    if (resource != null && url != null) {
    	//不能同时配置resource和url
      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) {
    	//把resource配置的文件加进来,因为同名的key会产生覆盖,所以resource优先级高于直接property配置
      defaults.putAll(Resources.getResourceAsProperties(resource));
    } else if (url != null) {
    	//把url配置的文件加进来,因为同名的key会产生覆盖,所以url优先级高于直接property配置
      defaults.putAll(Resources.getUrlAsProperties(url));
    }
    //这个是初始化时,方法参数传入的props
    Properties vars = configuration.getVariables();
    if (vars != null) {
    	//把方法参数传入的props加进来,同理,方法参数的方式优先级最高
      defaults.putAll(vars);
    }
    //组合后的结果设置到parser中
    parser.setVariables(defaults);
    //组合后的结果设置到configuration的variables属性中。
    configuration.setVariables(defaults);
  }
}

可以看出,properties配置解析比较简单直观,唯一注意一点的就是存在优先级覆盖问题,优先级从高到低为:方法传参的方式 高于 resource/url 指定外部配置文件的方式 高于 直接配置的property元素方式。

  • typeAliases的配置解析

先看下xml中的配置结构:

<typeAliases>
    <package name="xxx"/>
    <typeAlias type="xx" alias="xx" />
</typeAliases>

解析typeAliases的配置逻辑:

private void typeAliasesElement(XNode parent) {
  if (parent != null) { 
  	//遍历每个子元素
    for (XNode child : parent.getChildren()) {
    	//如果是package元素,则通过包扫描的方式处理
      if ("package".equals(child.getName())) {
      	//获取包名
        String typeAliasPackage = child.getStringAttribute("name");
        //扫描包并注册。
        configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
      
      //否则就是typeAlias元素,配置单个别名
      } else {
      	//获取别名配置
        String alias = child.getStringAttribute("alias");
        //获取类型配置
        String type = child.getStringAttribute("type");
        try {
        	//获取配置type的Class对象
          Class<?> clazz = Resources.classForName(type);
          if (alias == null) {
          	//如果没有配置了别名,则看类上是否Alias注解,有则通过该注解的值作为别名注册到typeAliasRegistry中,如果没有则通过类的简单名作为别名注册。
            typeAliasRegistry.registerAlias(clazz);
          } else {
          	//有配置别名的话,直接使用配置的别名注册。
            typeAliasRegistry.registerAlias(alias, clazz);
          }
        } catch (ClassNotFoundException e) {
          throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
        }
      }
    }
  }
}

来看下扫描包的实现,这个在其他地方也会用到(mapper接口的包扫描),逻辑位于TypeAliasRegistry.registerAliases方法中:

public void registerAliases(String packageName){
  registerAliases(packageName, Object.class);
}

public void registerAliases(String packageName, Class<?> superType){
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
 	//找到扫描包下是superType类型的所有类,这儿传入的是Object.class,那么就是扫描所有类
  resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
  Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
  //遍历扫描出来的类
  for(Class<?> type : typeSet){
    // Ignore inner classes and interfaces (including package-info.java)
    // Skip also inner classes. See issue #6
    //进行过滤处理后,注册别名(别名的处理和单个中的没有配置别名的处理一致)
    if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
      registerAlias(type);
    }
  }
}
  • environments的配置解析

同样,先看下xml配置样子

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        
        <dataSource type="UNPOOLED">
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://xxxx"/>
            <property name="username" value="xxx"/>
            <property name="password" value="xxx"/>
        </dataSource>
        
    </environment>
</environments>

解析environments的配置逻辑:

private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
    	//没有指定环境的话,使用default指定的默认环境
      environment = context.getStringAttribute("default");
    }
    //遍历解析每个环境配置
    for (XNode child : context.getChildren()) {
    	//获取环境ID
      String id = child.getStringAttribute("id");
      //如果当前准备解析环境ID,是指定的环境,才会真正的进入解析.
      if (isSpecifiedEnvironment(id)) {
      	//解析transactionManager配置,根据配置的类型反射实例化TransactionFactory对象.
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        //解析dataSource配置,根据配置的类型反射实例化DataSourceFactory对象.
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        //获取DataSource
        DataSource dataSource = dsFactory.getDataSource();
        //new一个Environment.Builder。
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        //将构建出的Environment设置到configuration的environment属性中。
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}

在整个解析过程中,多次使用类似的代码逻辑,即根据配置的类型反射实例化一个对象出来,比如上面的TransactionFactory、DataSourceFactory:

private TransactionFactory transactionManagerElement(XNode context) throws Exception {
  if (context != null) {
  	//获取指定类型
    String type = context.getStringAttribute("type");
    Properties props = context.getChildrenAsProperties();
    //反射实例化对象,可以看到使用的是默认的构造方法,因此要扩展的自定义的一些对象时,需要提供默认的无参构造方法,否则会报错。
    TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
    //大部分的可指定类型的接口,都会定义一个setProperties方法,通过这儿回调来设置自定义的属性。
    factory.setProperties(props);
    return factory;
  }
}

Mapper映射文件的解析

Mapper映射文件的元素虽然不多,核心的主要是cache、resultMap、sql、以及select|update|delete|insert。但解析还是相对来说比较繁琐的。

Mapper映射文件解析的入口:

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
    	//包扫描方式解析Mapper
      if ("package".equals(child.getName())) {
      	//获取包扫描路径配置
        String mapperPackage = child.getStringAttribute("name");
        //进行包扫描和解析。
        configuration.addMappers(mapperPackage);
        
      //单个Mapper解析。
      } else {
      	//单个Mapper配置的3中方式,resource、url、class。
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        
        //只配置了resource方式
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);
          //new一个XMLMapperBuilder来具体的解析
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          mapperParser.parse();
          
        //只配置了url方式,解析与resource一致。
        } 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();
          
        //只配置了class方式,解析逻辑会复杂一些。
        } else if (resource == null && url == null && mapperClass != null) {
          //加载mapper接口类
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          //进行Mapper Class类型的解析。
          configuration.addMapper(mapperInterface);
          
        //其他情况,抛出异常。
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

从简单入手,先看下XMLMapperBuilder来解析单个mapper.xml文件的流程:

public void parse() {
	//每个mapper.class 和 mapper.xml 只能被解析一次。
  if (!configuration.isResourceLoaded(resource)) {
  	//解析mapper根元素
    configurationElement(parser.evalNode("/mapper"));
    //解析完成后放入到loadedResources中,防止重复解析
    configuration.addLoadedResource(resource);
    //通过namespace看有没有对应的mapper接口类,如果有的话,注册到mapperRegistry中。
    bindMapperForNamespace();
  }
  ....
}

解析的主体代码:

private void configurationElement(XNode context) {
  try {
  	//获取namespace配置,该配置是必须的。
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    //builderAssistant 构建助手,使用它来构建如MappedStatement、cache、ResultMap等复杂的对象。
    builderAssistant.setCurrentNamespace(namespace);
    //解析cache-ref配置。
    cacheRefElement(context.evalNode("cache-ref"));
    //解析cache配置。
    cacheElement(context.evalNode("cache"));
    //解析parameterMap配置,以废弃,可忽略。
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    //解析resultMap配置。
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    //解析sql配置。
    sqlElement(context.evalNodes("/mapper/sql"));
    //解析select|insert|update|delete配置。
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
  }
}

不全部展开全部的解析,关注两个重要的cache的解析和【select|insert|update|delete】这些语句的解析。

  • cache配置解析

    首先看下xml中cache的配置:

    <cache eviction="FIFO" size="512" readOnly="true"/>
    

    具体的解析逻辑:

    private void cacheElement(XNode context) throws Exception {
      if (context != null) {
      	//获取缓存类型,默认为PERPETUAL
        String type = context.getStringAttribute("type", "PERPETUAL");
        Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        //获取逐出策略,默认为LRU。
        String eviction = context.getStringAttribute("eviction", "LRU");
        Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
        Long flushInterval = context.getLongAttribute("flushInterval");
        Integer size = context.getIntAttribute("size");
        boolean readWrite = !context.getBooleanAttribute("readOnly", false);
        boolean blocking = context.getBooleanAttribute("blocking", false);
        Properties props = context.getChildrenAsProperties();
        //通过builderAssistant来构建一个新的缓存对象,传入配置的参数。
        builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
      }
    }
    

    缓存对象的具体构建过程,参考 Mybatis缓存源码详解 的二级缓存源码解析。( https://blog.csdn.net/gruelxsp/article/details/103769381 ),详细的介绍了二级缓存的构建过程,如何通过装饰者模式来完成整个缓存的各种功能职责划分。

  • select|insert|update|delete配置解析

    可以说,这个解析才是最最核心的,真正是配置SQL语句的解析。

    xml配置样子:

    <select id="selectById" resultMap="userMap">
        select * from sys_user where id = #{id}
    </select>
    

    解析逻辑:

    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
      //遍历所有的语句配置
      for (XNode context : list) {
      	//构建XMLStatementBuilder来解析做具体的语句解析工作。
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
        	//执行语句解析。
          statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
          configuration.addIncompleteStatement(statementParser);
        }
      }
    }
    

    XMLStatementBuilder的parseStatementNode方法,执行真正的语句解析:

    public void parseStatementNode() {
    	//获取配置的信息,并做对应的处理。不单独一个一个解释。
      String id = context.getStringAttribute("id");
      String databaseId = context.getStringAttribute("databaseId");
    	...
      Integer fetchSize = context.getIntAttribute("fetchSize");
      Integer timeout = context.getIntAttribute("timeout");
      String parameterMap = context.getStringAttribute("parameterMap");
      String parameterType = context.getStringAttribute("parameterType");
      Class<?> parameterTypeClass = resolveClass(parameterType);
      String resultMap = context.getStringAttribute("resultMap");
      String resultType = context.getStringAttribute("resultType");
      String lang = context.getStringAttribute("lang");
      LanguageDriver langDriver = getLanguageDriver(lang);
    
      Class<?> resultTypeClass = resolveClass(resultType);
      String resultSetType = context.getStringAttribute("resultSetType");
      StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
      ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    
      String nodeName = context.getNode().getNodeName();
      SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
      boolean useCache = context.getBooleanAttribute("useCache", isSelect);
      boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    	
    	//这儿是对应用了sql语句片段的解析。
      // Include Fragments before parsing
      XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
      includeParser.applyIncludes(context.getNode());
    
      // Parse selectKey after includes and remove them.
      processSelectKeyNodes(id, parameterTypeClass, langDriver);
      
      //解析出具体的SQL语句,封装为SqlSource
      // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
      SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
      String resultSets = context.getStringAttribute("resultSets");
      String keyProperty = context.getStringAttribute("keyProperty");
      String keyColumn = context.getStringAttribute("keyColumn");
      
      KeyGenerator keyGenerator;
      String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
      keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
      if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
      } else {
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
            configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
            ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
      }
    
    	//将解析出来的配置信息,传递给builderAssistant,由它进行最后的mapppedStatement对象的构建和注册。
      builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
          fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
          resultSetTypeEnum, flushCache, useCache, resultOrdered, 
          keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }
    

    builderAssistant.addMappedStatement方法:

    public MappedStatement addMappedStatement(
        String id,
        SqlSource sqlSource,
        StatementType statementType,
        SqlCommandType sqlCommandType,
        Integer fetchSize,
        Integer timeout,
        String parameterMap,
        Class<?> parameterType,
        String resultMap,
        Class<?> resultType,
        ResultSetType resultSetType,
        boolean flushCache,
        boolean useCache,
        boolean resultOrdered,
        KeyGenerator keyGenerator,
        String keyProperty,
        String keyColumn,
        String databaseId,
        LanguageDriver lang,
        String resultSets) {
    
      if (unresolvedCacheRef) {
        throw new IncompleteElementException("Cache-ref not yet resolved");
      }
    	//这儿对id扩展,加上namespace,形成全局唯一的id。
      id = applyCurrentNamespace(id, false);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    	
    	//创建MappedStatement.Builder对象,通过它来构建MappedStatement
      MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
          .resource(resource)
          .fetchSize(fetchSize)
          .timeout(timeout)
          .statementType(statementType)
          .keyGenerator(keyGenerator)
          .keyProperty(keyProperty)
          .keyColumn(keyColumn)
          .databaseId(databaseId)
          .lang(lang)
          .resultOrdered(resultOrdered)
          .resultSets(resultSets)
          .resultMaps(getStatementResultMaps(resultMap, resultType, id))
          .resultSetType(resultSetType)
          .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
          .useCache(valueOrDefault(useCache, isSelect))
          .cache(currentCache);
    
      ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
      if (statementParameterMap != null) {
        statementBuilder.parameterMap(statementParameterMap);
      }
    	
    	//构建MappedStatement。
      MappedStatement statement = statementBuilder.build();
      //注册到configuration的mappedStatements中。注意,key除了为带namespace的全名称,还会注册一个简单的名称(方法名),不过应为方法名会冲突,所以在get存在冲突的方法名时,会抛出异常.
      configuration.addMappedStatement(statement);
      return statement;
    }
    

    以上是单个的mapper.xml整个解析流程。

    那么单个的mapper接口类是怎么解析的呢?从XMLConfigBuilder.mapperElement方法中的configuration.addMapper(mapperInterface);这行代码为入口分析:

    public <T> void addMapper(Class<T> type) {
      mapperRegistry.addMapper(type);
    }
    

    它委托给mapperRegistry来处理,MapperRegistry#addMapper方法逻辑:

    public <T> void addMapper(Class<T> type) {
    	//只会care接口类型。
      if (type.isInterface()) {
      	//是否已经注册过了
        if (hasMapper(type)) {
          throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
        	//不管解析有没有问题,先注册上去。注意注册的是MapperProxyFactory,这个在执行中会用到。
          knownMappers.put(type, new MapperProxyFactory<T>(type));
          
          //对于Mapper接口的解析的话,是通过MapperAnnotationBuilder来完成具体的解析的
          MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
          parser.parse();
          loadCompleted = true;
        } finally {
        	//如果没有正常解析完,把前面注册的mapper干掉。
          if (!loadCompleted) {
            knownMappers.remove(type);
          }
        }
      }
    }
    

    具体的解析逻辑MapperAnnotationBuilder#parse方法:

    public void parse() {
      String resource = type.toString();
      //不能重复解析
      if (!configuration.isResourceLoaded(resource)) {
      	//加载Mapper接口类对应的xml配置,这里面就会使用前面介绍的XMLMapperBuilder来完成整个mapper.xml文件的解析,mapper.xml的名称需要与接口类以及位置需要和接口类放在一样的classpath下,其原因就是在定位xml配置的时候,是直接通过mappeer接口类全限定名拼上.xml去找的。
        loadXmlResource();
        //放到loadedResources中。
        configuration.addLoadedResource(resource);
        
        assistant.setCurrentNamespace(type.getName());
        
        //解析接口上@CacheNamespace注解配置的cache。
        parseCache();
        
        //解析接口上@CacheNamespaceRef注解配置的cache。
        parseCacheRef();
        
        //对接口中的每个方法,进行解析。
        Method[] methods = type.getMethods();
        for (Method method : methods) {
    			...
            if (!method.isBridge()) {
            	//解析方法(解析上面的语句注解,构建出MappedStatement并注册)
              parseStatement(method);
            }
    			...
      }
    }
    

    上面代码中loadXmlResource方法、parseCache方法的核心逻辑与xml文件中的处理差不多,多出来的关键的方法是parseStatement,它会解析和构建新的MappedStatement。 具体的方法解析逻辑:

    void parseStatement(Method method) {
    	//获取方法的入参
      Class<?> parameterTypeClass = getParameterType(method);
      //获取LanguageDriver,用它来解析sql语句
      LanguageDriver languageDriver = getLanguageDriver(method);
      //解析Sql语句,这里面会找方法上的这些注解
      /**
      	sqlAnnotationTypes.add(Select.class);
        sqlAnnotationTypes.add(Insert.class);
        sqlAnnotationTypes.add(Update.class);
        sqlAnnotationTypes.add(Delete.class);
    
        sqlProviderAnnotationTypes.add(SelectProvider.class);
        sqlProviderAnnotationTypes.add(InsertProvider.class);
        sqlProviderAnnotationTypes.add(UpdateProvider.class);
        sqlProviderAnnotationTypes.add(DeleteProvider.class);
      */
      SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
      if (sqlSource != null) {
      	//获取@Options注解
        Options options = method.getAnnotation(Options.class);
        //mappedStatementId就是类的全限定名,再连上方法名。
        final String mappedStatementId = type.getName() + "." + method.getName();
        
        //后面一堆其他的解析。
        
        Integer fetchSize = null;
        Integer timeout = null;
        StatementType statementType = StatementType.PREPARED;
        ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
        SqlCommandType sqlCommandType = getSqlCommandType(method);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = !isSelect;
        boolean useCache = isSelect;
    
        KeyGenerator keyGenerator;
        String keyProperty = "id";
        String keyColumn = null;
        if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
          // first check for SelectKey annotation - that overrides everything else
          SelectKey selectKey = method.getAnnotation(SelectKey.class);
          if (selectKey != null) {
            keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
            keyProperty = selectKey.keyProperty();
          } else if (options == null) {
            keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          } else {
            keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            keyProperty = options.keyProperty();
            keyColumn = options.keyColumn();
          }
        } else {
          keyGenerator = NoKeyGenerator.INSTANCE;
        }
    
        if (options != null) {
          if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
            flushCache = true;
          } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
            flushCache = false;
          }
          useCache = options.useCache();
          fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
          timeout = options.timeout() > -1 ? options.timeout() : null;
          statementType = options.statementType();
          resultSetType = options.resultSetType();
        }
    
        String resultMapId = null;
        //@ResultMap注解的解析
        ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
        if (resultMapAnnotation != null) {
          String[] resultMaps = resultMapAnnotation.value();
          StringBuilder sb = new StringBuilder();
          for (String resultMap : resultMaps) {
            if (sb.length() > 0) {
              sb.append(",");
            }
            sb.append(resultMap);
          }
          resultMapId = sb.toString();
        } else if (isSelect) {
          resultMapId = parseResultMap(method);
        }
    	
    		//把解析出来的所有信息,传给MapperBuilderAssistant的addMappedStatement方法,进行MappedStatement的构建和注册。
        assistant.addMappedStatement(
            mappedStatementId,
            sqlSource,
            statementType,
            sqlCommandType,
            fetchSize,
            timeout,
            // ParameterMapID
            null,
            parameterTypeClass,
            resultMapId,
            getReturnType(method),
            resultSetType,
            flushCache,
            useCache,
            // TODO gcode issue #577
            false,
            keyGenerator,
            keyProperty,
            keyColumn,
            // DatabaseID
            null,
            languageDriver,
            // ResultSets
            options != null ? nullOrEmpty(options.resultSets()) : null);
      }
    }
    

    到此,就完成了单个Mapper接口类的解析和注册。

    更进一步,包扫描在单个Mapper接口类的解析和注册基础之上,多做了扫描包的工作,再次回XMLConfigBuilder#mapperElement方法中,通过configuration.addMappers(mapperPackage);这条代码进入Configuration#addMappers方法:

    public void addMappers(String packageName) {
      mapperRegistry.addMappers(packageName);
    }
    

    委托给MapperRegistry#addMappers方法:

    public void addMappers(String packageName) {
      addMappers(packageName, Object.class);
    }
    

    最终调用到:

    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();
      //遍历扫到的类,进行单个Mapper类的解析和注册。
      for (Class<?> mapperClass : mapperSet) {
      	//单个Mapper类的解析和注册
        addMapper(mapperClass);
      }
    }
    

    所以,包扫描就是将类扫出来之后,遍历对单个Mapper接口进行解析和注册,而对单个Mapper接口进行解析的过程中又使用了mapper.xml解析。

初始化源码总结

整个解析过程不算很复杂,但是很繁琐,通常不需要了解的这么细致,知道Configuration对象中的一些核心属性,比如environment、各种setting配置、variables、mapperRegistry(重要)、interceptorChain(重要)、mappedStatements(重要)等大概是如何解析的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值