MyBatis接口是如何執行SQL的?- mapper注入篇 | 8月更文挑戰

這是我參與8月更文挑戰的第10天,活動詳情查看:8月更文挑戰java

上文咱們只是知道了springboot的自動裝配是怎麼幫咱們來建立SqlSessionFactory的,在上面的步驟後,容器裏面此時就存在SqlSessionFactory的bean對象了,可是咱們直接使用@Autowired註解Mapper就能直接使用的緣由仍是一臉懵逼的。spring

首先,debug能夠發現此時的mapper的對象爲org.apache.ibatis.binding.MapperProxy對象,顯然在某個地方將這些Mapper接口代理爲MapperProxy注入了Spring容器中的。sql

image-20210805105859106.png

由包名能夠知道,這個代理對象是mybatis的對象,Tb1Mapper自己只是一個接口,是無法建立對象的,這個對象應該是在某個地方經過動態代理的方式建立對象並注入到Spring容器中的。apache

而咱們代碼中惟一和這些mapper接口存在關係的,就是@MapperScan這個註解。springboot

@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)markdown

MapperScannerRegistrar

代碼簡化以下:mybatis

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而後註冊進去。app

核心方法就是實現registerBeanDefinitions接口,如代碼所示,這個方法的主要做用就是將 @MapperScan 註解配置的信息所有設置到MapperScannerConfigurer這個類的BeanDefinition中,本質上就是建立MapperScannerConfigurer這個bean對象。ide

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源碼解析-快速一覽 > 使用過程

相關文章
相關標籤/搜索