Spring整合Mybatis源码剖析(二)-xml文件在哪被扫描解析的(1-加载时机)

学习源码过程中随手记录的笔记,仅供参考,有问题欢迎指出交流
可能比较枯燥,耐点心,但是弄懂了,必能知其然而知其所以然
学习源码建议亲手debug调试

使用的源码版本

​	mybatis版本3.5.3

​	spring版本5.2.0

一直有个疑问我们的sql.xml文件在什么地方被解析的,来吧,一起来一探究竟

本篇不仔细说明xml解析过程(期待下一篇吧),只探究加载时机

说到mybatis那么肯定需要在项目中配置SqlSessionFactory核心类

一般项目可以配置mybatis-config.xml配置文件,也可以不配置,不配置可以使用注解方式

纯注解方式

@Configuration
@MapperScan(basePackages = {"com.cheng.mapper"})
public class MybagisConfig {

   
    private String url = "xxxxx:3306/cheng?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useAffectedRows=true&serverTimezone=Asia/Shanghai";
   
    private String username = "xx";
   
    private String password = "xx";

    private Integer maxActive = 100;
    private Integer initialSize = 30;
    private Integer maxWait = 60000;
    private Integer minIdle = 20;
    private Integer minEvictableIdleTimeMillis = 300000;

    @Bean
    public DataSource dataSource() {
        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl(url);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxWait(maxWait);
        datasource.setMaxActive(maxActive);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        try {
            datasource.setFilters("stat,wall");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return datasource;
    }


    @Bean
    public DataSourceTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean
    public SqlSessionFactory userSqlSessionFactory(DataSource dataSource)
            throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        //分页插件 
        PageInterceptor pageHelper = new PageInterceptor();
        Properties properties = new Properties();
        properties.setProperty("reasonable", "true");
        properties.setProperty("supportMethodsArguments", "true");
        properties.setProperty("returnPageInfo", "check");
        properties.setProperty("params", "count=countSql");
        pageHelper.setProperties(properties);
        //添加插件
        sessionFactory.setPlugins(new Interceptor[]{pageHelper});
        //设置配置文件路径
        //sessionFactory.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
        //配置映射路径
        //sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/cheng/mapper/*.xml"));
        return sessionFactory.getObject();
    }

    @Bean
    public SqlSessionTemplate SqlSessionTemplate( SqlSessionFactory sqlSessionFactory) throws Exception {
        SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
        return sqlSessionTemplate;
    }

}

分析下SqlSessionFactoryBean,该类实现了InitializingBean

public class SqlSessionFactoryBean
    implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {}

那么在bean初始化时属性赋值之后会调用afterPropertiesSet()方法

看下SqlSessionFactoryBean#afterPropertiesSet方法

public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
        "Property 'configuration' and 'configLocation' can not specified with together");

    /**
     * 通过sqlSessionFactoryBuilder来构建我们的sqlSessionFactory
     */
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

