如果有成百上千个dao接口呢,那我们岂不是要配置添加成百上千个bean,当然不是这样,spring还为MyBatis添加了拓展的功能,可以通过扫描包目录的方式,添加dao,让我看看具体使用和实现。

<!-- 去掉该配置
<bean id="personDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="net.itaem.dao.PersonDao"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
-->
<!-- 如果 net.itaem.dao 包下面有很多dao需要注册,那么可以使用这种扫描的方式添加dao-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="net.itaem.dao"/>
</bean>

我们屏蔽掉了最原始的代码(userMapper 的创建)而增加了MapperScannerConfigurer的配置,basePackage属性是让你为映射器接口文件设置基本的包路径。你可以使用分号或逗号作为分隔符设置多于一个的包路径。每个映射器将会在指定的包路径中递归地被搜索到。被发现的映射器将会使用Spring对自动侦测组件默认的命名策略来命名。也就是说,如果没有发现注解,它就会使用映射器的非大写的非完全限定类名。但是如果发现了@Component或JSR-330@Named注解,它会获取名称。

public void afterPropertiesSet() throws Exception {
  notNull(this.basePackage, "Property 'basePackage' is required");
}

afterPropertiesSet()方法除了一句对basePackage属性的验证代码外并没有太多的逻辑实现。

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,如果MapperScannerConfigurer实现了该接口,那么说明在application初始化的时候该接口会被调用,具体实现,让我先看看:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
} 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.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
这里我们重点关注三个主要的方法,分别是
processPropertyPlaceHolders(); 
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

processPropertyPlaceHolders属性的处理

执行属性的处理,简单的说,就是把xml中${XXX}中的XXX替换成属性文件中的相应的值

  private void processPropertyPlaceHolders() {
Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class); if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) {
BeanDefinition mapperScannerBean = ((GenericApplicationContext) applicationContext)
.getBeanFactory().getBeanDefinition(beanName); // PropertyResourceConfigurer does not expose any methods to explicitly perform
// property placeholder substitution. Instead, create a BeanFactory that just
// contains this mapper scanner and post process the factory.
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition(beanName, mapperScannerBean); for (PropertyResourceConfigurer prc : prcs.values()) {
prc.postProcessBeanFactory(factory);
} PropertyValues values = mapperScannerBean.getPropertyValues(); this.basePackage = updatePropertyValue("basePackage", values);
this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
}
}

BeanDefinitionRegistries会在应用启动的时候调用,并且会早于BeanFactoryPostProcessors的调用,这就意味着PropertyResourceConfigurers还没有被加载所有对于属性文件的引用将会失效。为避免此种情况发生,此方法手动地找出定义的PropertyResourceConfigurers并进行提前调用以保证对于属性的引用可以正常工作。

根据配置属性生成过滤器

scanner.registerFilters();方法会根据配置的属性生成对应的过滤器,然后这些过滤器在扫描的时候会起作用。

  public void registerFilters() {
boolean acceptAllInterfaces = true;
//对于annotationClass属性的处理
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
//对于markerInterface属性的处理
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
}
//不扫描package-info.java文件
addExcludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
}

代码中得知,根据之前属性的配置生成了对应的过滤器。

(1)annotationClass属性处理。

如果annotationClass不为空,表示用户设置了此属性,那么就要根据此属性生成过滤器以保证达到用户想要的效果,而封装此属性的过滤器就是AnnotationTypeFilter。AnnotationTypeFilter保证在扫描对应Java文件时只接受标记有注解为annotationClass的接口。

(2)markerInterface属性处理。

如果markerInterface不为空,表示用户设置了此属性,那么就要根据此属性生成过滤器以保证达到用户想要的效果,而封装此属性的过滤器就是实现AssignableTypeFilter接口的局部类。表示扫描过程中只有实现markerInterface接口的接口才会被接受。

(3)全局默认处理。

在上面两个属性中如果存在其中任何属性,acceptAllInterfaces的值将会改变,但是如果用户没有设定以上两个属性,那么,Spring会为我们增加一个默认的过滤器实现TypeFilter接口的局部类,旨在接受所有接口文件。

(4)package-info.java处理。

对于命名为package-info的Java文件,默认不作为逻辑实现接口,将其排除掉,使用TypeFilter接口的局部类实现match方法。

从上面的函数我们看出,控制扫描文件Spring通过不同的过滤器完成,这些定义的过滤器记录在了includeFilters和excludeFilters属性中。

扫描java文件

scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

  public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
//如果配置了includeAnnotationConfig,则注册对应注解的处理器以保证注解功能的正常使用。
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
//scan是个全局方法,扫描工作通过doScan(basePackages)委托给了doScan方法,同时,还包括了includeAnnotationConfig属性的处理,
//AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)代码主要是完成对于注解处理器的简单注册,
//比如AutowiredAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor等,这里不再赘述,我们重点研究文件扫描功能的实现。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
//如果没有扫描到任何文件发出警告
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
//扫描basePackage路径下java文件
for (String basePackage : basePackages) {
//如果当前bean是用于生成代理的bean那么需要进一步处理
//findCandidateComponents方法根据传入的包路径信息并结合类文件路径拼接成文件的绝对路径,
//同时完成了文件的扫描过程并且根据对应的文件生成了对应的bean,
    //使用ScannedGenericBeanDefinition类型的bean承载信息,bean中只记录了resource和source信息。
//我们更感兴趣的是isCandidateComponent(metadataReader),此句代码用于判断当前扫描的文件是否符合要求,
    //而我们之前注册的一些过滤器信息也正是在此时派上用场的。
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
//解析scope属性
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
//如果是AnnotatedBeanDefinition类型的bean,需要检测下常用注解如:Primary、Lazy等
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;
}
该方法主要做了以下操作:
1)扫描basePackage下面的java文件
2)解析扫描到的java文件
3)调用各个在上一步骤注册的过滤器,执行相应的方法。
4)为解析后的java注册bean,注册方式采用编码的动态注册实现。
5)构造MapperFactoryBean的属性,mapperInterface,sqlSessionFactory等等,填充到BeanDefinition里面去。
做完这些,MapperFactoryBean对象也就构造完成了,扫描方式添加dao的工作也完成了。

