承接前文Spring mybatis源码篇章-Mybatis的XML文件加载,本文将在前文的基础上讲解Spring在Mybatis整合方面的另一动作

前话

根据前文的分析可得到以下结论

  1. MappedStatement是mybatis操作sql语句的持久层对象,其id由注解模式的${mapperInterface类全名}.${methodName}或者XML模式的${namespace}.${CRUD标签的id}确定,且是唯一的

  2. Mybatis对每个CRUD语句都会生成唯一的MappedStatement保存至ConfigurationmappedStatements(Map集合)中

  3. Mybatis提供注解模式和XML模式生成MappedStatement,在两者同时存在的情况下,前者会覆盖后者

  4. XML模式生成的MappedStatement,是保存在org.apache.ibatis.session.Configuration对象中的mappedStatements属性中(Map),并且也将${namespace}对应的DAO类存放至mapperRegistry属性里,供外部调用。

本文主要分析的是第四点,因为第四点的mapperRegistry类有一个特别的方法

  // SqlSession就是关键
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {}

我们都知道Spring在整合Mybatis的时候都会配置一个SqlSessionFactoryBean对象来生成一个SqlSessionFactory,而这个SqlSessionFactory就是作为SqlSession(数据库会话)的关键部分,那么Spring又怎么把这个对象与DAO接口类关联放在mapperRegistry里呢????

老方式

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.test.sqlmapper.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

上述的配置是针对单个的mapperInterface注入到应用程序中,试想如果有很多的接口则会导致Spring主配置文件臃肿,所以上述的办法已过时

新方式

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.mybatis.spring.sample.mapper" />
<!-- optional unless there are multiple session factories defined -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>

采用MapperScannerConfigurer扫描类来实现对basePackage指定的DAO接口集合统一关联SqlSession,这就节省了之前老配置很多的代码空间。

MapperScannerConfigurer

直接一睹MapperScannerConfigurer类的源码风采,优先查看其内部属性

    public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

      private String basePackage;

      private boolean addToConfig = true;

      private SqlSessionFactory sqlSessionFactory;

      private SqlSessionTemplate sqlSessionTemplate;

      private String sqlSessionFactoryBeanName;

      private String sqlSessionTemplateBeanName;

      private Class<? extends Annotation> annotationClass;

      private Class<?> markerInterface;

      private ApplicationContext applicationContext;

      private String beanName;

      private boolean processPropertyPlaceHolders;

      private BeanNameGenerator nameGenerator;
....
}

其源码上的注释其实写的很清楚了,注释篇幅过长,就不在这里展示了,稍微提下这个类的相关使用:

  • basePackage 基本属性,接口类扫描的包路径,支持,;分隔

  • sqlSessionFactoryBeanName 当有多个SqlSessionFactory环境时,官方通过其来指定加载特定的sqlSessionFactory,value即为bean的id值(建议使用此属性)

  • sqlSessionFactoty 默认是不用填的,其会去寻找id为sqlSessionFactory的sqlSessionFactory实例,sqlSessionTemplate的操作类似

  • annotationClass 注解类,其会去Spring环境下寻找拥有此注解的接口类,并忽略basePackage的属性,默认为null

  • markerInterface 父类接口类,其会去寻找继承此接口类的子接口类但不包括父类接口,并忽略basePackage的属性,与annotationClass并存,默认为null

MapperScannerConfigurer#postProcessBeanDefinitionRegistry()

直接进入其关键方法,观察下是如何进行扫描的

  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//支持${basePackage}形式
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//base classpath environment to scan
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);
//base annotationClass and markerInterface properties to register interface filters
scanner.registerFilters();
// scan
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

上述代码也就是设置相应的属性给ClassPathMapperScanner,具体的如何扫描我们继续往下看

ClassPathBeanDefinitionScanner

扫描的具体操作由ClassPathMapperScanner的父类ClassPathBeanDefinitionScanner来完成,我们简单的看下其中的逻辑

	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
