问题

Service层注入Dao时, Intellij 总会以红色波浪线提示我们

1
2
@Autowired
private UserDao userDao;

Could not autowire. No beans of ‘UserDao’ type found.
Checks autowiring problems in a bean class.

尽管我们都知道 Dao 层的 Bean 实际上都是有的,并且可以设置关闭这恼人的提示,但是我们有没有想过为什么 Intellij 就找不到这个 Bean 呢?甚至有人有这种做法

1
2
3
@Repository
public interface UserDao {
}

来避免提示,但是这种做法正确么?

所以今天我们的疑问就是

  1. 为什么 Dao 层不需要加 @Repository 注解,源码里到底做了什么?
  2. 加了 @Repository 注解有什么影响?

答案

  1. 关键在于 ClassPathMapperScanner 对指定包的扫描,并且扫描过程对 Spring 原本的扫描 Bean 的步骤 “加了料” ,Spring 本身只扫实现类,但 MyBatis 的扫描器扫了接口 。并且扫完接口之后,为接口配了个 BeanDefinition ,并且这个 bd 的 BeanClass 是 MapperFactoryBean 。

    对于 BeanDefinition 和 MapperFactoryBean 不了解的同学请查询相关资料和源码

  2. 仅仅只能解决 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() 方法也很简单,就是两步:

  1. 调用父类 ClassPathBeanDefinitionScanner 的doScan()方法,也就是 Spring 扫描BeanDefinition 的方法。过程不是很重要,我们需要知道这个扫描方法的一个关键就是
1
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

在其中对所有的候选者使用 isCandidateComponent() 方法判断是否为符合要求的 BeanDefinition。

1
2
3
4
5
6
7
8
9
10
for (TypeFilter tf : this.excludeFilters) {if (tf.match(metadataReader, this.metadataReaderFactory)) {return false;}
}
for (TypeFilter tf : this.includeFilters) {if (tf.match(metadataReader, this.metadataReaderFactory)) {return isConditionMatch(metadataReader);}
}

这有两组过滤器来过滤扫描到的资源。Spring 默认的过滤器是排除掉抽象类/接口的。而MyBatis 的扫描器重新注册了过滤器,默认对接口放行。

其实还有一些其它的过滤要求,但是不影响我们本问题的探究,所以不深入解读了。

源码读到这里,我们先找到了本文的第二个问题的答案。也就是 Spring 会忽略掉接口上面的注解,不会添加它进入 BeanDefiniiton ,也就难怪测试的时候会抛出 NoSuchBeanDefinitionException 的异常了。而 MyBatis 则会把这些接口拉过来注册BD 。

对 BeanDefinition 的加工

读到这里我们可能有了更大的疑问,拿接口注册 BeanDefinition ,那获取 Bean 的时候如何去实例化这个对象啊?接口可是不能实例化出对象的啊,而且我们也没有做实现。

原来是 MyBatis 的扫描器在调用完父类的扫描方法后,对 BeanDefinition 进行了加工 processBeanDefinitions() 。其中最关键的两行代码是

1
2
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
definition.setBeanClass(this.mapperFactoryBean.getClass());

第一行,我们发现把这个接口的类名塞到了构造器参数中

小彩蛋,这里塞的是 String ,而我们的构造器参数其实要的是 Class 。但是 Spring 的 ConstructorResolver.autowireConstructor 中用到了 Object[] argsToUse 去做了个转换 。

第二行,beanDefinition 的 BeanClass 被设置成了 MapperFactoryBean !

熟悉 Spring 和 MyBatis 的读者肯定一下就明白了,就是这个地方进行了”偷梁换柱”!

1
2
@Autowired
private UserDao userDao;

