mybatis-spring 框架原理与源码解析--初始化过程

mybatis-spring整合了mybatis和spring,使mybatis能参与spring事务。SqlSessionFactoryBean是核心组件,配置数据源后,可通过SqlSessionTemplate或Mapper接口进行DAO操作。SqlSessionFactoryBean实现FactoryBean接口,方便定制bean创建过程。@MapperScan或MapperScannerConfigurer用于扫描Mapper接口,创建MapperFactoryBean。MapperRegistry存储Mapper接口,MapperProxy作为InvocationHandler处理Mapper调用,接入mybatis核心逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

mybatis-spring将mybatis无缝集成到spring框架中,使得mybatis能够参与spring的事务,将mybatis mapper和sqlsession注入到其它bean中,将mybatis的异常转换成spring的DataAccessException等。本文将在代码层面,从spring容器启动到一次数据库访问结束期间,mybatis-spring的运行流程,来解析mybatis的原理及其核心技术。

配置mybatis


使用mybatis-spring,我们有多种配置bean的方式。当然无论如何配置,数据源是必不可少的:

<bean id = “dataSource” class = “org.springframework.jdbc.datasource.DriverManagerDatasource”>
    <property name = “driverClassName” value = “com.mysql.jdbc.Driver”/>
    <property name = “url” value = “jdbc:mysql://localhost:3306/test”/>
    <property name = “username” value = “xxx”/>
    <property name = “password” value = “xxx”/>
</bean>

另外,SqlSessionFactoryBean作为mybtis-spring的核心组件,同样不可缺少:

<bean id = “sqlSessionFactory” class = “org.mybatis.spring.SqlSessionFactoryBean”>
    <property name = “dataSource” ref = “dataSource”/>
    <property name = “configLocation” value = “classpath:mybatis.cfg.xml”/>
</bean>

其中,configLocation属性表示mybatis配置文件的路径。mybatis配置文件可以用于指定mapper文件的路径和设置类别名。

方式一:SqlSessionTemplate会话模板

<bean id = “sqlSessionTemplate” class = “org.mybatis.spring.SqlSessionTemplate”>
    <constructor-arg index = “0” ref = “sqlSessionFactory”/>
</bean>

然后我们可以在DAO bean中注入SqlSessionTemplate bean进行dao操作

@Service
public class UserDaoImpl implements UserDao {

    private SqlSessionTemplate sqlSession;

    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public List<User> selectUser() {
        return sqlSession.selectList(“com.example.mapper.selectAll”);
    }
}

方式二:dao类继承SqlSessionDaoSupport

@Service
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
    
    @Autowired
    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate){
         super.setSqlSessionTemplate(sqlSessionTemplate);
    }

    @Override
    public List<User> selectUser() {
        return this.getSqlSession().selectList(“com.example.mapper.selectAll”);
    }
}

由于mybatis-spring在1.2版本之后,将SqlSessionDaoSupport 中setSqlSessionTemplate 和 setSqlSessionFactory方法上的@Autowired注解移除了,因此我们需要重写其中的一个方法,自行注入SqlSessionFactory或者SqlSessionTemplate bean。方式二与方式一本质上无差别。

方式三:基于mapper接口和注解的配置

以上是使用mybatis的传统方式:在mapper文件中编写sql,将sql的id作为方法参数传入SqlSessionTemplate进行dao操作,这种方式并不符合面向对象的思想,因此mybatis引入了Mapper接口,在Mapper接口方法与Mapper xml文件sql id之间建立映射,因此可以直接通过Mapper接口来进行dao操作,原理后面讲。甚至,我们可以在接口方法上使用@Select等注解来编写sql,省去Mapper xml文件。

public interface UserMapper {
    @Select(“select * from USER”)
    List<user> selectUser();
}

然后创建一个对应的MapperFactoryBean

<bean id = “userMapper” class = “org.mybatis.spring.mapper.MapperFactoryBean”>
    <property name = “mapperInterface” value = “com.example.mapper.UserMapper”/>
    <property name = “sqlSessionFactory” ref = “sqlSessionFactory”/>
