mybatis-spring注解MapperScan的原理
ztj100 2024-12-04 17:14 17 浏览 0 评论
很多开发者应该都知道,我们只使用@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这个类的代码如下:
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的性能太高了。
相关推荐
- Vue3非兼容变更——函数式组件(vue 兼容)
-
在Vue2.X中,函数式组件有两个主要应用场景:作为性能优化,因为它们的初始化速度比有状态组件快得多;返回多个根节点。然而在Vue3.X中,有状态组件的性能已经提高到可以忽略不计的程度。此外,有状态组...
- 利用vue.js进行组件化开发,一学就会(一)
-
组件原理/组成组件(Component)扩展HTML元素,封装可重用的代码,核心目标是为了可重用性高,减少重复性的开发。组件预先定义好行为的ViewModel类。代码按照template\styl...
- Vue3 新趋势:10 个最强 X 操作!(vue.3)
-
Vue3为前端开发带来了诸多革新,它不仅提升了性能,还提供了...
- 总结 Vue3 组件管理 12 种高级写法,灵活使用才能提高效率
-
SFC单文件组件顾名思义,就是一个.vue文件只写一个组件...
- 前端流行框架Vue3教程:17. _组件数据传递
-
_组件数据传递我们之前讲解过了组件之间的数据传递,...
- 前端流行框架Vue3教程:14. 组件传递Props效验
-
组件传递Props效验Vue组件可以更细致地声明对传入的props的校验要求...
- 前端流行框架Vue3教程:25. 组件保持存活
-
25.组件保持存活当使用...
- 5 个被低估的 Vue3 实战技巧,让你的项目性能提升 300%?
-
前端圈最近都在卷性能优化和工程化,你还在用老一套的Vue3开发方法?作为摸爬滚打多年的老前端,今天就把私藏的几个Vue3实战技巧分享出来,帮你在开发效率、代码质量和项目性能上实现弯道超车!一、...
- 绝望!Vue3 组件频繁崩溃?7 个硬核技巧让性能暴涨 400%!
-
前端的兄弟姐妹们五一假期快乐,谁还没在Vue3项目上栽过跟头?满心欢喜写好的组件,一到实际场景就频频崩溃,页面加载慢得像蜗牛,操作卡顿到让人想砸电脑。用户疯狂吐槽,领导脸色难看,自己改代码改到怀疑...
- 前端流行框架Vue3教程:15. 组件事件
-
组件事件在组件的模板表达式中,可以直接使用...
- Vue3,看这篇就够了(vue3 从入门到实战)
-
一、前言最近很多技术网站,讨论的最多的无非就是Vue3了,大多数都是CompositionAPI和基于Proxy的原理分析。但是今天想着跟大家聊聊,Vue3对于一个低代码平台的前端更深层次意味着什么...
- 前端流行框架Vue3教程:24.动态组件
-
24.动态组件有些场景会需要在两个组件间来回切换,比如Tab界面...
- 前端流行框架Vue3教程:12. 组件的注册方式
-
组件的注册方式一个Vue组件在使用前需要先被“注册”,这样Vue才能在渲染模板时找到其对应的实现。组件注册有两种方式:全局注册和局部注册...
- 焦虑!Vue3 组件频繁假死?6 个奇招让页面流畅度狂飙 500%!
-
前端圈的朋友们,谁还没在Vue3项目上踩过性能的坑?满心期待开发出的组件,一到高并发场景就频繁假死,用户反馈页面点不动,产品经理追着问进度,自己调试到心态炸裂!别以为这是个例,不少人在电商大促、数...
- 前端流行框架Vue3教程:26. 异步组件
-
根据上节课的代码,我们在切换到B组件的时候,发现并没有网络请求:异步组件:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)