进入buildSqlSessionFactory();

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    // 声明一个Configuration对象用于保存mybatis的所有的配置信息
    final Configuration targetConfiguration;

    XMLConfigBuilder xmlConfigBuilder = null;
    // 初始化 configuration 对象,和设置其 `configuration.variables` 属性
    /**
     * 判断当前的SqlSessionFactoryBean是否在配置@Bean的时候 factoryBean.setConfiguration();
     *
     */
    if (this.configuration != null) {
      /**
       * 把配置的SqlSessionFactoryBean配置的configuration 赋值给targetConfiguration
       */
      targetConfiguration = this.configuration;
      if (targetConfiguration.getVariables() == null) {
        targetConfiguration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        targetConfiguration.getVariables().putAll(this.configurationProperties);
      }
    }
    /**
     * 切入点一:对configLocation进行非空判断,由于我们配置了SqlSessionFactoryBean的configLocation属性设置
     *
     * @Bean public SqlSessionFactoryBean sqlSessionFactory( ) throws IOException { SqlSessionFactoryBean factoryBean
     *       =new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource()); factoryBean.setConfigLocation(new
     *       ClassPathResource("mybatis/mybatis-config.xml")); factoryBean.setMapperLocations(new
     *       PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml")); return factoryBean;
     *       }
     */
  
    else if (this.configLocation != null) {
      /**
       * 创建我们xml配置构建器对象,对mybatis/mybatis-config.xml配置文件进行解析 在这里以及把我们的mybaits-config.xml解析出要给document对象
       */
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      /**
       * 因为我们在创建XMLConfigBuilder的时候已经把我们的Configuration对象创建出来了
       */
      targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
      LOGGER.debug(
          () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      targetConfiguration = new Configuration();
      /**
       * 判断configurationProperties不为空,那么就调用targetConfiguration.set方法 把configurationProperties注入到Configuration对象中
       */
      Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }

    /**
     * objectFactory不为空,那么就调用targetConfiguration.set方法 把objectFactory注入到Configuration对象中
     */
    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    /**
     * objectWrapperFactory不为空,那么就调用targetConfiguration.set方法把 ObjectWrapperFactory注入到Configuration对象中
     */
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);

    /**
     * vfs不为空,那么就调用targetConfiguration.set方法把 vfs注入到Configuration对象中
     */
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

    
    if (hasLength(this.typeAliasesPackage)) {

      /**
       * 第一步:扫描我们typeAliasesPackage 包路径下的所有的实体类的class类型 第二步:进行过滤,然后注册到Configuration的别名映射器中
       */
      scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
          .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
          .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
    }

    /**
     * 判断我们SqlSessionFactory是否配置了typeAliases(class类型) 一般typeAliasesPackage配置好了 就没有必要配置typeAliases
     * 注册到Configuration的别名映射器中
     */
    if (!isEmpty(this.typeAliases)) {
      Stream.of(this.typeAliases).forEach(typeAlias -> {
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      });
    }

    /**
     * 把我们自定义的插件注册到我们的mybatis的配置类上 系统默认的插件 Executor (update, query, flushStatements, commit, rollback, getTransaction,
     * close, isClosed) ParameterHandler (getParameterObject, setParameters) ResultSetHandler (handleResultSets,
     * handleOutputParameters) StatementHandler (prepare, parameterize, batch, update, query)
     */
    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }

    /**
     * 扫描我们自定义的类型处理器(用来处理我们的java类型和数据库类型的转化) 并且注册到我们的 targetConfiguration(批量注册)
     */
    if (hasLength(this.typeHandlersPackage)) {
      scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
          .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
          .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
    }

    /**
     * 通过配置<TypeHandlers></TypeHandlers>的形式来注册我们的类型处理器对象
     */
    if (!isEmpty(this.typeHandlers)) {
      Stream.of(this.typeHandlers).forEach(typeHandler -> {
        targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
      });
    }

    /**
     * MyBatis 从 3.2 开始支持可插拔的脚本语言, 因此你可以在插入一种语言的驱动(language driver)之后来写基于这种语言的动态 SQL 查询
     * 具体用法:博客地址:https://www.jianshu.com/p/5c368c621b89
     */
    if (!isEmpty(this.scriptingLanguageDrivers)) {
      Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
        targetConfiguration.getLanguageRegistry().register(languageDriver);
        LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
      });
    }
    Optional.ofNullable(this.defaultScriptingLanguageDriver)
        .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

    /**
     * 设置数据库厂商
     */
    if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
      try {
        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

    /**
     * 若二级缓存不为空,注册二级缓存
     */
    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

    if (xmlConfigBuilder != null) {
      try {
        /**
         * 真正的解析我们的配置(mybatis-config.xml)的document对象
         */
        xmlConfigBuilder.parse();
        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();
      }
    }

    /**
     * 为我们的configuration设置一个环境变量
     */
    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));

    /**
     * 切入点二:循环我们的mapper.xml文件
     */
    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            /**
             * 循环我们的mapper.xml文件
             */
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }

    /**
     * 通过建造者模式构建我们的SqlSessionFactory对象 默认是DefaultSqlSessionFactory
     */
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }

