mybatis MapperScannerConfigurer 源码剖析

mybatis MapperScannerConfigurer 示例

使用过 mybatis 的人应该都知道,mybatis 有个特性就是对于 Mapper 类,只须要声明接口就能够了,而不须要写具体的实现类,上层在使用 Mapper 接口时只须要直接注入 Mapper 接口就能够正常工做,下面咱们就来具体剖析下 mybatis 是怎样经过这个 Mapper 接口来自动生成 Mapper 实现类,而且注册到 spring 容器中。java

先来看一个 mybatis 的使用示例

applicationContext.xmlspring

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="configLocation" value="classpath:config/mybatis-config.xml" />
  <property name="dataSource" ref="dataSource" />
  <property name="mapperLocations" value="classpath:mapping/*Mapper.xml" />
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <!-- 扫描全部 RepositoryMapper 注解的类 -->
  <property name="annotationClass" value="com.jaf.framework.core.mapper.RepositoryMapper" />
  <!-- 扫描全部 BaseMapper 接口实现类 -->
  <property name="markerInterface" value="com.jaf.framework.core.mapper.BaseMapper" />
  <property name="basePackage" value="com.hbvtc.exam.mapper.*" />
  <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>

TeacherMapper.java & BaseMapper.javasql

@RepositoryMapper
public interface TeacherMapper extends BaseMapper<Teacher> {

}

public interface BaseMapper<E extends BaseEntity<?>> {
    void insertEntity(E var1);

    void updateById(E var1);

    <T> void deleteById(T var1);

    <T> void deleteByIds(T[] var1);

    Page<E> pageQuery(Map<String, Object> var1);
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepositoryMapper {
}

这个 mapper 只有接口,没有具体的实现类,而且这个 Mapper 接口是被 @RepositoryMapper 注解标识的,因此能够被 mybatis 扫描到。apache

注意,这里的 @RepositoryMapper 注解只是个普通的注解,并无被 spring @Component 注解标识,那么 mybatis 是怎样扫描到这些 mapper 接口,而且生成对应的实现类对象,而后注册到 spring 容器中的呢?缓存

TeacherDaoImpl.javamybatis

@Repository("teacherDao")
public class TeacherDaoImpl extends BaseDaoImpl<Teacher> implements TeacherDao {

  // 像使用普通的 spring bean 同样注入 mapper 类
	@Autowired
	private TeacherMapper teacherMapper;

	@Override
	protected BaseMapper<Teacher> getMapper() {
		return teacherMapper;
	}

}

上层 dao 实现类中,对 mapper 的注入像普通的 bean 同样。app

详细分析 mybatis 的 MapperScannerConfigurer

从上面的配置文件中能够看到,mybatis 配置的类为 org.mybatis.spring.mapper.MapperScannerConfigurer,也正是这个类实现了 mybatis mapper 接口的扫描,而且注册具体的实例到 spring 容器中。ide

先看下这个类的类继承关系图post

MapperScannerConfigurer 类继承关系图

能够看到这个类是实现了 BeanDefinitionRegistryPostProcessor 接口的,以前已经介绍过 BeanDefinitionRegistryPostProcessor 这个接口能够在 spring 容器启动过程当中对 Beandefinition 注册作一些扩展。this

下面咱们直接来看 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry 方法

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  if (this.processPropertyPlaceHolders) {
    processPropertyPlaceHolders();
  }