总结

其实了解了Spring整合MyBatis的流程,我们也就大体知道Spring整合一些框架所使用的扩展方法,不过大多是都是通过继承接口的方式,然后通过spring回调该接口的方式,实现我们自己想要的扩展逻辑,所以了解spring提供的一些扩展的接口以及抽象类是扩展的关键,就像InitializingBean,BeanDefinitionRegistryPostProcessor这些接口,知道了这些接口调用的方式,以及上面时候会调用,我们就可以知道,我们需要扩展的功能应该实现哪个接口,或者集成哪个抽象类。

SpringMyBatis解析4-MapperScannerConfigurer的更多相关文章

  1. SpringMyBatis解析2-SqlSessionFactoryBean

    通过分析整合示例中的配置文件,我们可以知道配置的bean其实是成树状结构的,而在树的最顶层是类型为org.mybatis.spring.SqlSessionFactoryBean的bean,它将其他相 ...

  2. SpringMyBatis解析3-MapperFactoryBean

    在使用mybatis的时候,我们获取dao的方式一般是这样: SqlSession session=sessionFactory.openSession(); PersonDao personDao= ...

  3. SpringMyBatis解析1-使用示例

    MyBatis使用介绍 MyBatis的详细使用介绍  http://www.cnblogs.com/xrq730/category/796495.html 建立PO public class Per ...

  4. maven插件mybatis-generator生成代码配置

    鸣谢:http://my.oschina.net/u/1763011/blog/324106?fromerr=nJakGh4P (也可参看此博客进行配置) http://www.cnblogs.com ...

  5. Mybatis-Plus BaseMapper自动生成SQL及MapperProxy

    目录 Spring+Mybatis + Mybatis-Plus 自定义无XML的sql生成及MapperProxy代理生成 问题产生背景 框架是如何使用 无Xml的SQL是如何生成生成及SQL长成什 ...

  6. Mybaits 源码解析 (十)----- 全网最详细,没有之一:Spring-Mybatis框架使用与源码解析

    在前面几篇文章中我们主要分析了Mybatis的单独使用,在实际在常规项目开发中,大部分都会使用mybatis与Spring结合起来使用,毕竟现在不用Spring开发的项目实在太少了.本篇文章便来介绍下 ...

  7. Mybatis jpa mini 代码解析

    源码地址(git):https://github.com/LittleNewbie/mybatis-jpa 一.Mybatis简介 mybatis中文官方文档:http://www.mybatis.o ...

  8. SpringBoot 源码解析 (九)----- Spring Boot的核心能力 - 整合Mybatis

    本篇我们在SpringBoot中整合Mybatis这个orm框架,毕竟分析一下其自动配置的源码,我们先来回顾一下以前Spring中是如何整合Mybatis的,大家可以看看我这篇文章Mybaits 源码 ...

  9. Hello Mybatis 04 使用spring-mybatis

    顺着上一篇,这里介绍下spring-mybatis的配置. 我们使用mybatis去操作数据库的时候,每次都要不停地openSession,closeSession好烦躁哇--这样工作哪里有效率可言! ...

随机推荐

  1. JS点击灯泡变亮(学自W3school)

    JS学习笔记1(学自W3school)        function changeImage()  {  element = document.getElementByIdx_x('myimage' ...

  2. asp.net 后台获取input的值

    前台:<input id="test" value="" runat="server" /> 只要架上runat="s ...

  3. 3ds max输出图片

    通过渲染菜单调出改窗口,然后调整成这种模式,就能渲染出这种效果的图. 可以调背景色: 全局照明:染色,这个控制的是渲染物体的颜色

  4. c语言中的浮点数

    一.浮点数常量(小数) 0.11L, 0.0f ,0.0,1.88,2.5f ,0.188E1 E3表示103        比如 1.88E 3=1.88*1000=1880.0f E-3表示10- ...

  5. poj1988(并查集)

    题目链接:http://poj.org/problem?id=1988 题意:有n个箱子,初始时每个箱子单独为一列: 接下来有p行输入,M, x, y 或者 C, x: 对于M,x,y:表示将x箱子所 ...

  6. 苹果官方制作MAC OS的启动U盘的步骤

    工具/原料 一个8G或者更大容量的U盘 MAC OS系统镜像DMG文件 方法/步骤 1.打开应用程序 - 使用工具里的磁盘工具,将U盘格式化为MAC OS扩展日志式,名称输入Mavericks,并创建 ...

  7. php 上传文件实例 注册账号

    注册界面 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3 ...

  8. 与你相遇好幸运,Tippecanoe在Centos下の安装

    全新的CentOS 7 x86_64 安装编译工具 yum install -y gcc automake autoconf libtool make yum insyall -y gcc gcc-c ...

  9. javascript 面向对象编程小记

    虽然平常用jquery用的很熟,但是基本都是面向过程的写法.一个事件一个function,很少有面向对象的写法.今天得写一个日期控件,不得不用上面向对象编程. 刚开始我的想法是: var datepi ...

  10. hdu 1251:统计难题(字典树,经典题)

    统计难题 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131070/65535 K (Java/Others)Total Submi ...