@MapperScan

@MapperScan import了一个很重要的MapperScannerRegistrar,一个mapper注册处理器,主要完成 mapper接口的扫描和bean定义的注入。

MapperScan两个比较重要的配置信息:

basePackages : 指定扫描的mapper包路径。

factoryBean : 默认为MapperFactoryBean, 扫描到的mapper接口最后都会被包装成一个MapperFactoryBean



MapperScannerRegistrar


MapperScannerRegistrar 是一个实现了 ImportBeanDefinitionRegistrar接口的一个类,主要完成了registerBeanDefinitions 注册 bean的定义的功能

void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {//用于扫描mapper接口并注册bean定义ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);// this check is needed in Spring 3.1Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);// 处理mapperScan配置信息,将这些配置都设置给scanner Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");if (!Annotation.class.equals(annotationClass)) {scanner.setAnnotationClass(annotationClass);}Class<?> markerInterface = annoAttrs.getClass("markerInterface");if (!Class.class.equals(markerInterface)) {scanner.setMarkerInterface(markerInterface);}Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");if (!BeanNameGenerator.class.equals(generatorClass)) {scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));}Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass);}scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));List<String> basePackages = new ArrayList<>();basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));//注册过滤器scanner.registerFilters();//这里才是真正处理扫描注册过程scanner.doScan(StringUtils.toStringArray(basePackages));}

继续跟进doScan(),ClassPathMapperScanner 是继承于ClassPathBeanDefinitionScanner,所以先执行的super.doScan,这将mapper接口都包装成BeanDefinitionHolder,然后再执行processBeanDefinitions(beanDefinitions),这一步主要是增强BeanDefinition ,注入sqlSessionFactory、sqlSessionTemplate、MapperFactoryBean 等配置。

@Overridepublic Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");} else {processBeanDefinitions(beanDefinitions);}return beanDefinitions;}

这一步主要是增强BeanDefinition ,注入sqlSessionFactory、sqlSessionTemplate、MapperFactoryBean 等配置。

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {GenericBeanDefinition definition;for (BeanDefinitionHolder holder : beanDefinitions) {definition = (GenericBeanDefinition) holder.getBeanDefinition();String beanClassName = definition.getBeanClassName();LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName()+ "' and '" + beanClassName + "' mapperInterface");// the mapper interface is the original class of the bean// but, the actual class of the bean is MapperFactoryBeandefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59// BeanDefinition 最后要实例化的对象class ,就是MapperFactoryBeandefinition.setBeanClass(this.mapperFactoryBeanClass);definition.getPropertyValues().add("addToConfig", this.addToConfig);boolean explicitFactoryUsed = false;if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionFactory != null) {definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);explicitFactoryUsed = true;}if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {if (explicitFactoryUsed) {LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionTemplate != null) {if (explicitFactoryUsed) {LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);explicitFactoryUsed = true;}if (!explicitFactoryUsed) {LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);}}

MapperFactoryBean

下面介绍一下MapperFactoryBean
可以看到这个该类是一个FactoryBean


Spring容器中获取Bean的时候,是调用的getObject()

  @Overridepublic T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}

最终会调用到MapperRegistry的getMapper(),最终生成的是一个代理对象

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}

可以看到new了一个MapperProxy,然后生成动态代理对象。

  public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}

MapperProxy是一个InvocationHandler,所以代理对象最后执行的是invoke方法

public class MapperProxy<T> implements InvocationHandler, Serializable
@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (isDefaultMethod(method)) {return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);}

根据mapperInterfacesqlSession.getConfiguration(),创建了一个MapperMethod
会根据mapper的接口名和方法名找到xml解析后生成的对应MappedStatement

private MapperMethod cachedMapperMethod(Method method) {return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));}

MapperMethod最后执行的excute方法。

public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);if (method.returnsOptional()&& (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + command.getName());}if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName()+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");}return result;}

总结

通过对 @MapperScan 的分析,最后可以简单总结为:MapperScannerRegistrar完成了mapper接口的扫描并生成BeanDefinitionBeanDefinition里面真正的beanClass是MapperFactoryBeanSpring容器在getBean的时候真正拿到的是MapperFactoryBean.getObject(),得到是一个MapperProxy代理对象。