// 找寻classpath对应的目录,其中的解析帮助类为PathMatchingResourcePatternResolver
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// 遍历
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
// 设置基本的属性,比如lazy-init/autowire-mode等
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
// 对针对@mapper注解的bean
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 确保不重复注册到bean工厂
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
  • 上述代码最关键的便是找寻对应目录的所有interface接口,其是通过PathMatchingResourcePatternResolver帮助类来完成的,后续笔者独立成篇加以分析

  • 对找寻的beanDefinitions集合过程中也会进行filters过滤,即用到了annotationClassmarkerInterface属性

ClassPathMapperScanner

针对上述的符合条件后获取的beanDefinitions集合,子类便要进行最后的加工处理

  /**
* Calls the parent search that will search and register all the candidates.
* Then the registered objects are post processed to set them as
* MapperFactoryBeans
*/
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//由父类去找到符合条件的interface类,并转化为bean类
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 {
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
} // the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
//最终将definition包装成MapperFactoryBean,beanClass设置为其内部属性MapperInterface
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false;
//根据sqlsessionFactoryBeanName寻找运行状态的SqlsessionFactory的虚引用,但并没有去真实加载
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)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
//当没有指定SqlSession对象,则设置MapperFactoryBean自动去找寻
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
} return beanDefinitions;
}

根据上述代码得知,其最终被包装的类为MapperFactoryBean,由其完成最终的关联

MapperFactoryBean

