問題:
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對象並執行。