</bean>

然后我们可以在任何地方注入UserMapper进行dao操作了。事实上,在底层依然是调用SqlSessionTemplate。

手动为每一个Mapper接口创建一个MapperFactoryBean显然是低效的,Mybatis-Spring提供了一个MapperScannerConfigurer,可以自动扫描Mapper接口并创建MapperFactoryBean。只需要设置其basePackage属性即可

<bean class = “org.mybatis.spring.mapper.MapperScannerConfigurer”>
    <property name = “basePackage” value = “com.example.mapper”/>
</bean>

mybatis-spring还提供了另外一种注解的方式,实现跟MapperScannerConfigurer同样的效果:

@Configuration
@MapperScan(basePackages = "org.mybatis.spring.sample.mapper")
public class MybatisConfig {
}

如果mapper xml文件和mapper接口在相同的路径下,那么MapperFactoryBean会自动去寻找和绑定对应的Mapper,此时就没有必要在mybatis配置文件中指明mapper xml文件的路径了。事实上还有另外一种方式来指定mapper xml文件的路径,即在SqlSessionFactoryBean中配置

<bean id = “sqlSessionFactory” class = “org.mybatis.spring.SqlSessionFactoryBean”>
    <property name = “dataSource” ref = “dataSource”/>
    <property name = “mapperLocation” value = “classpath:com/example/mapper/*.xml”/>
</bean>

这样,我们可以将mybatis配置文件也去除了,除非你需要配置类别名。

更多关于mybatis-spring的使用信息,可参考官网http://www.mybatis.org/spring/getting-started.html

原理分析


1. SqlSessionFactoryBean

SqlSessionFactoryBean实现了FactoryBean<SqlSessionFactory>接口,其getObject()方法返回的是一个DefaultSqlSessionFactory实例。这意味着我们可以从Spring容器中获取两个类型的bean:SqlSessionFactoryBean和SqlSessionFactory。关于Spring的BeanFactory在获取bean时如何处理FactoryBean,可以参考https://blog.youkuaiyun.com/zknxx/article/details/79572387https://blog.youkuaiyun.com/zknxx/article/details/79588391。使用FactoryBean的好处是,我们可以定制bean实例的创建过程;如,实例化SqlSessionFactory是较为复杂的,需要配置大量的信息,但是如果采取编码的方式,在SqlSessionFactoryBean中封装其实例化细节,问题就变得简单多了。

SqlSessionFactoryBean还实现了InitializingBean接口,创建DefaultSqlSessionFactory实例的逻辑:

this.sqlSessionFactory = buildSqlSessionFactory()

在接口方法afterPropertiesSet()中被调用,即在SqlSessionFactoryBean bean创建过程中会初始化sqlSessionFactory属性,并将这个属性作为getObject()的返回。我们看看buildSqlSessionFactory()方法:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    // 待填充的一个Configuration变量,包含mybatis的所有配置,用作DefaultSqlSessionFactory构造器参数
    // 三种填充方式:通过SqlSessionFactoryBean注入、从XML配置建造器获取、默认配置new Configuration()
    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    // 通过SqlSessionFactoryBean注入
    if (this.configuration != null) {
      configuration = this.configuration;
      if (configuration.getVariables() == null) {
        configuration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        configuration.getVariables().putAll(this.configurationProperties);
      }
    }
    // 从XML配置建造器获取
    else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } 
    // 默认配置
    else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      if (this.configurationProperties != null) {
        configuration.setVariables(this.configurationProperties);
      }
    }
    // mybatis用来创建对象的工厂类,用户可自定义并注入
    if (this.objectFactory != null) {
      configuration.setObjectFactory(this.objectFactory);
    }
    // 用来创建ObjectWrapper的工厂类。ObjectWrapper用于包装对象,可以是bean和集合;提供一组便于获取对象元信息的API
    if (this.objectWrapperFactory != null) {
      configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }
    // vfs,提供一组方便操作类和资源的API
    if (this.vfs != null) {
      configuration.setVfsImpl(this.vfs);
    }
    // 类别名
    if (hasLength(this.typeAliasesPackage)) {
      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeAliasPackageArray) {
        configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
        }
      }
    }

    if (!isEmpty(this.typeAliases)) {
      for (Class<?> typeAlias : this.typeAliases) {
        configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type alias: '" + typeAlias + "'");
        }
      }
    }
    // 装载所有插件
    if (!isEmpty(this.plugins)) {
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered plugin: '" + plugin + "'");
        }
      }
    }
    // 装载TypeHandler。TypeHandler用于数据库查询结果集和Java对象之间的转换
    if (hasLength(this.typeHandlersPackage)) {
      String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeHandlersPackageArray) {
        configuration.getTypeHandlerRegistry().register(packageToScan);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
        }
      }
    }

    if (!isEmpty(this.typeHandlers)) {
      for (TypeHandler<?> typeHandler : this.typeHandlers) {
        configuration.getTypeHandlerRegistry().register(typeHandler);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type handler: '" + typeHandler + "'");
        }
      }
    }
    // 装载DatabaseIdProvider。DatabaseIdProvider用于根据Datasource对象返回一个id,
    // 这个id用于为不同的数据库类型创建不同的查询sql
    if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
      try {
        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }
    // 装载二级缓存
    if (this.cache != null) {
      configuration.addCache(this.cache);
    }
    // 如果mybatis配置文件存在,对其进行解析,写进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();
      }
    }

    // 如果未创建事务工厂,则使用Spring的事务工厂
    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }
    // Environment维护事务工厂、数据源以及一个值为“SqlSessionFactoryBean”的id
    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
    // 解析mapper文件
    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }
    // 构造SqlSessionFactory实例,实际返回的是DefaultSqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(configuration);
  }

可以看出,SqlSessionFactoryBean存在的意义在于提供一个让用户配置mybatis的入口。

2. @MapperScan和MapperScannerConfigurer

@Mapper通过MapperScannerRegistrar来实现跟MapperScannerConfigurer同样的效果:扫描指定package下的接口,创建MapperFactoryBean。两者内部都是借助ClassPathMapperScanner进行接口的扫描和bean注册。

MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,该接口用于Spring在处理Configuration类时向容器注册额外的bean definition。

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 获取@MapperScan注解的属性信息
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    // 核心组件,用于扫描指定路径下的接口,将其注册为bean definition
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
      scanner.setResourceLoader(resourceLoader);
    }
    // 下面开始将注解中标记的信息注入ClassPathMapperScanner
    // 设置注解类,用作扫描过滤器的过滤条件之一
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }
    // 设置标记接口,用作扫描过滤器的过滤条件之一
    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }
    // bean名称生成器
    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }
    // 设置MapperFactoryBean实例
    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }
    // 将注册的sqlSessionTemplate bean的名称传给scanner
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    // 将注册的sqlSessionFactoryRef bean的名称传给scanner
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<String>();
    for (String pkg : annoAttrs.getStringArray("value")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    // 注册扫描过滤器,根据annotationClass和markerInterface进行过滤,
    // 如果两者都未设置,则扫描package下的所有class
    scanner.registerFilters();
    // 执行扫描
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

扫描Mapper接口并注册对应的bean到Spring容器,核心是ClassPathMapperScanner,继承自ClassPathBeanDefinitionScanner,该类用于扫描和检测classpath下的bean candidates,将对应的bean definetion注册到registry(BeanFactory或者ApplicationContext)中,默认的扫描过滤器包含@Component、@Repository、@Service和@Controller等注解标注的类。ClassPathMapperScanner并不使用默认的过滤器,并自己定义了一套过滤规则。

接着看ClassPathMapperScanner的doScan方法

  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

调用了ClassPathBeanDefinitionScanner的doScan方法,其中的细节不在此延伸,总之返回了一个集合,包含指定package下的所有bean definition。我们看看Mybatis-Spring接着对这些bean definition做了什么处理

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();

      if (logger.isDebugEnabled()) {
        logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
          + "' and '" + definition.getBeanClassName() + "' mapperInterface");
      }

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      // Mapper接口class作为MapperFactoryBean的构造器参数
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      // 将bean class重新设置为MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      // 设置MapperFactoryBean的addToConfig属性
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
      // 将sqlSessionFactory和sqlSessionTemplate bean注入到MapperFactoryBean。
      // 注意,MapperFactoryBean继承自SqlSessionDaoSupport
      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

至此,已经完成了为每一个Mapper接口注册一个对应的MapperFactoryBean。MapperFactoryBean同样实现了FactoryBean接口,接着看其getObject方法究竟创建了一个怎样的实例。

// MapperFactoryBean
  @Override
  public T getObject() throws Exception {
    // 最终调用Configuration.getMapper方法
    // 泛型T是MapperFactoryBean对应的接口类型,最终返回的是一个动态代理类实例
    return getSqlSession().getMapper(this.mapperInterface);
  }

不要忘了Configuration实例是在什么时候被组装的:在SqlSessionFactoryBean的afterPropertiesSet方法里。Configuration中维护了一个MapperRegistry实例(Configuration和MapperRegistry互为构造器参数),用来储存所有注册的Mapper,根据接口类型和sqlSession对象从中获取Mapper实例。

// Configuration
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

那么Mapper是何时被添加到MapperRegistry中的呢?有三处:

1,如果在mybatis的xml配置文件中创建了mappers节点,那么在通过XmlConfigBuilder解析配置文件时会注册所有mapper。

// SqlSessionFactoryBean.buildSqlSessionFactory
if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();


// XmlConfigBUilder
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.");
          }
        }
      }
    }
  }

2,指定了SqlSessionFactoryBean的mapperLocations时,会调用XMLMapperBuilder来解析mapper文件,将mapper注册到configuration中

// SqlSessionFactoryBean.buildSqlSessionFactory
if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();


// XMLMapperBuilder
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

3, MapperFactoryBean实现了Spring的DaoSupport接口,在创建MapperFactoryBean时会将对应的Mapper接口注册到Configuration中

// MapperFactoryBean
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        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维护的MapperRegistry属性在添加Mapper时做了些什么事情

// MapperRegistry
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 {
        // 将已经加载的接口类型放入一个map中
        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.
        // 我们除了在xml中配置Mapper,还可以通过注解在接口方法中标注。
        // 这里就是为了处理这些通过注解配置的Mapper,其作用等同于XmlMapperBuilder
        // 核心的用途在于解析注解中配置的sql和resultmap等,生成MappedStatement放入    
        // Configuration中,id为“接口名.方法名"
        // 同时也会使用XmlMapperBuilder解析同目录下的同名xml文件
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

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

我们回到MapperRegistry的getMapper方法,从这里能获取到什么呢?

  // MapperRegistry
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 根据Mapper接口类型取出对应的MapperProxyFactory实例,注意,这个实例是在addMapper时添加进去的
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      // 每次getMapper会从对应的mapperProxyFactory中获取一个新的MapperProxy实例
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }


// MapperProxyFactory
  protected T newInstance(MapperProxy<T> mapperProxy) {
    // 通过jdk的动态代理创建一个Mapper接口类型的代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    // 创建一个MapperProxy对象, MapperProxy实现了InvocationHandler接口
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

MapperProxy实现了InvocationHandler接口,所有对Mapper代理对象的调用都会被托管到MapperProxy的invoke方法

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 如果是从Object对象继承的方法,则直接通过反射本MapperProxy实例调用
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } 
      // 如果是接口中的default方法,则通过MethodHandle调用
      else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    // 对Mapper接口方法的调用,全都转嫁到这里啦。将由Executor来执行绑定的MappedStatement
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

至此我们已经进入Mybatis的核心逻辑(在此之前的步骤是mybatis与Spring集成的情况下,以Spring的方式来优雅地配置mybatis),我将在后续文章中继续解读mybatis的核心组件源码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值