SpringBoot整合Mybatis源码解析之 @MapperScan相关推荐

  1. springboot集成mybatis源码分析-启动加载mybatis过程(二)

    springboot集成mybatis源码分析-启动加载mybatis过程(二) 1.springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplicati ...

  2. springboot集成mybatis源码分析(一)

    springboot集成mybatis源码分析(一) 本篇文章只是简单接受使用,具体源码解析请看后续文章 1.新建springboot项目,并导入mybatis的pom配置 配置数据库驱动和mybat ...

  3. Mybatis源码解析《二》

    导语 在前一篇文章Mybatis源码解析<一>中,已经简单了捋了一下mybatis核心文件和mapper配置文件的一个基本的解析流程,这是理解mybatis的基本,和spring中的配置文 ...

  4. 对标阿里P8的MyBatis源码解析文档,面试/涨薪两不误,已献出膝盖

    移动互联网的特点是大数据.高并发,对服务器往往要求分布式.高性能.高灵活等,而传统模式的Java数据库编程框架已经不再适用了. 在这样的背景下,一个Java的持久框架MyBatis走入了我们的世界,它 ...

  5. springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三)

    springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三) 例: package com.example.demo.service;import com.exa ...

  6. 【MyBatis源码解析】MyBatis一二级缓存

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...

  7. mybatis源码解析(一)

    Mybatis 源码解析 (一) 一. ORM框架的作用 实际开发系统时,我们可通过JDBC完成多种数据库操作.这里以传统JDBC编程过程中的查询操作为例进行说明,其主要步骤如下: (1)注册数据库驱 ...

  8. mybatis源码解析一 xml解析(解析器)

    最近闲来无事,看着一些源码类的书籍,只是光看,好像并不能给自己很好的益处,无法记下来,所以就有了这个Mybatis源码解析系列的博客.网上也有大量的源码解析,在此记录有两个原因,一是为了加深自己的印象 ...

  9. Mybatis源码解析(一):环境搭建

    Mybatis源码系列文章 手写源码(了解源码整体流程及重要组件) Mybatis源码解析(一):环境搭建 Mybatis源码解析(二):全局配置文件的解析 Mybatis源码解析(三):映射配置文件 ...

最新文章

  1. 渗透知识-常用DOS命令windows
  2. 2018危机与机遇丨PMCAFF年度精选合集
  3. [Linux]配置网络
  4. 关于python语言数值操作符、以下选项错误的是 答案是_关于Python注释,以下选项中描述错误的是...
  5. 用python做一个简单的投票程序_以一个投票程序的实例来讲解Python的Django框架使...
  6. 这是一个我面试某公司的算法题目:对一个字符数组进行排序,根据给定的字符,大于它的,放在数组的左边,小于它的,放在数组的右边,且数组中的元素之间的相对位置要保持不变。...
  7. mysql查询条件是小数 查不到6.28_28.mysql数据库之查询
  8. 用户生命周期运营白皮书2.0
  9. springmvc5中设计模式
  10. 做好准备,让你的短信应用迎接Android 4.4(KitKat)
  11. RDD的创建 -Scala educoder
  12. 众信金融8·28开放日:聆听投资人心声
  13. mysql 使用中_phpmyadmin显示MySQL数据表“使用中” 修复后依然无效的解决方法
  14. PHP爱好者:十天学会php之第一天
  15. 百科知识:呼叫转移与呼叫前转
  16. c语言大学题库pdf,C语言试题库(完整版)..pdf
  17. Linux 创建无线热点
  18. 第三集 怪物学院 第十六章
  19. 中学物理教学参考杂志社中学物理教学参考编辑部2022年第21期目录
  20. word自动设置表格格式

热门文章

  1. TTY 到底是个什么玩意?
  2. Ubuntu 安装 PostgreSQL 教程
  3. 下载ROS-Academy-for-Beginners-master后catkin_make报错
  4. #51单片机#数码管静态与动态显示
  5. LED显示屏周边设备
  6. 使用css实现三角符号
  7. 新版个人理财工具开发完成
  8. Vue的导入(import)和导出(export、export default)
  9. base64转file文件(两种方法)
  10. 虚幻引擎(UE4)编辑器窗口快捷键整理