笔者此处只关注其关键方法checkDaoConfig(),源码如下

  /**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
super.checkDaoConfig(); notNull(this.mapperInterface, "Property 'mapperInterface' is required"); Configuration configuration = getSqlSession().getConfiguration();
// 判断mapperInterface接口是否已被注册过
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 此处就跟mybatis加载主配置文件时对mapper节点指定package属性或者mapperClass属性的入口是一样的;xml方式的加载也是会走这一步
configuration.addMapper(this.mapperInterface);
} catch (Throwable t) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
throw new IllegalArgumentException(t);
} finally {
ErrorContext.instance().reset();
}
}
}

上述主要是调用Configuration#addMapper()方法来将相应的DAO接口放入mapperRegistry中的knownMappers属性,我们可以看下这个属性的类型

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

恩,有动态代理的味道~~~

怎么取相应的DAO接口呢供外界调用呢,就是getObejct()方法

  public T getObject() throws Exception {
// 间接关联了SqlSession与mapperInterface
return getSqlSession().getMapper(this.mapperInterface);
}

通过上述的代码便调用了上文所提及问题的mapperRegistry#getMapper()方法了,但具体的如何调用我们后文分析~~

总结

作下简单的小结

  1. MapperScannerConfigurer类主要实现将basePackage包下的所有接口类都包装成MapperFactoryBean对象,内含相应的SqlSessionFactory数据库会话

  2. MapperScannerConfigurer类默认情况下在形成MappedStatement的过程中会优先去找寻与接口同目录下的XML文件来加载生成。这点与Mybatis的主文件加载方式类同~~~只不过其更方便

  3. MapperScannerConfigurer一般结合sqlSessionFactoryBeanmapperLocations属性来完成Mybatis主文件的mappers的工作~~

  4. 官方以及笔者推荐大家使用MapperScannerConfigurer与SqlSessionFactoryBean搭配使用~~~~

Spring mybatis源码篇章-MapperScannerConfigurer关联dao接口的更多相关文章

  1. Spring mybatis源码篇章-sql mapper配置文件绑定mapper class类

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-MybatisDAO文件解析(二) 背景知识 MappedStatement是mybatis操作sql ...

  2. Spring mybatis源码篇章-MybatisDAO文件解析(二)

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-MybatisDAO文件解析(一) 默认加载mybatis主文件方式 XMLConfigBuilder ...

  3. Spring mybatis源码篇章-MybatisDAO文件解析(一)

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-SqlSessionFactory 加载指定的mybatis主文件 Mybatis模板文件,其中的属性 ...

  4. Spring mybatis源码篇章-Mybatis的XML文件加载

    通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-Mybatis主文件加载 前话 前文主要讲解了Mybatis的主文件加载方式,本文则分析不使用主文件加载方式 ...

  5. Spring mybatis源码篇章-Mybatis主文件加载

    通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-SqlSessionFactory 前话 本文承接前文的内容继续往下扩展,通过Spring与Mybatis的 ...

  6. Spring mybatis源码篇章-NodeHandler实现类具体解析保存Dynamic sql节点信息

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-XMLLanguageDriver解析sql包装为SqlSource SqlNode接口类 publi ...

  7. Spring mybatis源码篇章-动态SQL节点源码深入

    通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-动态SQL基础语法以及原理 前话 前文描述到通过mybatis默认的解析驱动类org.apache.ibat ...

  8. Spring mybatis源码篇章-XMLLanguageDriver解析sql包装为SqlSource

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-MybatisDAO文件解析(二) 首先了解下sql mapper的动态sql语法 具体的动态sql的 ...

  9. Spring mybatis源码篇章-动态SQL基础语法以及原理

    通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-Mybatis的XML文件加载 前话 前文通过Spring中配置mapperLocations属性来进行对m ...

随机推荐

  1. JavaWeb三大组件之Filter

    对请求或者响应进行拦截,并做额外的逻辑处理 filter能在一个请求访问目标资源之前对其进行拦截然后做某些逻辑处理,例如权限检查,也可以在一个输出响应到达客户端之前对其进行拦截并做一些额外的操作(例如 ...

  2. Python TVTK 标量数据可视化与矢量数据可视化,空间轮廓线可视化

    Python数据可视化分为 标量可视化,矢量可视化,轮廓线可视化 标量又称无向量,只有大小没有方向,运算遵循代数运算法则比如质量,密度,温度,体积,时间 矢量又称向量,它是由大小,方向共同确定的量,运 ...

  3. 换PHP7后访问Apache虚拟站点Forbidden的问题解决

    Httpd.conf中,注释掉前2行,补上后2行 <Directory /> #AllowOverride none #Require all denied Order deny,allo ...

  4. Nginx 教程(3):SSL 设置

    SSL 和 TLS SSL(Socket Secure Layer 缩写)是一种通过 HTTP 提供安全连接的协议. SSL 1.0 由 Netscape 开发,但由于严重的安全漏洞从未公开发布过.S ...

  5. ANDROID说说对MENU的理解

    ANDROID%E5%88%9D%E5%AD%A6%E4%B9%8B%E7%AE%80%E6%98%93%E8%AE%A1%E7%AE%97%E5%99%A8 Ѿ�����ڴ����㺰ô�

  6. NGUI_创建图集Altas

    在project面板下创建一个名为Textures的folder,把事先准备好的贴图素材直接拖到Textures下 2.导航栏NGUI处打开atlas 3.project下新建一个atlas的文件夹, ...

  7. Gson centos日期转换失败

    https://4aiur.github.io/2018/03/26/gson-dateformat-pattern/ 问题描述: 线上的日志里报了一个JsonSyntaxException的异常: ...

  8. 深入理解JVM(二)——内存模型、可见性、指令重排序

    上一篇我们介绍了JVM的基本运行流程以及内存结构,对JVM有了初步的认识,这篇文章我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况. 内存 ...

  9. 【javascript】谈谈HTML5: Web-Worker、canvas、indexedDB、拖拽事件

    前言:作为一名Web开发者,可能你并没有对这个“H5”这个字眼投入太多的关注,但实际上它早已不知不觉进入到你的开发中,并且总有一天会让你不得不正视它,了解它并运用它   打个比方:<海贼王> ...

  10. Javascript高级编程学习笔记(45)——DOM 操作表格及DOM动态集合

    操作DOM表格 早些时候,HTML 还是以表格布局为主, 所以DOM操作表格是比较重要的一点 但是现如今 有其它的选择,所以表格的操作也就慢慢地淡出了人们的视线 所以这里也就不过多去详细展开,这里也就 ...