  // ClassPathBeanDefinitionScanner 从 ClassPathBeanDefinitionScanner 继承,这个类主要负责 mybatis mapper 文件的扫描
  // 扫描的基本规则就是根据以前配置文件中配置的
  // basePackage: 在哪一个包下扫描
  // markerInterface: 扫描哪一个接口,markerInterface 接口的全部实现类会被扫描到
  // annotationClass: 或者是扫描被 annotationClass 注解的全部类
  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  scanner.setAddToConfig(this.addToConfig);
  scanner.setAnnotationClass(this.annotationClass);
  scanner.setMarkerInterface(this.markerInterface);
  scanner.setSqlSessionFactory(this.sqlSessionFactory);
  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  scanner.setResourceLoader(this.applicationContext);
  scanner.setBeanNameGenerator(this.nameGenerator);
  scanner.registerFilters();
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan

public int scan(String... basePackages) {
  int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

  doScan(basePackages);

  // Register annotation config processors, if necessary.
  if (this.includeAnnotationConfig) {
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
  }

  return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

#scan 方法是在父类(ClassPathBeanDefinitionScanner)中定义的,其中最主要的是 #doScan 方法。

org.mybatis.spring.mapper.ClassPathMapperScanner#doScan

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  // 主要仍是经过 super.doScan 方法来扫描,返回全部符合条件的 BeanDefinitionHolder
  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 {
    // mybatis 对返回的 Beandefinition 进行了进一步的处理
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}

mybatis 的 ClassPathMapperScanner 类对父类中的 #doScan 方法作了重写,但对于 Beandefinition 的扫描和注册到 spring 容器中,主要仍是经过 super.doScan 方法来实现。

org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan 方法

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  Assert.notEmpty(basePackages, "At least one base package must be specified");
  Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
  for (String basePackage : basePackages) {
    // 这个方法的具体逻辑就不展开了,感兴趣的能够本身跟进下源码
    // 主要过滤条件是经过 org.mybatis.spring.mapper.ClassPathMapperScanner#registerFilters 方法来完成注册的
    // 过滤条件包含配置文件中配置的 annotationClass 和 markerInterface

    // 另外这里须要注意的一点是,这里返回的 BeanDefinition 具体的实现类是 ScannedGenericBeanDefinition,而且 BeanDefinition 属性 beanClass 对应的是接口类(正常状况下应该是一个具体的实现类,由于接口是没法实例化的)
    Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
    for (BeanDefinition candidate : candidates) {
      ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
      candidate.setScope(scopeMetadata.getScopeName());
      String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
      if (candidate instanceof AbstractBeanDefinition) {
        postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
      }
      if (candidate instanceof AnnotatedBeanDefinition) {
        // 处理其余的一些注解(@Lazy, @Primary, @DependsOn...)
        AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
      }
      if (checkCandidate(beanName, candidate)) {
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
        definitionHolder =
            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        beanDefinitions.add(definitionHolder);

        // 向 spring 容器注册 BeanDefinition
        registerBeanDefinition(definitionHolder, this.registry);
      }
    }
  }
  return beanDefinitions;
}

这个方法负责扫描 MapperScannerConfigurer 配置的 basePackage 下的全部符合 annotationClass 注解的接口/类,或者实现了 markerInterface 接口的接口/类,而且生成对应的 BeanDefinition,而后注册到 spring 容器中。
到目前为止咱们应该解决了 mybatis 是如何扫描到全部的 mapper 接口,而且注册到 spring 容器中的问题,可是须要注意的是这里返回的 BeanDefinition 里面的 beanClass 属性对应的可能接口类,一个接口是没办法被实例化的,也就是目前为止扫描到的这些 BeanDefinition 在后续 spring 容器启动过程当中没办法生成对应的 bean 实例。
咱们继续回到上面的 org.mybatis.spring.mapper.ClassPathMapperScanner#doScan 这个方法,这个方法里面还有调用了一个很重要的方法就是 org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions,具体源代码以下(删除了部分日志打印相关代码):

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

    // 这两句代码时关键,这里将 beanClass 进行了从新设置,设置为了 MapperFactoryBean,而且将原来接口 class 设置为构造方法参数
    definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
    definition.setBeanClass(this.mapperFactoryBean.getClass());

    definition.getPropertyValues().add("addToConfig", this.addToConfig);

    // 设置 sqlSessionFactory,若是有配置
    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;
    }

    // 设置 sqlSessionTemplate,若是有配置
    if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
      definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
      explicitFactoryUsed = true;
    } else if (this.sqlSessionTemplate != null) {
      definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
      explicitFactoryUsed = true;
    }

    // 若是 sqlSessionFactory & sqlSessionTemplate 都没有指定,那么启用类型自动注入
    if (!explicitFactoryUsed) {
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
  }
}

通过这一步的处理,如今 mybatis 扫描到的全部符合条件的 BeanDefinition 都是能够在后续 spring 容器启动过程当中,转换成具体 bean 的了,也就是如今的 BeanDefinition 已是能够正常使用的了。 下面咱们再深刻一步地看下这个 MapperFactoryBean 是如何生成具体的实现类的,由于咱们的代码中并无对 Mapper 接口生成具体的实现类,查看 org.apache.ibatis.binding.MapperProxyFactory#newInstance 源码以下:

public T getObject() throws Exception {
  return getSqlSession().getMapper(this.mapperInterface);
}

跟着调用链一直追踪,最后会调用到 org.apache.ibatis.binding.MapperRegistry#getMapper

public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {
  // mybatis 经过 jdk 的动态代理方式来为全部的 mapper 接口生成一个代理实现类
  // 使用 MapperProxy 做为动态代理的 InvocationHandler
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

到这里就已经很是明确了,mybatis 经过 jdk 的动态代理方式来为全部的 mapper 接口生成一个代理实现类。

顺便咱们来看下 org.apache.ibatis.binding.MapperProxy#invoke 方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    // 若是 mapper 是一个具体的实现类,那么直接调用实现类的方法
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else if (isDefaultMethod(method)) {
      // default method 是指那些例如:toString, equals 方法等
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  // 当 mapper 接口中的一个方法被调用的时候,会进入到这里,mybatis 会对这个方法生成一个 MapperMethod 对象,而且缓存这个对象
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
}

org.apache.ibatis.binding.MapperMethod#execute

// mapper 接口中的 select / update 等方法最终被执行的地方
public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
    case INSERT: {
      // 调用了 sqlSession 的 insert / update / delete ...
    Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    case DELETE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    case SELECT:
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {
        result = executeForCursor(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
      break;
    case FLUSH:
      result = sqlSession.flushStatements();
      break;
    default:
      throw new BindingException("Unknown execution method for: " + command.getName());
  }
  if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    throw new BindingException("Mapper method '" + command.getName()
        + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  }
  return result;
}

至此 mybatis 对于 mapper 接口的扫描、如何注册到 spring 容器中、如何对 mapper 接口生成动态代理实现类,以及动态代理类方法执行已经所有解析完了。

本文全部源代码基于 mybatis:3.4.5 & mybatis-spring:1.3.1

相关文章
相关标签/搜索