【Mybatis源码】Mybatis如何为mapper接口生成代理对象--JDK动态代理

引言

  • mybatis版本:3.5.1
  • mybatis-spring:2.0.1

使用过Mybatis框架的开发人员应该都知道,在编写dao层时,只需要提供mapper接口与相应的xxxMapper.xml,无需实现类,便可以将mapper接口对象交由Spring容器管理,疑问:

  1. Mybatis是如何为mapper接口生成代理对象的?
  2. Mybatis又是如何将mapper对象交给Spring管理?

我们在整合mybatis与spring时,都会使用MyBatis-Spring 将 MyBatis 代码无缝地整合到 Spring 中。

MyBatis-Spring出现的动机:

Spring 2.0 只支持 iBatis 2.0。那么,我们就想将 MyBatis3 的支持添加到 Spring 3.0 中(参见 Spring Jira 中的 问题 )。不幸的是,Spring 3.0 的开发在 MyBatis 3.0 官方发布前就结束了。 由于 Spring 开发团队不想发布一个基于未发布版的 MyBatis 的整合支持,如果要获得 Spring 官方的支持,只能等待下一次的发布了。基于在 Spring 中对 MyBatis 提供支持的兴趣,MyBatis 社区认为,应该开始召集有兴趣参与其中的贡献者们,将对 Spring 的集成作为 MyBatis 的一个社区子项目。

那么,在mybatis-spring中,mybatis对于集成spring做了哪些扩展?

@MapperScan注解

首先需要扫描指定mapper包下面的接口,为这些接口生成代理对象,再通过FactoryBean将这些代理对象交给Spring管理。

注:对FactoryBean不熟悉的,可以查看:https://blog.csdn.net/a1036645146/article/details/111661211

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
    // 省略部分。。。
    Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}

这个注解的主要作用是将MapperScannerConfigurer放到BeanDefinitionMap当中。

那么它是如何工作的呢?在spring中,有一个内置类ConfigurationClassPostProcessor,这个类的作用是扫描带spring注解的类添加到bdmap中,并且解析配置类上的@Import标签,若这个标签中的类实现了ImportBeanDefinitionRegistrar接口,会将这这个类实例化后放入到 importBeanDefinitionRegistrars 缓存当中。扫描完成后会执行这个集合当中的对象的registerBeanDefinitions()方法。

mybatis-spring中MapperScannerRegistrar#registerBeanDefinitions()这个方法的作用就是将MapperScannerConfigurer注册到BeanDefinitionMap当中。

//MapperScannerRegistrar
@Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }
 void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
    BeanDefinitionRegistry registry, String beanName) {

  BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  // 省略部分。。。

  registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}

MapperScannerConfigurer

mybatis-spring框架中MapperScannerConfigurer 基于spring的扩展实现了BeanDefinitionRegistryPostProcessor接口。在Spring的内置扫描器扫描结束后会触发这个类的postProcessBeanDefinitionRegistry方法,构建一个mybatis的扫描器(ClassPathMapperScanner)来执行扫描mapper接口,将mapper接口对象交给了Spring管理。

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {


  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
    // 创建一个mybatis 的扫描器,扫描mapper接口包,可将mapper.class到BeanDefinition的转换
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    // 下面的一系列set方法是对这个扫描器进行初始化
    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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    scanner.registerFilters();//注册一些扫描用的过滤器
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

。。。
}

其中,ClassPathMapperScanner是mybatis实现的一个扫描器,继承了spring内置的扫描器ClassPathBeanDefinitionScanner,但它并没有重写父类的scan方法,所以这里调用的是父类的scan方法,如下:

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
	
    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);
	}
。。。
}

重点在执行doScan方法,该方法由ClassPathMapperScanner重写了,主要是将mapper.class到BeanDefinition的转换

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

  // 扫描mapper接口包,将mapper.class转为对应的beanDefinition
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);//扫描mapper包下的接口

    if (beanDefinitions.isEmpty()) {
      LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      processBeanDefinitions(beanDefinitions);// //将这些mapper接口变成FactoryBean
    }

    return beanDefinitions;
  }

。。。
}

其中,processBeanDefinitions 这个方法会设置构造函数的参数为这个接口,然后将beanClass修改为MapperFactoryBean之后实例化的时候会调用这个FactoryBean的getObject()方法来构建一个代理Mapper对象。这就是如何将map接口转变成一个对象交给Spring管理的关键

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName()
          + "' and '" + beanClassName + "' mapperInterface");

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      // 添加构造函数需要的参数
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      //设置类型为mapperFactoryBeanClass。之后的实例化会调用MapperFactoryBean中的getObject()来构建出一个基于接口的代理对象交给spring管理
      definition.setBeanClass(this.mapperFactoryBeanClass);
      。。。

    }
  }

MapperProxyFactory

Mybatis框架中,MapperProxyFactory,是mapper代理工厂,可基于接口和MapperProxy来构建一个mapper代理对象,实现了将接口转变成一个对象:

MapperProxy 实现了 InvocationHandler 接口的invoke方法,所以,我们明白了第1个问题,Mybatis是基于JDK动态代理来生成mapper接口的对象的。

注:对jdk动态代理不熟悉的,可以查看:https://blog.csdn.net/a1036645146/article/details/111881599

其中,MapperMethod#execute()如下:

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 根据sql语句的类型,选择不同的分支流程执行
    switch (command.getType()) {
      case INSERT: {// 新增语句
        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);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        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;
  }
。。。
}

MapperFactoryBean

MapperFactoryBean实现了Spring的FactoryBean扩展接口。FactoryBean是一个工厂Bean,可以生成某一类型Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程。具体可以查看《【Spring源码:FactoryBean一】终于弄懂FactoryBean是如何自定义bean的创建过程了》

它可以调用getObject()方法根据接口的不同,返回不同的接口代理对象,这个对象也就是通过上面的MapperProxyFactory产生的。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;
  private boolean addToConfig = true;

  public MapperFactoryBean() {
    //intentionally empty 
  }
  
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

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

。。。

}

总结

1. Mybatis首先扫描指定包下面的mapper接口,为每一个mapper接口创建一个代理工厂MapperProxyFactory,放入MapperRegistry的一个属性集合缓存中:

Map<Class<?>, MapperProxyFactory<?>> knownMappers

2. 使用这些代理工厂,通过实现JDK动态代理去创建mapper接口对应的代理对象,MapperProxy;
3. 通过代理对象去调用实际的业务逻辑。

参考:https://blog.csdn.net/gongsenlin341/article/details/108600008

史上最强Tomcat8性能优化

阿里巴巴为什么能抗住90秒100亿?--服务端高并发分布式架构演进之路

B2B电商平台--ChinaPay银联电子支付功能

学会Zookeeper分布式锁,让面试官对你刮目相看

SpringCloud电商秒杀微服务-Redisson分布式锁方案

查看更多好文,进入公众号--撩我--往期精彩

一只 有深度 有灵魂 的公众号0.0

猜你喜欢

转载自blog.csdn.net/a1036645146/article/details/112359624
今日推荐