SpringBoot與Mybatis整合源碼深度解析

問題:

       SpringBoot中沒有配置Mybatis的整合類,只有Mybatis的配置文件,那麼是怎麼進行關聯的 ?

       在Mapper接口上不加@component或者是@repository也能將Mapper注入到Spring中,是如何實現的?

       所寫的Mapper接口是如何關聯到xml裏面所寫的sql語句的?

       如果對於上述三個問題的答案不是很清楚的話,可以往下面,本文將從源碼的角度分析SpringBoot與Mybatis整合的源碼,對於Spring和Mybatis的整合思想都是一樣的,因爲現在公司中大多數使用的是SpringBoot,所以選用SpringBoot與Mybatis整合作爲分析的重點。

       SpringBoot與Mybatis整合體現在代碼上是很簡單,只需要三步就行:

       一.引入Mybatis啓動包:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>${mybatis.springboot.version}</version>
</dependency>

      二.在SpringBoot的啓動類上面@MapperScan註解:

@MapperScan(value = "it.cast.wechat.mapper")
@SpringBootApplication
public class WechatApplication {
    public static void main(String[] args) {
        SpringApplication.run(WechatApplication.class, args);
    }
}

    三:在SpringBoot的application.properties配置文件中指定mybatis的配置文件和mapper的配置文件的路徑:

mybatis.config-location = classpath:mybatis/SqlMapConfig.xml
mybatis.mapper-locations = classpath:mybatis/mapper/*.xml

   這樣就指定Mapper接口、Mapper的xml文件和Mybatis配置文件的路徑,這樣就可以讀取到文件,並進行解析了。

   下面就根據SpringBoot的啓動流程進行解析

源碼解析:

1.啓動類

@MapperScan(value = "it.cast.wechat.mapper")
@SpringBootApplication
public class WechatApplication {
    public static void main(String[] args) {
        //1.傳入自身的class類,啓動SpringBoot項目
        SpringApplication.run(WechatApplication.class, args);
    }
}

      首先將自身的class類傳入進行然後進行啓動,這裏牽扯到SpringBoot的啓動流程,就不展開講解了,說一下主要內容,其會在WechatApplication 變成一個BeanDefinition,然後放入到BeanFactory的BeanDefinitionNames集合中,然後在Spring的refresh方法中執行調用bean工廠的後置處理器時,將遍歷BeanDefinitionNames集合,然後會找到包裹WechatApplication類的BeanDefinition,此時會解析其類中的註解信息,然後就可以買到@MapperScan註解了。其參考:Spring中bean的生命週期(最詳細),而解析的關鍵類ConfigurationClassPostProcessor可以自行百度,以後有時間會分析一下這個類的源碼。

2.@MapperScan註解

     @MapperScan註解,會使用@Import+ImportBeanDefinitionRegistrar接口的方法向容器中註冊一個bean,關於@Import的源碼分析可以參考:Spring中@Import註解源碼分析,關於@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 {};

}

3.MapperScannerRegistrar類

     MapperScannerRegistrar類實現了ImportBeanDefinitionRegistrar接口,所以可以使用@Import來向Spring容器中導出BeanDefinition。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
     //1.獲取@MapperScan註解屬性信息
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
    }
  }

  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
    //2.使用BeanDefinitionBuilder來構造MapperScannerConfigurer的BeanDefinition對象
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);

    //3.從@MapperScan註解屬性中獲取設置的值,然後設置給MapperScannerConfigurer的bd
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      builder.addPropertyValue("annotationClass", annotationClass);
    }

    //忽略其他設置屬性的代碼

    List<String> basePackages = new ArrayList<>();
    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
   
    //獲取@MapperScan註解的屬性值,然後添加到basePackages集合中
    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
        .collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
        .collect(Collectors.toList()));

    //給BeanDefinition設置屬性
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
    //4.註冊該MapperScannerConfigurer的bd
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }
}

     上面可以總結爲四步:

       1.獲取@MapperScan註解上面的屬性信息,因爲@Import(MapperScannerRegistrar.class)是標註在@MapperScan上面的,所以第一步應該先獲取@MapperScan註解的屬性信息,這些屬性信息使我們手動設置的,在WechatApplication類上使用@MapperScan,是這樣使用的@MapperScan(value = "it.cast.wechat.mapper")。

       2.使用BeanDefinitionBuilder來構造MapperScannerConfigurer的BeanDefinition對象,對於Spring整合Mybatis熟悉的同學應該都知道,需要在Spring的XML文件中配置MapperScannerConfigurer對象,對於SpringBoot來說,這些都給你屏蔽了,不過其實現都是一樣的。

       3.將@MapperScan註解屬性中的值,設置到MapperScannerConfigurer的BeanDefinition對象上面,這是我們就可以拿到了在@MapperScan設置的value等值

       4.使用builder創建BeanDefinition,並放入Spring容器中。

4.MapperScannerConfigurer類

        由上圖可知其MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor接口,我們知道BeanDefinitionRegistryPostProcessor接口是Spring中的一個擴展點,是的作用就是往Spring中注入BeanDefinition的,在Spring的refresh方法中執行調用bean工廠的後置處理器時進行執行。

        這裏Spring在調用BeanDefinitionRegistryPostProcessor時,會調用三次,其調用先後順序是這樣的:

        1.如果類實現了BeanDefinitionRegistryPostProcessor,並且實現了PriorityOrdered

        2.如果類實現了BeanDefinitionRegistryPostProcessor,並且實現了Ordered

        3.如果類只實現了BeanDefinitionRegistryPostProcessor

        掃描WechatApplication類是在ConfigurationClassPostProcessor類中的postProcessBeanDefinitionRegistry方法進行的,其實現了BeanDefinitionRegistryPostProcessor,並且實現了PriorityOrdered,在掃描WechatApplication類是,會掃描其身上的註解信息,然後進行解析進行掃描類,此時掃描到@MapperScan註解,向Spring容器中注入MapperScannerConfigurer,然後當ConfigurationClassPostProcessor掃描完之後,接着會調用MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法,進行掃描接口。

5.MapperScannerConfigurer類的postProcessBeanDefinitionRegistry方法

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	if (this.processPropertyPlaceHolders) {
	  processPropertyPlaceHolders();
	}
	//1.構建一個ClassPathMapperScanner,並填充相應屬性
	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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
	if (StringUtils.hasText(lazyInitialization)) {
	  scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
	}
	//2.註冊Filter,因爲上面構造函數我們沒有使用默認的Filter,
        //有兩種Filter,includeFilters:要掃描的;excludeFilters:要排除的
	scanner.registerFilters();
	// 3.掃描basePackage,basePackage可通過",; \t\n"來填寫多個,
        // ClassPathMapperScanner重寫了doScan方法
	scanner.scan(
		StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

        第2步,註冊掃描器,就是制定掃描規則,具體看代碼塊6,在3步會使用ClassPathMapperScanner完成掃描,其中ClassPathMapperScanner重寫了doScan方法,具體看代碼塊7

6.ClassPathMapperScanner類的registerFilters方法

public void registerFilters() {
	//是否允許掃描接口
	boolean acceptAllInterfaces = true;

	//1. 如果指定了註解,那麼就將該註解加入到掃描集中
	if (this.annotationClass != null) {
	  addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
	  acceptAllInterfaces = false;
	}

	//2.如果指定了標記接口,則將標記接口添加到includeFilters,
        // 但這邊重寫了matchClassName方法,並返回了false,
        // 相當於忽略了標記接口上的匹配項,所以該參數目前相當於沒有任何作用
	if (this.markerInterface != null) {
	  addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
		@Override
		protected boolean matchClassName(String className) {
		  return false;
		}
	  });
	  acceptAllInterfaces = false;
	}
        // 3.如果沒有指定annotationClass和markerInterface,則添加默認的includeFilters,直接返回true,接受所有類
	if (acceptAllInterfaces) {
	  // default include filter that accepts all classes
	  addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
	}

	// 4.添加默認的excludeFilters,排除以package-info結尾的類
	addExcludeFilter((metadataReader, metadataReaderFactory) -> {
	  String className = metadataReader.getClassMetadata().getClassName();
	  return className.endsWith("package-info");
	});
}

       一般我們都不會執行 annotationClass 和 markerInterface的,也就是會添加默認的 Filter,相當於會接受除了 package-info 結尾的所有類。因此,basePackage 包下的類不需要使用 @Component 註解或 XML 中配置 bean 定義,也會被添加到 IoC 容器中。

7.ClassPathBeanDefinitionScanner類的scan和doScan方法

public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
	//1.進行真正的掃描
	doScan(basePackages);

	// Register annotation config processors, if necessary.
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}
	//2.返回註冊BeanDefinition的個數
	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
	//3.調用父類去掃描指定包下面的類
	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 {
	  //4.對掃描到的beanDefinitions進行處理,在此處會將beanDefinition的beanClass進行替換
	  processBeanDefinitions(beanDefinitions);
	}

	return beanDefinitions;
}

        在第3步會調用父類去掃描制定包下面的類,然後返回掃描到的beanDefinitions集合,這裏父類的掃描是調用Spring框架裏面的類,這裏就不進行分析,只需要知道會返回beanDefinitions集合,在第4步,會對返回的beanDefinitions進行處理,具體看代碼塊8.

8.ClassPathMapperScanner類的processBeanDefinitions方法

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

	  // the mapper interface is the original class of the bean
	  // but, the actual class of the bean is MapperFactoryBean
	  //1.獲取參數構造器的,並未其添加值,其值爲beanClassName--類的全限定名
	  definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
	  //2.設置其BeanClass爲mapperFactoryBeanClass爲mapperFactoryBeanClass,在創建bean時,會根據BeanDefinition的BeanClass
	  //和構造方法進行創建
	  definition.setBeanClass(this.mapperFactoryBeanClass);

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

	  boolean explicitFactoryUsed = false;
	  //3. 因爲sqlSessionFactoryBeanName、sqlSessionTemplateBeanName在@MapperScan中沒有執行,
	  //所以下面的判斷都不會進
	  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)) {
		definition.getPropertyValues().add("sqlSessionTemplate",
			new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
		explicitFactoryUsed = true;
	  } else if (this.sqlSessionTemplate != null) {
		definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
		explicitFactoryUsed = true;
	  }
	  
	  //4.如果前面的sqlSessionFactoryBeanName、sqlSessionTemplateBeanName沒有指定的話,那麼就
	  //設置其注入模型安裝類型進行
	  if (!explicitFactoryUsed) {
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
	  }
	  definition.setLazyInit(lazyInitialization);
	}
}

      其中第2步和第3步就指定了在創建該bean時,需要使用的類和構造方法,其中第2步,指定了需要使用MapperFactoryBean的有參構造器創建,第3步將原來的類,替換成了MapperFactoryBean,使用MapperFactoryBean來創建bean。

      其中第4步就比較牛了,之前看源碼的百思不得其姐,爲什麼MapperFactoryBean父類SqlSessionDaoSupport中的sqlSessionTemplate屬性沒有加@Autowired,創建的MapperFactoryBean對象就能夠將sqlSessionTemplate類自動注入進來,而且我還看多看了幾遍,沒有在任何地方加@Autowired和直接的屬性設置

      至於原因那麼就是BeanDefinition中的注入模式了,Spring中指定了3種注入模式,如果你在BeanDefinition中指定了按照類型進行注入,即使你在屬性上面不加任何Spring的註解,也是可以完成屬性的依賴注入的,這一點以前沒注意,此時,你可能有疑惑,爲什麼我在項目中不加@Autowired,屬性就注入不進來,那是因爲註解類默認使用不注入,這是你只有在屬性上面加@Autowired才能完成依賴注入。

      到此,只是完成了mapper接口->BeanDefinition的轉變,下面還需要完成BeanDefinition->bean的轉變,因爲你已經知道了MapperFactoryBean是一個FactoryBean,使用FactoryBean可以得到一個Mapper的代理對象。

9.Mapper接口的bean的創建

       在Mapper接口bean的創建時,會通過代碼塊8中的第2步和第三步創建一個MapperFactoryBean的實例,使用的是有參構造器。

        注意:Mapper接口bean在創建的時候,創建的是MapperFactoryBean的實例的bean,而不是一個正常的Mapper接口所代理的bean,這一點要記住,關於FactoryBean一定要先看一下:Spring中FactoryBean源碼分析(最詳細),這樣你就知道此時創建的時候,僅僅是創建MapperFactoryBean的實例的bean,而使用的時候纔回去調用MapperFactoryBean的getObject方法,使用的時候即有其他bean依賴了Mapper接口的bean,這時就會去創建使用MapperFactoryBean實例去創建bean,注意且重要。

         先看一下MapperFactoryBean的源碼:

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

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    // intentionally empty
  }

    //實例化時,調用該構造器,在代碼塊8中已經講到了
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

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

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

  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }
  
  @Override
  public boolean isSingleton() {
    return true;
  }
  
  public void setMapperInterface(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public void setAddToConfig(boolean addToConfig) {
    this.addToConfig = addToConfig;
  }

  public boolean isAddToConfig() {
    return addToConfig;
  }
}

//SqlSessionDaoSupport抽象類
public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSessionTemplate sqlSessionTemplate;

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }

  @SuppressWarnings("WeakerAccess")
  protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionTemplate(sqlSessionFactory);
  }

  public final SqlSessionFactory getSqlSessionFactory() {
    return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
  }

  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSessionTemplate = sqlSessionTemplate;
  }

  public SqlSession getSqlSession() {
    return this.sqlSessionTemplate;
  }

  public SqlSessionTemplate getSqlSessionTemplate() {
    return this.sqlSessionTemplate;
  }

  @Override
  protected void checkDaoConfig() {
    notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  }

}

       這裏我想講的是在創建MapperFactoryBean進行依賴注入時,會根據類型進行注入,此時,即使屬性中沒有加@Autowired註解也是需要進行屬性注入的,只不過要滿足幾個條件:1.屬性有set方法  2.不是基本類型  3.沒有對屬性進行手動賦值,其具體代碼可以看Spring的AbstractAutowireCapableBeanFactory#populateBean ->AbstractAutowireCapableBeanFactory#autowireByType -> AbstractAutowireCapableBeanFactory#unsatisfiedNonSimpleProperties,感興趣的同學可以自己去查看源碼,我這裏就不介紹。

       對於MapperFactoryBean滿足依賴注入的有sqlSessionTemplate和sqlSessionFactory,這一點我比較好奇,不是隻有隻有sqlSessionTemplate一個屬性嗎?這一點在寫文章的時候才發現,以後再查找原因,反正現在知道是安裝方法進行查找的,如果有知道的同學告訴我一下。

      如上圖,在創建addressMapper時,查找到的需要進行屬性依賴注入的確實是2個。

      此時,Spring容器會去查找個兩個bean,並進行依賴注入。

10.sqlSessionFactory bean的創建

       sqlSessionFactory是一個方法bean,其會獲取到Spring的application.properties的配置文件中關於mybatis的配置。關於怎麼獲取的可以自行,SpringBoot自動配置原理,現在查看一下sqlSessionFactory方法:

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
	SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
	factory.setDataSource(dataSource);
	factory.setVfs(SpringBootVFS.class);
	//1.獲取配置文件中設置的mybatis配置文件的路徑
	if (StringUtils.hasText(this.properties.getConfigLocation())) {
	  factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
	}
	applyConfiguration(factory);
	//2.獲取配置文件中設置的mybatis配置文件的路徑
	if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
	  factory.setMapperLocations(this.properties.resolveMapperLocations());
	}
	
	//忽略其他配置的代碼

	return factory.getObject();
}

@Override
public SqlSessionFactory getObject() throws Exception {
	//如果沒有sqlSessionFactory,則進行創建
	if (this.sqlSessionFactory == null) {
	  afterPropertiesSet();
	}
	return this.sqlSessionFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
	notNull(dataSource, "Property 'dataSource' is required");
	notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
        //3.創建sqlSessionFactory工廠
	this.sqlSessionFactory = buildSqlSessionFactory();
}

      讀取application.properties中關於mybatis設置的參數,然後執行第3步去創建SqlSessionFactory對象,具體代碼看代碼塊11.

 11.SqlSessionFactoryBean類的buildSqlSessionFactory方法

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    final Configuration targetConfiguration;
    
    //省略部分代碼
    
    //1.解析mapper文件,很重要的
    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        //2.遍歷所有的mapper文件
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            //3.解析mapper xml文件
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }
    //4.使用targetConfiguration構建DefaultSqlSessionFactory,熟悉mybatis的同學應該知道,在不進行整合,只使用Mybatis的使用
    //會這個創建sqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

       構建SqlSessionFactory 的關鍵就是構建Configuration對象,該Configuration對應着mybatis的配置文件,其結構幾乎相同,就是將xml轉化爲Java對象的過程,其中是會配置進行解析的,其可以參考一位大神寫的:《深入理解mybatis原理》 Mybatis初始化機制詳解

      其中第1步就是相當於解析mybatis的配置文件中的mappers節點,關於第3步就是解析每一個mapper文件,具體代碼看代碼塊12.

12.XMLMapperBuilder類的parse方法

public void parse() {
	//1.判斷是否已經解析過
	if (!configuration.isResourceLoaded(resource)) {
	  //2.解析mapper文件,從mapper節點開始
	  configurationElement(parser.evalNode("/mapper"));
	  //3.將resource添加到已加載列表
	  configuration.addLoadedResource(resource);
	  //4.綁定namespace的mapper
	  bindMapperForNamespace();
	}

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

      第2步解析mapper文件,具體看代碼塊13

      第4步綁定namespace的mapper,具體看代碼塊15

13.XMLMapperBuilder類的configurationElement方法

private void configurationElement(XNode context) {
  try {
    // 1.獲取namespace屬性
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    // 2.設置currentNamespace屬性
    builderAssistant.setCurrentNamespace(namespace);
    // 3.解析parameterMap、resultMap、sql等節點
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    // 4.解析增刪改查節點,封裝成Statement
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}
 
private void buildStatementFromContext(List<XNode> list) {
  if (configuration.getDatabaseId() != null) {
    buildStatementFromContext(list, configuration.getDatabaseId());
  }
  // 解析增刪改查節點,封裝成Statement
  buildStatementFromContext(list, null);
}
 
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    // 1.構建XMLStatementBuilder
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      // 2.解析節點
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }

      解析Mapper文件,逐步解析Mapper文件中的子節點,關鍵是解析select|insert|update|delete節點,然後封裝Statement對象,然後使用statementParser.parseStatementNode();進行解析select|insert|update|delete節點,具體代碼看代碼塊14,

      這邊每個 XNode 都相當於如下的一個 SQL,下面封裝的每個 MappedStatement 可以理解就是每個 SQL。

<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from address
    where address_id = #{addressId,jdbcType=VARCHAR}
  </select>

14: XMLStatementBuilder類的parseStatementNode方法

public void parseStatementNode() {
	
	//忽略解析過程,就是解析出select|insert|update|delete節點有的屬性如id、parameterType、resultMap等屬性

	//1.添加MapperStatement
	builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
			fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
			resultSetTypeEnum, flushCache, useCache, resultOrdered,
			keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

//MapperBuilderAssistant#addMappedStatement
public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }
    
    // 2.將id填充上namespace,例如:selectByPrimaryKey變成
    // it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey
    id = applyCurrentNamespace(id, false);
    //3.判斷是否爲select節點
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    //4.使用參數構建MappedStatement.Builder
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }
    //5.構建MappedStatement
    MappedStatement statement = statementBuilder.build();
    //6.添加到MappedStatement集合中(重要)
    configuration.addMappedStatement(statement);
    return statement;
}

//Configuration#addMappedStatement
public void addMappedStatement(MappedStatement ms) {
    //7.添加到可以看到mappedStatements緩存中,可以看到是以id位key, MappedStatement爲value進行存儲
    //而id是it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey  namespace + select|insert|update|delete節點的id
    mappedStatements.put(ms.getId(), ms);
}

       這三個方法所完成的目標爲:解析elect|insert|update|delete節點,構建出一個MappedStatement,然後以id爲key,id例如it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey,MappedStatement爲value保存到mappedStatements集合中。

15.XMLMapperBuilder類的bindMapperForNamespace方法

private void bindMapperForNamespace() {
	//1.獲取當前解析的Namespace
	String namespace = builderAssistant.getCurrentNamespace();
	if (namespace != null) {
		Class<?> boundType = null;
		try {
			//2.根據namespace去加載類,可以看出來,如果namespace不是全限定類名就會找不到,
			//納悶爲啥不知道報錯
			boundType = Resources.classForName(namespace);
		} catch (ClassNotFoundException e) {
			//ignore, bound type is not required
		}
		if (boundType != null) {
			if (!configuration.hasMapper(boundType)) {
			  // Spring may not know the real resource name so we set a flag
			  // to prevent loading again this resource from the mapper interface
			  // look at MapperAnnotationBuilder#loadXmlResource
			  configuration.addLoadedResource("namespace:" + namespace);
			  //3.如果不存在以該Class爲key的mapper,則進行添加
			  configuration.addMapper(boundType);
			}
		}
	}
}
//Configuration#addMapper
public <T> void addMapper(Class<T> type) {
	mapperRegistry.addMapper(type);
}
//MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
	//4.判斷是否爲接口,不爲接口,則直接忽略了
	if (type.isInterface()) {
		if (hasMapper(type)) {
			throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
		}
		boolean loadCompleted = false;
		try {
			// 將type和以該type爲參數構建的MapperProxyFactory作爲鍵值對,
			// 放到knownMappers緩存中去
			knownMappers.put(type, new MapperProxyFactory<>(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.
			MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
			parser.parse();
			loadCompleted = true;
	  } finally {
		if (!loadCompleted) {
			knownMappers.remove(type);
		}
	  }
	}
}

         主要是將剛剛解析過的 mapper 文件的 namespace 放到 knownMappers 緩存中,key 爲 namespace 對應的 class,value 爲 MapperProxyFactory。

         到此也就完成了對Mybatis的解析,然後生成一個Configuration類,放到sqlSessionFactory裏面返回給代碼塊9中依賴sqlSessionFactory的bean,也就是MapperFactoryBean bean了。

文中總結:

         到此關於SpringBoot與Mybatis整合的源碼分析已經進行一大半了,現在進行總結一下:

         代碼塊1-8是根據在@MapperScan中配置Mapper接口所在路徑,根據Mybatis自定義的BeanDefinitionRegistryPostProcessor的實現類進行掃描接口,在掃描之後會返回BeanDefinition,此時將所有的BeanDefinition的beanClass都給替換成MapperFactoryBean.class,然後指定使用有參構造方法創建bean,並制定注入模型按照類型注入,因爲要爲MapperFactoryBean的父類屬性sqlSessionFactory進行復制。

         代碼塊9-15主要講的是在完成Mapper bean的創建工作時,會首先創建Mapper bean所依賴的sqlSessionFactory,其中大部分篇幅主要講的就是sqlSessionFactory的創建,其創建完成就是mybatis的配置文件、mapper文件裏面都已經解析過了,關鍵是在解析mapper文件時,會將每一個select、delete、insert、update節點都封裝成一個MapperStatement對象然後保存到Configuration對象的一個Map,其中key爲namespace + select、delete、insert、update節點的id,例如:it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey,  value就爲select、delete、insert、update節點對應的MapperStatement對象,然後將解析過的 mapper 文件的 namespace 放到 knownMappers 緩存中,key 爲 namespace 對應的 class,value 爲 MapperProxyFactory。

        代碼塊9-15主要是構建sqlSessionFactory的過程,在構建完成後,會將sqlSessionFactory返回給依賴它的Mapper bean,繼續完成Mapper bean的創建,在構建完成Mapper bean之後,就可以使用。

                    

        當有Service層代碼依賴Mapper bean的接口,如上圖,就會去Spring 容器中查找這個bean,我們在1-15中已經完成了addressMapper bean的創建,根據之前知道addressMapper  bean是是以MapperFactoryBean類創建的,所以是一個FactoryBean的示例,在不加“&”符號的時候,獲取的是FactoryBean所包裹的對象,關於FactoryBean可以參考:Spring中FactoryBean的使用

        下面就開始MapperFactoryBean的分析

16.MapperFactoryBean類的getObject方法

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

//org.mybatis.spring.support.SqlSessionDaoSupport#getSqlSession
public SqlSession getSqlSession() {
	return this.sqlSessionTemplate;
}

//org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
@Override
public <T> T getMapper(Class<T> type) {
	return configuration.getMapper(type, this);
}

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

//org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	// 1.從knownMappers緩存中獲取
	final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
	if (mapperProxyFactory == null) {
		throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
	}
	try {
		// 2.新建實例
		return mapperProxyFactory.newInstance(sqlSession);
	} catch (Exception e) {
		throw new BindingException("Error getting mapper instance. Cause: " + e, e);
	}
}

//org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
public T newInstance(SqlSession sqlSession) {
	// 3.構造一個MapperProxy
	final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
	// 4.使用MapperProxy來構建實例對象
	return newInstance(mapperProxy);
}

//org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>)
protected T newInstance(MapperProxy<T> mapperProxy) {
	// 5.使用JDK動態代理來代理要創建的實例對象,InvocationHandler爲mapperProxy,
    // 因此當我們真正調用時,會走到mapperProxy的invoke方法
	return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
  

        通過 mapperInterface 從 knownMappers 緩存中獲取到 MapperProxyFactory 對象,然後使用 JDK 動態代理創建 MapperProxyFactory 實例對象,InvocationHandler 爲 MapperProxy。

        此時代碼塊16完成了從FactoryBean中獲取真實的bean,此時也就完成了addressMapper的創建和依賴addressMapper的其他類的屬性注入。

        當調用addressMapper的方法時,就會調用MapperProxy 的 invoke 方法,具體看代碼塊17.

17.MapperProxy類的invoke方法

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //1.判斷是否Object類的方法,如果是不進行操作
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (method.isDefault()) {  //2.判斷是否是JDK8中新增的默認方法
        if (privateLookupInMethod == null) {
          return invokeDefaultMethodJava8(proxy, method, args);
        } else {
          return invokeDefaultMethodJava9(proxy, method, args);
        }
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //3.獲取緩存的MapperMethod方法,如果沒有則創建
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //4.執行mapperMethod的execute方法
    return mapperMethod.execute(sqlSession, args);
  }  
  
  //org.apache.ibatis.binding.MapperProxy#cachedMapperMethod
  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method,
        k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

         MapperMethod這個類主要用來描述一個Mapper 文件中的select、update、delete、insert節點的信息、如id,此時的id是從MapperStateMent中取出的,所以格式爲:it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey ,然後是節點的類型,是select、update、delete、insert的一種。

        在第4步中,會執行MapperMethod的execute方法,具體看代碼塊18.

18.MapperMethod類的execute方法

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //1.根據節點類型進行不同的操作
    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;
        //2.以查詢結果爲List進行分析
        } 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;
  }
  
  //org.apache.ibatis.binding.MapperMethod#executeForMany
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      //3.此時就進入了mybatis處理的邏輯了,到這裏算是從Mapper接口道xml文件的映射了
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

        根據方法返回值和select、update、delete、insert節點的信息查找出對應的操作是插入、查詢、更新還是刪除操作,然後根據參數和MapperStatement的Id(command.getName()),通過sqlSession來完成數據路的操作,對於sqlSession是mybatis對Connection的封裝,可以參考大神的博客:《深入理解mybatis原理》 MyBatis的架構設計以及實例分析

       到此SpringBoot與Mybatis整合的源碼算是分析完了,能堅持看下去真不容器,現在進行最後總結。

最後總結:

         在SpringBoot項目啓動之後,在解析啓動類上面的註解時,獲取@MapperScan註解上面配置的信息,然後向Spring容器中導入一個mybatis自定義的BeanDefinitionRegistryPostProcessor的實現類進行掃描註定包下面的接口,在Spring調用bean工廠的後期處理器時,會調用這個mybatis自定義的BeanDefinitionRegistryPostProcessor的實現類,進行掃描指定包然後返回掃描到的BeanDefinition集合,此時,mybatis會對每一個BeanDefinition做一些特殊的處理,改變BeanDefinition的class對象爲一個MapperFactoryBean的FactoryBean,然後指定使用有參構造方法創建bean,並制定注入模型按照類型注入,因爲要爲MapperFactoryBean的父類屬性sqlSessionFactory進行復制。

         在完成Mapper bean的創建工作時,會首先創建Mapper bean所依賴的sqlSessionFactory,sqlSessionFactory創建完成就相當於mybatis的配置文件、mapper文件裏面都已經解析過了,關鍵是在解析mapper文件時,會將每一個select、delete、insert、update節點都封裝成一個MapperStatement對象然後保存到Configuration對象的一個Map,其中key爲namespace + select、delete、insert、update節點的id,例如:it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey,  value就爲select、delete、insert、update節點對應的MapperStatement對象,然後將解析過的 mapper 文件的 namespace 放到 knownMappers 緩存中,key 爲 namespace 對應的 class,value 爲 MapperProxyFactory。

        在構建sqlSessionFactory完成後,會將sqlSessionFactory返回給依賴它的Mapper bean,繼續完成Mapper bean的創建,在構建完成Mapper bean之後,就可以使用,在使用這個Mapper bean的時候,會調用其代理對象的invoke方法,然後根據此方法和Class類,去得到StatementId,然後最終會根據StatementId去查找MapperStatement對象並執行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章