如果配置了mybatis-config.xml并且在配置中申明mappers
<configuration>
	<mappers>
        <package name="com.cheng.mapper"/>
    </mappers>
</configuration>

如果指定了mybatis-config.xml
那么会new xmlConfigBuilder()#parse解析mybatis-config.xml文件

<configuration>
	<mappers>
        <package name="com.cheng.mapper"/>
    </mappers>
</configuration>

看下切入点一代码

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

public Configuration parse() {
    /**
     * 若已经解析过了 就抛出异常
     */
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    /**
     * 设置解析标志位
     */
    parsed = true;
    /**
     * 解析我们的mybatis-config.xml的
     * 节点
     * <configuration>
     *
     *
     * </configuration>
     */
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

进入parseConfiguration()方法,解析Mybatis-config.xml各种配置

private void parseConfiguration(XNode root) {
    try {
      /**
       * 解析 properties节点
       *     <properties resource="mybatis/db.properties" />
       *     解析到org.apache.ibatis.parsing.XPathParser#variables
       *           org.apache.ibatis.session.Configuration#variables
       */
      propertiesElement(root.evalNode("properties"));
      /**
       * 解析我们的mybatis-config.xml中的settings节点
       * 具体可以配置哪些属性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings
       * <settings>
            <setting name="cacheEnabled" value="true"/>
            <setting name="lazyLoadingEnabled" value="true"/>
           <setting name="mapUnderscoreToCamelCase" value="false"/>
           <setting name="localCacheScope" value="SESSION"/>
           <setting name="jdbcTypeForNull" value="OTHER"/>
            ..............
           </settings>
       *
       */
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      /**
       * 基本没有用过该属性
       * VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。
         Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序
         解析到:org.apache.ibatis.session.Configuration#vfsImpl
       */
      loadCustomVfs(settings);
      /**
       * 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
       * SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
       * 解析到org.apache.ibatis.session.Configuration#logImpl
       */
      loadCustomLogImpl(settings);
      /**
       * 解析我们的别名
       * <typeAliases>
           <typeAlias alias="Author" type="com.cheng.pojo.Author"/>
        </typeAliases>
       <typeAliases>
          <package name="com.cheng.pojo"/>
       </typeAliases>
       解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases
       */
      typeAliasesElement(root.evalNode("typeAliases"));
      /**
       * 解析我们的插件(比如分页插件)
       * mybatis自带的
       * Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
         ParameterHandler (getParameterObject, setParameters)
         ResultSetHandler (handleResultSets, handleOutputParameters)
         StatementHandler (prepare, parameterize, batch, update, query)
        解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors
       */
      pluginElement(root.evalNode("plugins"));

      /**
       * todo
       */
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      // 设置settings 和默认值
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631

      /**
       * 解析我们的mybatis环境
         <environments default="dev">
           <environment id="dev">
             <transactionManager type="JDBC"/>
             <dataSource type="POOLED">
             <property name="driver" value="${jdbc.driver}"/>
             <property name="url" value="${jdbc.url}"/>
             <property name="username" value="root"/>
             <property name="password" value="Zw726515"/>
             </dataSource>
           </environment>

         <environment id="test">
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
           <property name="driver" value="${jdbc.driver}"/>
           <property name="url" value="${jdbc.url}"/>
           <property name="username" value="root"/>
           <property name="password" value="123456"/>
           </dataSource>
         </environment>
       </environments>
       *  解析到:org.apache.ibatis.session.Configuration#environment
       *  在集成spring情况下由 spring-mybatis提供数据源 和事务工厂
       */
      environmentsElement(root.evalNode("environments"));
      /**
       * 解析数据库厂商
       *     <databaseIdProvider type="DB_VENDOR">
                <property name="SQL Server" value="sqlserver"/>
                <property name="DB2" value="db2"/>
                <property name="Oracle" value="oracle" />
                <property name="MySql" value="mysql" />
             </databaseIdProvider>
       *  解析到:org.apache.ibatis.session.Configuration#databaseId
       */
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      /**
       * 解析我们的类型处理器节点
       * <typeHandlers>
            <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
          </typeHandlers>
          解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap
       */
      typeHandlerElement(root.evalNode("typeHandlers"));
      /**
       * 最最最最最重要的就是解析我们的mapper
       *
       resource:来注册我们的class类路径下的
       url:来指定我们磁盘下的或者网络资源的
       class:
       若注册Mapper不带xml文件的,这里可以直接注册
       若注册的Mapper带xml文件的,需要把xml文件和mapper文件同名 同路径
       -->
       <mappers>
          <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
          <mapper class="com.tuling.mapper.DeptMapper"></mapper>


            <package name="com.tuling.mapper"></package>
          -->
       </mappers>
       * package 1.解析mapper接口 解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers
                 2.
       */
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

进入mapperElement(root.evalNode("mappers"));
配置的方式不同解析切入点细微差别

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      /**
       * 获取我们mappers节点下的一个一个的mapper节点
       */
      for (XNode child : parent.getChildren()) {
        /**
         * 判断我们mapper是不是通过批量注册的
         * <package name="com..mapper"></package>
         */
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          /**
           * 判断从classpath下读取我们的mapper
           * <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
           */
          String resource = child.getStringAttribute("resource");
          /**
           * 判断是不是从我们的网络资源读取(或者本地磁盘得)
           * <mapper url="D:/mapper/EmployeeMapper.xml"/>
           */
          String url = child.getStringAttribute("url");
          /**
           * 解析这种类型(要求接口和xml在同一个包下)
           * <mapper class="com.tuling.mapper.DeptMapper"></mapper>
           *
           */
          String mapperClass = child.getStringAttribute("class");

          /**
           * 我们得mappers节点只配置了
           * <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
           */
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            /**
             * 把我们的文件读取出一个流
             */
            InputStream inputStream = Resources.getResourceAsStream(resource);
            /**
             * 创建读取XmlMapper构建器对象,用于来解析我们的mapper.xml文件
             */
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            /**
             * 真正的解析我们的mapper.xml配置文件(说白了就是来解析我们的sql)
             */
            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.");
          }
        }
      }
    }
  }

