MyBatis接口是如何执行SQL的?- mapper注入篇

1,325 阅读4分钟

上文我们只是知道了springboot的自动装配是怎么帮我们来创建SqlSessionFactory的,在上面的步骤后,容器里面此时就存在SqlSessionFactory的bean对象了,但是我们直接使用@Autowired注解Mapper就能直接使用的原因还是一脸懵逼的。

首先,debug可以发现此时的mapper的对象为org.apache.ibatis.binding.MapperProxy对象,显然在某个地方将这些Mapper接口代理为MapperProxy注入了Spring容器中的。

image-20210805105859106.png

由包名可以知道,这个代理对象是mybatis的对象,Tb1Mapper本身只是一个接口,是没法创建对象的,这个对象应该是在某个地方通过动态代理的方式创建对象并注入到Spring容器中的。

而我们代码中唯一和这些mapper接口存在关系的,就是@MapperScan这个注解。

@MapperScan

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 主要这个
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
  String[] value() default {};
  String[] basePackages() default {};
  Class<?>[] basePackageClasses() default {};
  //...
}

可以看到这个注解@Import(MapperScannerRegistrar.class)这个类,@Import的作用就是导入配置类,所以先看看这个类。(当然,如果你没有使用这个注解,而是使用配置文件的方式,那么会在自动配置中一样去创建MapperScannerRegistrar这个bean)

MapperScannerRegistrar

代码简化如下:

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  @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);
    // 一系列的addPropertyValue操作,主要就是将@MapperScan注解中的配置设置到该BeanDefinition中
    builder.addPropertyValue("processPropertyPlaceHolders", true);
    List<String> basePackages = new ArrayList<>();
    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
        .collect(Collectors.toList()));
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
    // 将设置好的MapperScannerConfigurer的BeanDefinition注册到Spring中。
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
  }
}

MapperScannerRegistrar 这个类实现了ImportBeanDefinitionRegistrar这个接口,这个接口是spring 对外提供的接口,目的是实现bean的动态注入,只不过这个需要我们自己去构建BeanDefinition然后注册进去。

核心方法就是实现registerBeanDefinitions接口,如代码所示,这个方法的主要作用就是将 @MapperScan 注解配置的信息全部设置到MapperScannerConfigurer这个类的BeanDefinition中,本质上就是创建MapperScannerConfigurer这个bean对象。

MapperScannerConfigurer

经过上面的步骤,MapperScannerConfigurer这个bean的核心就是获取到@MapperScan里面配置的mapper接口的路径信息。简单看下该类的核心实现:

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
  private String basePackage;
  //... 一些属性
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    //... 一些set操作,不过此时基本都为null,主要是这个scan方法。
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
}

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,该接口是BeanFactoryPostProcessor的子接口,提供了一个postProcessBeanDefinitionRegistry,该方法进一步往Spring中注册BeanDefinition。

特殊说明下:

  • BeanFactoryPostProcessor这个PostProcessor是在bean的Definition信息已经加载完成,但是还没有进行初始化的时候执行postProcessBeanFactory方法。一般实现该接口用于修改某个BeanDefinition的属性信息。
  • BeanDefinitionRegistryPostProcessor是在BeanFactoryPostProcessor之前执行的,他处于BeanDefinition将要被加载,我们可以看到他接口方法的参数是BeanDefinitionRegistry,所以是可以再往Spring中注册一些新的BeanDefinition等组件的。

该方法主要做了2件事请:

  1. 创建了ClassPathMapperScanner对象scanner,并将registry对象传进去。
  2. 调用scanner的scan方法,这里就是扫描mapper的核心方法。

所以我们再去看看ClassPathMapperScanner

ClassPathMapperScanner

代码简化如下:

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

  private SqlSessionFactory sqlSessionFactory;
  private SqlSessionTemplate sqlSessionTemplate;
  
  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
  
  // 构造函数,registry继续传入ClassPathBeanDefinitionScanner
  public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
    super(registry, false);
  }
  // 扫描MapperScan配置的包下所有的接口,并创建为BeanDefinition注册到Spring中去。
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 调用父类的扫描方法
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    processBeanDefinitions(beanDefinitions);
    return beanDefinitions;
  }

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    // 遍历那些已经被临时封装为BeanDefinition的mapper接口
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      // 设置该BeanDefinition的class为MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBeanClass);
      
  }
}

ClassPathMapperScanner继承了Spring的包扫描器ClassPathBeanDefinitionScanner 同时覆写了doScan的方法,上面scanner.scan实际调用的是ClassPathBeanDefinitionScanner的scan方法。

我们看看这个方法做了哪些事情:

  1. 调用父类ClassPathBeanDefinitionScannerdoScan扫描了mapper接口包的接口类,并将其初步封装为BeanDefinitionHolder(可以直接先看做就是BeanDefinition)
  2. 这些BeanDefinition在super.doScan(basePackages);调用完成后,就已经全部注册入register中去了。
  3. 下面针对获取的这些BeanDefinition,调用processBeanDefinitions进行逐步加工,我这里只保留了核心:设置该BeanDefinition的class为MapperFactoryBean!

最终我们获取到的mapper接口的BeanDefinition如下图:

image-20210811203152072.png

这下就很明了了,这步将mapper接口的BeanDefinition的class设置为MapperFactoryBean,那么Spring在初始化bean的时候,就会去调用MapperFactoryBean的构造方法。

我们继续去看看MapperFactoryBean这个类:

MapperFactoryBean

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  
  private Class<T> mapperInterface;
  // 该属性是从SqlSessionDaoSupport摘取出来的,该类是个抽象类
  private SqlSessionTemplate sqlSessionTemplate;

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

  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }
    
  public MapperFactoryBean() {
    // intentionally empty
  }

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

可以看到MapperFactoryBean是一个FactoryBean,所以他最终往容器中注入的Bean是getObject返回的方法,即:

getSqlSession().getMapper(this.mapperInterface);

这个方法的追踪如下:

public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this);
}
⬇️ # Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}
⬇️ # MapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}
⬇️ # MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}
⬇️ # MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
  // 这里可以看到了,是基于JDK的动态代理创建了代理的Bean
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

所以,我们@Autowired注入的对象,实际上都是MapperProxy这个类的对象,我们再看看这个类。

MapperProxy

public class MapperProxy<T> implements InvocationHandler, Serializable {
  
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }
  
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 如果是Object的方法,说明不是调用sql的方法,直接放行。
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // mapper的sql调用方法
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
}

这部分具体是如何调用到执行的SQL的,这部分可以看我之间的Mybatis源码解析-快速一览 > 使用过程