彻底搞懂使用MyBatis时为什么Dao层不需要@Repository
问题
Service层注入Dao时, Intellij 总会以红色波浪线提示我们
|
|
Could not autowire. No beans of ‘UserDao’ type found.
Checks autowiring problems in a bean class.
尽管我们都知道 Dao 层的 Bean 实际上都是有的,并且可以设置关闭这恼人的提示,但是我们有没有想过为什么 Intellij 就找不到这个 Bean 呢?甚至有人有这种做法
|
|
来避免提示,但是这种做法正确么?
所以今天我们的疑问就是
- 为什么 Dao 层不需要加 @Repository 注解,源码里到底做了什么?
- 加了 @Repository 注解有什么影响?
答案
关键在于 ClassPathMapperScanner 对指定包的扫描,并且扫描过程对 Spring 原本的扫描 Bean 的步骤 “加了料” ,Spring 本身只扫实现类,但 MyBatis 的扫描器扫了接口 。并且扫完接口之后,为接口配了个 BeanDefinition ,并且这个 bd 的 BeanClass 是 MapperFactoryBean 。
对于 BeanDefinition 和 MapperFactoryBean 不了解的同学请查询相关资料和源码
仅仅只能解决 Intellij 静态查找 bean 的问题,没有实际作用。即使加了注解,比如@Controller,@Service 等等,也会被 Spring 的扫描器给忽略掉,因为扫描器会过滤掉接口。
源码探索
下面的源码部分如果读者提前有 MyBatis 的 Bean 的执行流程,和 Spring 的 Bean加载的相关知识就更好理解。
1. 分析问题
关于为什么不需要注解就能获取到 Dao 层的 Bean,看似答案很简单,因为配置了扫描指定这个包里的 xxxDao.class 啊,比如使用注解 @MapperScan(“com.example.dao”)。
这个答案太过表面,觉得问题简单只是因为对 Spring 的 Bean 不熟悉。
我们何时见过 @Component 及其衍生的3个注解 @Controller、@Service、@Repository 加在接口上面的?
自己测试新建个接口,上面加注解,然后找个 Controller 里 @Autowired 注入一下,项目立马会报错 NoSuchBeanDefinitionException 。
2. 切入源码
切入点
既然使用注解 @MapperScan 就好使,那么我们就从这个点切入源码看一下,先找出源码中何处用了此注解,非常幸运的是,只有一处用到了此注解 :MapperScannerRegistrar.registerBeanDefinitions() 。
并且从类名和方法名就可以很清楚的看出这个类的功能是扫描 Mapper 并注册,方法的功能就是注册 BeanDefinitions 到 Spring 中。方法的源码我就不贴了,很容易看出来是创建一个扫描器 ClassPathMapperScanner
,设置好一系列属性比如 Spring 的注册表之后,执行 doScan() 方法去扫描 @MapperScan 提供的包。
doScan() 扫描资源,转换为 BeanDefinition
doScan() 方法也很简单,就是两步:
- 调用父类 ClassPathBeanDefinitionScanner 的doScan()方法,也就是 Spring 扫描BeanDefinition 的方法。过程不是很重要,我们需要知道这个扫描方法的一个关键就是
|
|
在其中对所有的候选者使用 isCandidateComponent() 方法判断是否为符合要求的 BeanDefinition。
|
|
这有两组过滤器来过滤扫描到的资源。Spring 默认的过滤器是排除掉抽象类/接口的。而MyBatis 的扫描器重新注册了过滤器,默认对接口放行。
其实还有一些其它的过滤要求,但是不影响我们本问题的探究,所以不深入解读了。
源码读到这里,我们先找到了本文的第二个问题的答案。也就是 Spring 会忽略掉接口上面的注解,不会添加它进入 BeanDefiniiton ,也就难怪测试的时候会抛出 NoSuchBeanDefinitionException 的异常了。而 MyBatis 则会把这些接口拉过来注册BD 。
对 BeanDefinition 的加工
读到这里我们可能有了更大的疑问,拿接口注册 BeanDefinition ,那获取 Bean 的时候如何去实例化这个对象啊?接口可是不能实例化出对象的啊,而且我们也没有做实现。
原来是 MyBatis 的扫描器在调用完父类的扫描方法后,对 BeanDefinition 进行了加工 processBeanDefinitions() 。其中最关键的两行代码是
|
|
第一行,我们发现把这个接口的类名塞到了构造器参数中。
小彩蛋,这里塞的是 String ,而我们的构造器参数其实要的是 Class 。但是 Spring 的 ConstructorResolver.autowireConstructor 中用到了 Object[] argsToUse 去做了个转换 。
第二行,beanDefinition 的 BeanClass 被设置成了 MapperFactoryBean !
熟悉 Spring 和 MyBatis 的读者肯定一下就明白了,就是这个地方进行了”偷梁换柱”!
|
|
还是拿 UserDao 为例,我们向 Spring 容器说 “给我来个 UserDao 的实例”,而 Spring 根据注册时候的 BeanDefinition ,去工厂( MapperFactoryBean )里面扔了个 UserDao.class 的参数进去,工厂的 getObject() 方法给我们返回了它制造的 userDao 。
就这样,我们没有去写实现类,轻轻松松拿到了我们需要的 userDao 。
至于 MapperFactoryBean 里做了什么返回了 userDao 出来?其实就是它的 getObject 方法返回的是 DefaultSqlSession.getMapper(Class type)方法,返回的是 MapperProxy 代理的类,而这个代理类的 invoke 方法并不像我们平时见到的代理中的 invoke 方法一样调用原始目标的 method.invoke ,而是去找 MapperMethod 执行了。
收获
这次的源码探究下来,收获的不仅仅是了解了 Dao 层 Bean 的注入,更是串起了我们最常用的 Spring 和 MyBatis ,换句话说,我们打通了从 Service 层到 Dao 层。
在以往 Debug 代码时看到的 MapperProxy,MapperMethod,我们清楚了这是从何而来,也对 MyBatis 中代理的巧妙运用更加熟悉。
彻底搞懂使用MyBatis时为什么Dao层不需要@Repository相关推荐
- SpringBoot精通系列-使用Mybatis Generator生成Dao层代码
导语 使用Mybatis的时候通常会创建很多的映射文件以及创建很多的Model对象,相对来说比较麻烦也有很多的重复的工作.下面就来使用一个Mybatis Dao层代码生成器. 文章目录 开始使用M ...
- Mybatis 原始Dao层开发
对Mybatis进行原始Dao层开发的举例子: 定义获取session工具类: package com.langsin.unit;import java.io.InputStream;import o ...
- java do po dto_彻底搞懂DAO,PO,BO,DTO,VO,DO
原标题:彻底搞懂DAO,PO,BO,DTO,VO,DO 有干货,就分享,点上面的蓝字"测试之道"关注. 上才艺,哦不,上图... Entity 最常用实体类,基本和数据表一一对应, ...
- 肝了一早上,终于把mybatis的一级缓存和二级缓存原理搞懂了~
今天的沉淀,是助力明天飞向远方的基石~ 每当自己沉思下来写学习文章的时候,内心深处总是会得到一片宁静. 缓存的概念 我们在查询数据时,经常去查询一些条件相同.数据的正确与否对最终结果影响不大的数据,并 ...
- 一文彻底搞懂Mybatis系列(十六)之MyBatis集成EhCache
MyBatis集成EhCache 一.MyBatis集成EhCache 1.引入mybatis整合ehcache的依赖 2.类根路径下新建ehcache.xml,并配置 3.POJO类 Clazz 4 ...
- 一文搞懂两表关联时left join 、 on 以及where条件不同写法对结果的影响--文末有结论
一文搞懂两表关联时left join 与 on 以及where条件不同写法对结果的影响–文末有结论 1.数据准备(建议使用本地mysql) a表: b表: 建表语句: Create EXTERNaL ...
- 学习最新大厂付费视频时整理的万字长文+配图带你搞懂 MySQL
万字长文+配图带你搞懂 MySQL MySQL SQL的介绍 SQL分类 MySQL语法 创建数据库 修改.删除.使用数据库 DDL查询数据表 DDL创建数据表 修改数据表结构 删除数据表 DML添加 ...
- 带你彻底搞懂MyBatis的底层实现之缓存模块(Cache)-吊打面试官必备技能
基础支持层位于MyBatis整体架构的最底层,支撑着MyBatis的核心处理层,是整个框架的基石.基础支持层中封装了多个较为通用的.独立的模块.不仅仅为MyBatis提供基础支撑,也可以在合适的场 ...
- if mybatis tk 多个_面试题:mybatis 中的 DAO 接口和 XML 文件里的 SQL 是如何建立关系的?...
前言 这是 mybatis 比较常问到的面试题,我自己在以前的面试过程中被问到了2次,2次都是非常重要的面试环节,因此自己印象很深刻. 这个题目我很早就深入学习了,但是一直没有整理出来,刚好最近一段时 ...
- 3分钟搞定SpringBoot+Mybatis+druid多数据源和分布式事务
在一些复杂的应用开发中,一个应用可能会涉及到连接多个数据源,所谓多数据源这里就定义为至少连接两个及以上的数据库了. 下面列举两种常用的场景: 一种是读写分离的数据源,例如一个读库和一个写库,读库负责各 ...
最新文章
- [密码学] DES(二)
- java finereport_java报表FineReport_JS整理
- 关于viewport我自己的理解
- python怎么创建虚拟环境_anaconda怎么创建python虚拟环境
- 用C#编写Linux守护进程
- JavaFX:太空侵略者在175 LOC以下
- Java迭代器的一般用法
- 【QT源码】系列01
- 26种土的掉渣的东西,看你有多少
- EDA技术实用教程 | 复习十三 | 计数器
- PowerDesigner如何自定义报表模板
- 【AUTOSAR-COM】-10.4-发送的IPDU Callout(Com_TxIpduCallout)的使用小结
- Idea使用起来反应比较慢
- Python教程:命令行参数处理
- 网易我的世界能安装java模组吗_网易的我的世界能不能自己制作模组?怎么制作?...
- 计算机相关扩展活动战队名字,2020最新战队名字大全
- 地理计算 | EXCEL中快速计算列表的经纬度距离
- 致爱致青春——关于北京爱情故事
- pic16多路adc采样
- bootstrapTable printThis打印插件 中 printThis.js中的一个buge