SpringBoot整合Mybatis源码解析之 @MapperScan
@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);}
根据mapperInterface
,sqlSession.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接口的扫描并生成BeanDefinition,BeanDefinition里面真正的beanClass是MapperFactoryBean,Spring容器在getBean的时候真正拿到的是MapperFactoryBean.getObject(),得到是一个MapperProxy代理对象。
SpringBoot整合Mybatis源码解析之 @MapperScan相关推荐
- springboot集成mybatis源码分析-启动加载mybatis过程(二)
springboot集成mybatis源码分析-启动加载mybatis过程(二) 1.springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplicati ...
- springboot集成mybatis源码分析(一)
springboot集成mybatis源码分析(一) 本篇文章只是简单接受使用,具体源码解析请看后续文章 1.新建springboot项目,并导入mybatis的pom配置 配置数据库驱动和mybat ...
- Mybatis源码解析《二》
导语 在前一篇文章Mybatis源码解析<一>中,已经简单了捋了一下mybatis核心文件和mapper配置文件的一个基本的解析流程,这是理解mybatis的基本,和spring中的配置文 ...
- 对标阿里P8的MyBatis源码解析文档,面试/涨薪两不误,已献出膝盖
移动互联网的特点是大数据.高并发,对服务器往往要求分布式.高性能.高灵活等,而传统模式的Java数据库编程框架已经不再适用了. 在这样的背景下,一个Java的持久框架MyBatis走入了我们的世界,它 ...
- springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三)
springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三) 例: package com.example.demo.service;import com.exa ...
- 【MyBatis源码解析】MyBatis一二级缓存
MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...
- mybatis源码解析(一)
Mybatis 源码解析 (一) 一. ORM框架的作用 实际开发系统时,我们可通过JDBC完成多种数据库操作.这里以传统JDBC编程过程中的查询操作为例进行说明,其主要步骤如下: (1)注册数据库驱 ...
- mybatis源码解析一 xml解析(解析器)
最近闲来无事,看着一些源码类的书籍,只是光看,好像并不能给自己很好的益处,无法记下来,所以就有了这个Mybatis源码解析系列的博客.网上也有大量的源码解析,在此记录有两个原因,一是为了加深自己的印象 ...
- Mybatis源码解析(一):环境搭建
Mybatis源码系列文章 手写源码(了解源码整体流程及重要组件) Mybatis源码解析(一):环境搭建 Mybatis源码解析(二):全局配置文件的解析 Mybatis源码解析(三):映射配置文件 ...
最新文章
- 渗透知识-常用DOS命令windows
- 2018危机与机遇丨PMCAFF年度精选合集
- [Linux]配置网络
- 关于python语言数值操作符、以下选项错误的是 答案是_关于Python注释,以下选项中描述错误的是...
- 用python做一个简单的投票程序_以一个投票程序的实例来讲解Python的Django框架使...
- 这是一个我面试某公司的算法题目:对一个字符数组进行排序,根据给定的字符,大于它的,放在数组的左边,小于它的,放在数组的右边,且数组中的元素之间的相对位置要保持不变。...
- mysql查询条件是小数 查不到6.28_28.mysql数据库之查询
- 用户生命周期运营白皮书2.0
- springmvc5中设计模式
- 做好准备,让你的短信应用迎接Android 4.4(KitKat)
- RDD的创建 -Scala educoder
- 众信金融8·28开放日:聆听投资人心声
- mysql 使用中_phpmyadmin显示MySQL数据表“使用中” 修复后依然无效的解决方法
- PHP爱好者:十天学会php之第一天
- 百科知识:呼叫转移与呼叫前转
- c语言大学题库pdf,C语言试题库(完整版)..pdf
- Linux 创建无线热点
- 第三集 怪物学院 第十六章
- 中学物理教学参考杂志社中学物理教学参考编辑部2022年第21期目录
- word自动设置表格格式