news 2026/4/6 15:09:00

为什么 MyBatis Mapper 接口能像普通 Bean 一样被 @Autowired?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
为什么 MyBatis Mapper 接口能像普通 Bean 一样被 @Autowired?

案例

案例一: MyBatis单独使用
在 resources 目录下新建 mybatis-config.xml 配置文件,文件内容如下:

<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!--配置日志--> <settings> <setting name="logImpl" value="LOG4J2"/> </settings> <!--配置数据源和事务管理器--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/study"/> <property name="username" value="root"/> <property name="password" value="xxxxxx"/> </dataSource> </environment> </environments> <!--配置 mapper 文件的位置--> <mappers> <mapper resource="com/study/mybatis/UserMapper.xml"/> </mappers> </configuration>

resources下面的com/study/mybatis文件夹中新建UserMapper.xml文件,文件内容如下:

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.study.mybatis.mapper.UserMapper"> <select id="getUserById" resultType="com.study.mybatis.entity.User"> select * from User where id = #{id} </select> </mapper>

同时在对应层级的 package 下新建UserMapper接口,该接口中包含一个getUserById()方法和UserMapper.xml文件中配置的相对应。代码如下:

@Mapper public interface UserMapper { User getUserById(@Param("id") Long id); }

然后在 Java 代码中,可以通过现构造SqlSessionFactory对象,从这个对象中获取一个SqlSession对象,然后再通过它获取到UserMapper接口的动态代理对象,然后通过这个动态代理对象来调用对应的方法。代码如下:

public static void main(String[] args) throws IOException { // 读取配置文件生成 SqlSessionFactory 对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 获取 SqlSession 对象,然后通过该对象获取 Mapper 接口的动态代理对象 // 然后根据操作该动态代理对象 try (SqlSession session = sqlSessionFactory.openSession()) { UserMapper userMapper = session.getMapper(UserMapper.class); User user = userMapper.getUserById(1L); System.out.println(JSON.toJSONString(user)); } }

案例二: MyBatis和Spring一起使用

@Configuration @MapperScan("com.study.mapper") public class MyBatisConfig { @Bean public DataSource dataSource() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/study"); dataSource.setUsername("root"); dataSource.setPassword("xxxxxx"); return dataSource; } @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); sessionFactoryBean.setDataSource(dataSource); sessionFactoryBean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml") ); return sessionFactoryBean.getObject(); } } @RestController @RequestMapping("/users") public class UserController { @Autowired private UserMapper userMapper; @GetMapping("/{id}") @ResponseBody public User getUserById(@PathVariable Long id) { return userMapper.getUserById(id); } }

从上面的两个案例可以看到,当 MyBatis 单独使用的时候,需要手动通过SqlSession对象来获取Mapper接口的动态代理对象;当 MyBatis 和 Spring 单独使用的时候,可以把Mapper对象当作一个普通的 Bean 对象,通过@Autowired注解注入到需要使用的类里面就可以了。

那当 MyBatis 和 Spring 一起使用的时候,是如何做到把Mapper对象作为一个 Bean 注入到 Spring 中的呢?本文将带你从源码的角度进行分析。

先说结论,Spring 通过把@MapperScan注解指定的路径下的Mappper接口扫描成 Bean 定义,并通过MapperFactoryBean作为 Bean 定义的BeanClass属性,在MapperFactoryBeangetObject()方法中还是调用了SqlSessiongetMapper()方法获取到动态代理对象,并把这个动态代理对象放到 Spring 容器中,供其它需要注入它的地方使用。

源码

首先看下@MapperScan注解,它通过@Import注解引入了实现了ImportBeanDefinitionRegistrar接口的MapperScannerRegistrar类。代码如下:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan { }

在它的registerBeanDefinitions()方法中,会从@MapperScan注解中获取配置的值,比如像sqlSessionFactoryRef,basePackages等一些配置,然后构造了一个MapperScannerConfigurer的 Bean 定义然后注册。代码如下:

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); // 省略代码 String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef"); if (StringUtils.hasText(sqlSessionTemplateRef)) { builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef")); } String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef"); if (StringUtils.hasText(sqlSessionFactoryRef)) { builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef")); } // 省略代码 List<String> basePackages = new ArrayList<>(); 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())); if (basePackages.isEmpty()) { basePackages.add(getDefaultBasePackage(annoMeta)); } builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); // for spring-native builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); // 这里注册了一个MapperScannerConfigurer类型的Bean定义 registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); } }

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,这个接口的作用在前面的文章 3 个案例看透 Spring @Component 扫描:从普通应用到 Spring Boot介绍过。
在它的postProcessBeanDefinitionRegistry()方法中会委托ClassPathMapperScanner去扫描路径下的Mapper接口为 Bean 定义。代码如下:

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } 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); if (StringUtils.hasText(lazyInitialization)) { scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization)); } if (StringUtils.hasText(defaultScope)) { scanner.setDefaultScope(defaultScope); } // 这里会为scanner注册过滤器 scanner.registerFilters(); // 调用scan方法扫描Mapper scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); } }