还是拿 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相关推荐

  1. SpringBoot精通系列-使用Mybatis Generator生成Dao层代码

    导语   使用Mybatis的时候通常会创建很多的映射文件以及创建很多的Model对象,相对来说比较麻烦也有很多的重复的工作.下面就来使用一个Mybatis Dao层代码生成器. 文章目录 开始使用M ...

  2. Mybatis 原始Dao层开发

    对Mybatis进行原始Dao层开发的举例子: 定义获取session工具类: package com.langsin.unit;import java.io.InputStream;import o ...

  3. java do po dto_彻底搞懂DAO,PO,BO,DTO,VO,DO

    原标题:彻底搞懂DAO,PO,BO,DTO,VO,DO 有干货,就分享,点上面的蓝字"测试之道"关注. 上才艺,哦不,上图... Entity 最常用实体类,基本和数据表一一对应, ...

  4. 肝了一早上,终于把mybatis的一级缓存和二级缓存原理搞懂了~

    今天的沉淀,是助力明天飞向远方的基石~ 每当自己沉思下来写学习文章的时候,内心深处总是会得到一片宁静. 缓存的概念 我们在查询数据时,经常去查询一些条件相同.数据的正确与否对最终结果影响不大的数据,并 ...

  5. 一文彻底搞懂Mybatis系列(十六)之MyBatis集成EhCache

    MyBatis集成EhCache 一.MyBatis集成EhCache 1.引入mybatis整合ehcache的依赖 2.类根路径下新建ehcache.xml,并配置 3.POJO类 Clazz 4 ...

  6. 一文搞懂两表关联时left join 、 on 以及where条件不同写法对结果的影响--文末有结论

    一文搞懂两表关联时left join 与 on 以及where条件不同写法对结果的影响–文末有结论 1.数据准备(建议使用本地mysql) a表: b表: 建表语句: Create EXTERNaL ...

  7. 学习最新大厂付费视频时整理的万字长文+配图带你搞懂 MySQL

    万字长文+配图带你搞懂 MySQL MySQL SQL的介绍 SQL分类 MySQL语法 创建数据库 修改.删除.使用数据库 DDL查询数据表 DDL创建数据表 修改数据表结构 删除数据表 DML添加 ...

  8. 带你彻底搞懂MyBatis的底层实现之缓存模块(Cache)-吊打面试官必备技能

      基础支持层位于MyBatis整体架构的最底层,支撑着MyBatis的核心处理层,是整个框架的基石.基础支持层中封装了多个较为通用的.独立的模块.不仅仅为MyBatis提供基础支撑,也可以在合适的场 ...

  9. if mybatis tk 多个_面试题:mybatis 中的 DAO 接口和 XML 文件里的 SQL 是如何建立关系的?...

    前言 这是 mybatis 比较常问到的面试题,我自己在以前的面试过程中被问到了2次,2次都是非常重要的面试环节,因此自己印象很深刻. 这个题目我很早就深入学习了,但是一直没有整理出来,刚好最近一段时 ...

  10. 3分钟搞定SpringBoot+Mybatis+druid多数据源和分布式事务

    在一些复杂的应用开发中,一个应用可能会涉及到连接多个数据源,所谓多数据源这里就定义为至少连接两个及以上的数据库了. 下面列举两种常用的场景: 一种是读写分离的数据源,例如一个读库和一个写库,读库负责各 ...

最新文章

  1. [密码学] DES(二)
  2. java finereport_java报表FineReport_JS整理
  3. 关于viewport我自己的理解
  4. python怎么创建虚拟环境_anaconda怎么创建python虚拟环境
  5. 用C#编写Linux守护进程
  6. JavaFX:太空侵略者在175 LOC以下
  7. Java迭代器的一般用法
  8. 【QT源码】系列01
  9. 26种土的掉渣的东西,看你有多少
  10. EDA技术实用教程 | 复习十三 | 计数器
  11. PowerDesigner如何自定义报表模板
  12. 【AUTOSAR-COM】-10.4-发送的IPDU Callout(Com_TxIpduCallout)的使用小结
  13. Idea使用起来反应比较慢
  14. Python教程:命令行参数处理
  15. 网易我的世界能安装java模组吗_网易的我的世界能不能自己制作模组?怎么制作?...
  16. 计算机相关扩展活动战队名字,2020最新战队名字大全
  17. 地理计算 | EXCEL中快速计算列表的经纬度距离
  18. 致爱致青春——关于北京爱情故事
  19. pic16多路adc采样
  20. bootstrapTable printThis打印插件 中 printThis.js中的一个buge

热门文章

  1. c语言实现linux下的top命令来获取cpu利用率_Linux性能调优之CPU性能优化
  2. [Bzoj2152]聪聪可可
  3. javaweb项目静态资源被拦截的解决方法
  4. MySQL将一张表的某些列数据,复制到另外一张表,并且修改某些内容
  5. grub的boot loader安装在磁盘上的位置
  6. k-d tree学习总结
  7. noi 8780 拦截导弹
  8. Unity3D 动态改变地形
  9. 解决清除浮动的最佳办法
  10. Visual Basic 2012 借助DataGridView控件将Excel 2010数据导入到SQL server 2012