进入configuration.addMappers(mapperPackage);

if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        }

来到MapperRegistry#addMapper

 public <T> void addMapper(Class<T> type) {
    /**
     * 判断我们传入进来的type类型是不是接口
     */
    if (type.isInterface()) {
      /**
       * 判断我们的缓存中有没有该类型
       */
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        /**
         * 创建一个MapperProxyFactory 把我们的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.    mapper注解构造器
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        /**
         * 进行解析
         */
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

进入new MapperAnnotationBuilder(config, type)#parse方法

public void parse() {
    String resource = type.toString();
    // 是否已经解析mapper接口对应的xml
    if (!configuration.isResourceLoaded(resource)) {
      // 根据mapper接口名获取 xml文件并解析,  解析<mapper></mapper>里面所有东西放到configuration
      loadXmlResource();
      // 添加已解析的标记
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      // 获取所有方法 看是不是用了注解
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            // 是不是用了注解  用了注解会将注解解析成MappedStatement
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

如果配置文件指定了dao包路径,并且xml文件和dao包路径相同
比如dao包名:com.cheng.mapper,那么配置的xml文件路径是(com/cheng/mapper)
在这里面会将包名’.'替换成 ‘/’ ,然后进行扫描解析工作
image-20210727104754845
进入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";
      // #1347
      InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
      if (inputStream == null) {
        // Search XML mapper that is not in the module but in the classpath.
        try {
          inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
        } catch (IOException e2) {
          // ignore, resource is not required
        }
      }
      if (inputStream != null) {
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

最终会在new XMLMapperBuilder#parse()进行xml文件的解析


如果配置文件中没有申明Mappers
在sqlSessionFactory中配置了mapper路径
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:com/cheng/mapper/*.xml"));

和上面在mybatis配置文件中指定Mapper的过程一致
最终都会new XMLMapperBuilder#parse里进行xml文件的解析

切入点二代码

if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            /**
             * 真正的循环我们的mapper.xml文件
             */
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }

再看最后一种,如果配置文件没有申明mapper,也没有配置xml文件映射关系,那么会在bean初始化的时候进行xml文件解析

上篇说到我们的dao接口最终会被设置成MapperFactoryBean

看下MapperFactoryBean结构也实现了InitializingBean

image-20210723100027953

那么在该Bean初始化时也会调用InitializingBean#afterPropertiesSet()方法

不过在父类DaoSupport中处理了
进行检查解析checkDaoConfig();

public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		// Let abstract subclasses check their configuration.
		checkDaoConfig();

		// Let concrete implementations initialize themselves.
		try {
			initDao();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}

最终来到MapperFactoryBean中的checkDaoConfig();方法

protected void checkDaoConfig() {
    /**
     * 调用父类的SqlSessionDaoSupport的方法来检查我们的SqlSessionFactory 或者sqlSessionTemplate是否为空
     */
    super.checkDaoConfig();

    /**
     * 断言我们的mapperInterface(我们mapper接口class类型是否为空)
     */
    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    /**
     * 在这里进行了二个操作,第一步:getSqlSession() 是调用父类的获取SqlSession类型(接口)实现类 SqlSessionTemplate 第二步:getConfiuration
     * 是调用sqlSessionTemplate的sqlSessionFactory对象获取他的Configuration属性
     */
    Configuration configuration = getSqlSession().getConfiguration();
    /**
     * 判断我们的mapperRegistry 的knownMappers Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
     */
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        /**
         * 把我们的接口类型保存到sqlSessionFactory的属性Configuration对象 的MapperRegistry属性中
         */
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

进入 configuration.addMapper(this.mapperInterface);方法
会来到Configuration#addMapper()

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

继续跟进来到MapperRegister#addMapper()

public <T> void addMapper(Class<T> type) {
    /**
     * 判断我们传入进来的type类型是不是接口
     */
    if (type.isInterface()) {
      /**
       * 判断我们的缓存中有没有该类型
       */
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        /**
         * 创建一个MapperProxyFactory 把我们的Mapper接口保存到工厂类中
         */
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        /**
         * 进行解析
         */
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

进入parser.parse();坚持住,没几行了…

public void parse() {
    String resource = type.toString();
    // 是否已经解析mapper接口对应的xml
    if (!configuration.isResourceLoaded(resource)) {
      // 根据mapper接口名获取 xml文件并解析,  解析<mapper></mapper>里面所有东西放到configuration
      loadXmlResource();
      // 添加已解析的标记
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      // 获取所有方法 看是不是用了注解
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            // 是不是用了注解  用了注解会将注解解析成MappedStatement
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

进入loadXmlResource();
将nameSpace替换成/ 去resoure下去找xml文件,如果xml文件没有放在resource下(我一般喜欢把dao和xml放一个包下),那么会去编译后的src中去查找

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())) {
    //将nameSpace替换成/
    String xmlResource = type.getName().replace('.', '/') + ".xml";
    // #1347
    //如果xml文件没有放在resource中,会去src/目录中去拿
    InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
    if (inputStream == null) {
      // Search XML mapper that is not in the module but in the classpath.
      try {
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e2) {
        // ignore, resource is not required
      }
    }
    if (inputStream != null) {
      XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
      xmlParser.parse();
    }
  }
}

看到这XMLMapperBuilder的parse方法和上面xml的过程就一样了

XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
      xmlParser.parse();

是不是又好像懂了点什么…

如果觉得还算凑合,给个赞哦

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值