ClassPathMapperScanner继承了ClassPathBeanDefinitionScanner,在它的doScan()方法中,首先会调用父类的doScan()方法扫描 Bean 定义,然后在processBeanDefinitions()方法中将 Bean 的BeanClass设置为MapperFactoryBean,从名字可以看出它是一个实现了FactoryBean接口的类。代码如下:

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { @Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { // 省略代码 } else { processBeanDefinitions(beanDefinitions); } return beanDefinitions; } private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { AbstractBeanDefinition definition; BeanDefinitionRegistry registry = getRegistry(); for (BeanDefinitionHolder holder : beanDefinitions) { definition = (AbstractBeanDefinition) holder.getBeanDefinition(); // 设置BeanClass为MapperFactoryBean definition.setBeanClass(this.mapperFactoryBeanClass); // 省略代码 } } }

在 Spring 中当一个 Bean 定义的 BeanClass 被设置为FactoryBean的时候,最终注册到容器中的实际上是它的getObject()方法返回的对象。而MapperFactoryBeangetObject()方法实际上还是通过SqlSession对象来获取 Mapper 的动态代理对象。从而实现了把原来
手动通过调用SqlSessiongetMapper()方法得到动态代理对象放到 Spring 容器中了,其它地方就可以通过@Autowired注解进行注入。代码如下:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { @Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/5 13:01:37

Java关键字解析之abstract:抽象的本质、规范定义与多态基石

前言 在Java面向对象的世界里&#xff0c;abstract是一个充满“前瞻性”的关键字——它像一张“设计蓝图”&#xff0c;将类或方法标记为“未完成”或“待实现”&#xff0c;强制后续开发者遵循预设的规范去填充细节。这种抽象性并非模糊不清&#xff0c;而是通过“定义标准、…

作者头像 李华
网站建设 2026/4/4 6:34:25

Mermaid在线编辑器新手入门指南:轻松制作专业图表

Mermaid在线编辑器新手入门指南&#xff1a;轻松制作专业图表 【免费下载链接】mermaid-live-editor Edit, preview and share mermaid charts/diagrams. New implementation of the live editor. 项目地址: https://gitcode.com/gh_mirrors/me/mermaid-live-editor 还在…

作者头像 李华
网站建设 2026/4/1 5:10:44

FPGA实战:一段让我重新认识时序收敛的FPGA迁移之旅

从Kintex-7到Versal&#xff1a;一段让我重新认识时序收敛的FPGA迁移之旅 摘要 &#xff1a;当一段在Kintex-7上稳定运行多年的MIPI Rx代码&#xff0c;迁移到Versal后开始随机出错&#xff0c;我没想到问题竟隐藏在一个看似"安全"的buffer逻辑中。这是一个关于时钟域…

作者头像 李华
网站建设 2026/3/28 6:35:52

YOLOv8+PyQt5农作物杂草检测(可以重新训练模型,yolov8模型,从图像、视频和摄像头三种路径识别检测,包含登陆页面、注册页面和检测页面)

资源包含可视化的农作物杂草检测系统&#xff0c;基于最新的YOLOv8训练的农作物杂草检测模型&#xff0c;和基于PyQt5制作的可视化农作物杂草检测系统&#xff0c;包含登陆页面、注册页面和检测页面&#xff0c;该系统可自动检测和识别图片或视频当中出现的农作和物杂草&#x…

作者头像 李华
网站建设 2026/3/27 14:41:51

OpenCore Legacy Patcher终极指南:让老Mac重获新生的10个技巧

OpenCore Legacy Patcher终极指南&#xff1a;让老Mac重获新生的10个技巧 【免费下载链接】OpenCore-Legacy-Patcher 体验与之前一样的macOS 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher OpenCore Legacy Patcher是一款革命性的开源工具…

作者头像 李华
网站建设 2026/3/21 9:55:33

云顶之弈智能助手:一键自动化挂机完全指南

云顶之弈智能助手&#xff1a;一键自动化挂机完全指南 【免费下载链接】LOL-Yun-Ding-Zhi-Yi 英雄联盟 云顶之弈 全自动挂机刷经验程序 外挂 脚本 ,下载慢可以到https://gitee.com/stringify/LOL-Yun-Ding-Zhi-Yi 项目地址: https://gitcode.com/gh_mirrors/lo/LOL-Yun-Ding-…

作者头像 李华