mybatis-spring注解MapperScan的原理
很多开发者应该都知道,我们只使用@MapperScan这个注解就可以把我们写的Mybatis的Mapper接口加载到Spring的容器中,不需要对每个Mapper接口加@Mapper这个注解了,加快了我们开发的效率。如下:
就可以把我们写在io.renren.mapper这个包下的Mapper接口加载到我们的Spring容器中。当然mybatis-spring能使用这样的注解还是因为的大神开发者给我们提供了大量的可扩展的接口。下面就聊聊它的原理就是@MapperScan这个注解如下:
/*
* @since 1.2.0
* @see MapperScannerRegistrar
* @see MapperFactoryBean // 这个类贴出来它很重要
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
好家伙又是@Import这个类帮助我们导入了MapperScannerRegistrar【PS这个类前面文章也多次用到,它的3个作用我前面的文章也都说过了,有兴趣可以翻看我的前面写的文章】,这个类的代码如下:
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
// 注册了一个 MapperScannerConfigurer的BeanDefinition
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
它主要是帮我们注入了一个MapperScannerConfigurer类型的BeanDefinition 。那我们就先看这个类的相关代码如下:
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor{
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
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); scanner.registerFilters();
// 开始扫描指定类下的Mapper接口
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
如上面的代码 MapperScannerConfigurer这个类实现了BeanDefinitionRegistryPostProcessor【以前的文章也有提到】
是Spring提供给开发者供开发者实现这个接口然后可以介入Spring的启动周期,具体Spring是什么时候运行这段代码的呢?教大家一个好的方法如下:
我们断点打到上面的位置,然后Debug启动项目,然就可以看到这个方法的调用路径如下,经过5步就调用到这个方法了。
第一个方法AbstractApplicationContext.refresh方法应该是Spring中最重要的方法了,Spring的很多很多功能都是在这个方法里面完成的,有兴趣的可以读一下,下面贴出来直接回调的这个方法如下:
/**
* Invoke the given BeanDefinitionRegistryPostProcessor beans.
* 这个方法就会回调MapperScannerConfigurer的
* postProcessBeanDefinitionRegistry方法
*/
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
}
帮大家找到Spring回调的方法了,就还回到MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法。我们主要看scan【扫描】这个方法,进入这个方法如下:
Spring大神写代码有个特点方法前面带do就是真正工作的方法【PS我们写复杂业务的时候也可以模仿do....方法】因为do有做,执行的意思。代码如下:
/**
在我们指定的包内执行
* Perform a scan within the specified base packages,
// 返回扫描到的 bean definitions.
* returning the registered bean definitions.
*/
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 扫描到的beanDefinitions 放到这里
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 获取候选的组件【我们写的Mapper接口】,主要的逻辑都在这里
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
beanDefinitions.add(definitionHolder);
}
return beanDefinitions;
}
findCandidateComponents方法如下:
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// 根据我们写的包名获取Mapper接口在项目中的实际路径
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 获取包下的所有接口的资源
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
for (Resource resource : resources) {
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
if (isCandidateComponent(sbd)) {
// 把我们写的Dao或者Mapper接口放入candidates中
candidates.add(sbd);
}
}
}
// 把封装的Mapper的 BeanDefinition返回
return candidates;
}
把封装好的BeanDefinition封装好返回后,后面就会进入Spring Bean的生命周期进行Bean的初始化,然后就提供给开发者直接使用了。到这里有的小伙伴该问了,我们写的是接口Spring怎么能给接口初始化呢?上面这些都是瞎聊的。如果能这样问说明你的Java基础很不错哦,值得鼓励。那小伙伴是否记得我文章开始说 MapperFactoryBean这个类呢?我还特别强调这个类很重要,这个类是FactoryBean。Mybatis是把我们写的接口生成了的代理对象MapperProxy 。由于我使用的是MybatisPlus,它又封装一下MapperProxy变成了MybaitsMapperProxy,这个类直接copy了Mybatis的代码。我们通过MapperFactoryBean的getObject()方法就能获取MapperProxy了,它不是接口。
FactoryBean和BeanFactory的区别:
FactoryBean,是Spring提供的一个扩展点,用于复杂Bean的创建。mybatsi在跟Spring做整合时候就用到了这个扩展点。并且FactoryBean所创建的Bean跟普通的Bean不一样。它有getObject()方法才可以获取具体的Bean
BeanFactory是Spring IOC容器的顶级接口,其实现类有XMLBeanFactory ,DefaultListBeanFactory。BeanFactory为Srping管理Bean提供了一套通用的接口规范。
如果不信可以看如下,图中解释的很明白了:
上面就是MapperScan的整个原理,如果跟着上面的代码走一遍流程相信你对它的原理就更懂了,你写代码的时候对写的Mapper接口应该就知道不加@Mapper这个注解Spring也能帮你完成你写的Mapper接口的初始化。
再分享一个Spring生成UUID的很高效的工具类:AlternativeJdkIdGenerator。
大意是它使用了SecureRandom作为种子,来替换调用UUID#randomUUID()。它提供了一个更好、更高性能的表现 发现仅仅循环生成1000万次,Spring提供的算法性能远远高于JDK的。因此建议大家以后使用AlternativeJdkIdGenerator去生成UUID,性能会更好一点
缺点是:还需要new对象才能使用,不能通过类名直接调用静态方法,当然我们可以二次封装。另外,一般输出串我们都会进一步这么处理:.toString().replace("-", "")
如下对比一下就知道它的性能有多好了。
/**
* 直接调用java的那个UUID工具
*/
JdkIdGenerator jdkIdGenerator = new JdkIdGenerator();
AlternativeJdkIdGenerator alternativeJdkIdGenerator = new AlternativeJdkIdGenerator();
Instant start;
Instant end;
int count = 10000000;
//jdkIdGenerator
start = Instant.now();
for (int i = 0; i < count; i++) {
jdkIdGenerator.generateId();
}
end = Instant.now();
System.out.println("jdkIdGenerator循环" + count + "次耗时:" + Duration.between(start, end).toMillis() + "ms");
//alternativeJdkIdGenerator
start = Instant.now();
for (int i = 0; i < count; i++) {
alternativeJdkIdGenerator.generateId();
}
end = Instant.now();
System.out.println("alternativeJdkIdGenerator循环" + count + "次耗时:" + Duration.between(start, end).toMillis() + "ms");
如下结果如下Spring的性能太高了。
mybatis-spring注解MapperScan的原理的更多相关文章
- 浅尝Spring注解开发_AOP原理及完整过程分析(源码)
浅尝Spring注解开发_AOP原理及完整过程分析(源码) 浅尝Spring注解开发,基于Spring 4.3.12 分析AOP执行过程及源码,包含AOP注解使用.AOP原理.分析Annotation ...
- spring注解开发-扩展原理(源码)
1.BeanFactoryPostProcessor BeanPostProcessor:bean后置处理器,bean创建对象初始化前后进行拦截工作的; BeanFactoryPostProcesso ...
- mybatis-4 mybatis与spring结合使用及原理
1.创建项目maven,方便依赖下载.使用的jar如下: <dependencies> <dependency> <groupId>org.springframew ...
- SpringMvc+Spring+MyBatis 基于注解整合
最近在给学生们讲Spring+Mybatis整合,根据有的学生反映还是基于注解实现整合便于理解,毕竟在先前的工作中团队里还没有人完全舍弃配置文件进行项目开发,由于这两个原因,我索性参考spring官方 ...
- Spring+MyBatis纯注解零XML整合(4)
不得不说,利用XML作为配置文件是一个非常好的想法,它可以轻松地实现配置集中化,而且修改之后无需再次编译.然而,由于大多数情况下开发者基本都会拿到程序的源码,加之对于各种XML配置文件一般情况下也只有 ...
- SpringBoot整合Mybatis使用注解或XML的方式开发
2018-6-4 补充mybatis-spring-boot注解的使用 1.导包 只需要再导入mysql+mybatis两个包 <dependency> <groupId>or ...
- SpringBoot Mybatis整合(注解版),SpringBoot集成Mybatis(注解版)
SpringBoot Mybatis整合(注解版),SpringBoot集成Mybatis(注解版) ================================ ©Copyright 蕃薯耀 2 ...
- mybatis基于注解形式的多数据源
最近在做一个系统管理项目,需要使用到多数据源,尝试了注解形式和xml形式的多数据源配置,以下是基于注解形式的Mybatis多数据源配置. 1.application.yml 配置文件 database ...
- Springboot+Mybatis AOP注解动态切换数据源
在开发中因需求在项目中需要实现多数据源(虽然项目框架是SpringCloud,但是因其中只是单独的查询操作,觉得没必要开发一个项目,所以采用多数据源来进行实现) 1.在配置文件中创建多个数据连接配置 ...
- spring注解源码分析--how does autowired works?
1. 背景 注解可以减少代码的开发量,spring提供了丰富的注解功能.我们可能会被问到,spring的注解到底是什么触发的呢?今天以spring最常使用的一个注解autowired来跟踪代码,进行d ...
随机推荐
- CCF 202006-1 线性分类器
#include <iostream> #include <bits/stdc++.h> #include <string> using namespace std ...
- 【BOOK】正则表达式
正则表达式 1. 开源中国-正则表达式测试工具:https://tool.oschina.net/regex/ 2. 匹配规则 3. match() 从字符串起始位置匹配正则表达式 若从起始位置匹配不 ...
- Typora初学
Markdown学习 TYPORA操作 Ctrl+Home 返回Typora顶部 Ctrl+End 返回Typora底部 Ctrl+T 创建表格 Ctrl+H 搜索并替换 Ctrl+Shift+M 公 ...
- cin和缓存区问题
稍微记录一下今天刷题遇到的C++问题 看到使用while(cin >> s);来读取最后一个字符串.百度了一下发现cin以空格,制表符和回车为终止依据.也就是说我输入"abc 1 ...
- 编译configure常用参数详解
./configure常用参数解释: 具体通过–help来查看具体支持什么功能.有时候编译不通过,可能依赖一些库,如果这些库关联的功能我们不需要,可以通过---disable-*lib来取消相关库的编 ...
- springboot默认的json配置
springboot默认的json配置 1.@JsonIgnore 返回前端时对应字段不进行序列化返回 public class User { @JsonIgnore private String n ...
- 下载Vue.js输入Vue -V报错解决办法
报错如图所示 解决办法: 1. 以管理员身份运行vscode; 2. 执行:get-ExecutionPolicy,显示Restricted,表示状态是禁止的; 3. 执行:set-Execution ...
- linux基础命令4
用户和组群账户管理 用户的 角色是通过UID(用户ID号)来标识的,每个用户的UID都是不同的. 在Linux系统中有三大类用户,分别是root 用户.系统用户和普通用户. root用户UID为0.r ...
- web后端之表单传值
第一种 第二种 第三种陪置web.xml文件
- Vue+SSM+Element-Ui实现前后端分离(1)
前言:最近学习vue,就突发奇想,小菜鸟的我是时候锻炼锻炼自己.闲话不说,整起 <-_-> 整体规划:先搭建前端,接下来后端,最后整合. 一.创建vue项目 1.安装nodejs( 傻瓜式 ...