问题:
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对象并执行。