1. MyBatis Mapper 接口的魔法:为什么能直接@Autowired?
第一次在Spring项目里看到MyBatis的Mapper接口被@Autowired注入时,我盯着那段代码看了足足三分钟。这完全违背了我对Java接口的认知——接口不是不能实例化吗?为什么这里能直接当Bean用?后来在跟踪一个NullPointerException时,我终于忍不住扒开了MyBatis的源码,这才发现背后藏着一套精妙的设计。
1.1 常规认知与现实的冲突
按照Java基础,接口确实不能直接实例化。我们通常需要先写一个实现类,比如:
java复制public interface UserMapper {
User selectById(Long id);
}
public class UserMapperImpl implements UserMapper {
@Override
public User selectById(Long id) {
// 实际数据库操作
}
}
然后在Spring配置中声明Bean:
xml复制<bean id="userMapper" class="com.example.UserMapperImpl"/>
但实际开发中,我们从未写过这些实现类。MyBatis让我们可以直接写这样的代码:
java复制@Autowired
private UserMapper userMapper;
这种"魔法"的实现,核心在于MyBatis和Spring的深度整合。当你用@Autowired注入Mapper接口时,Spring容器里实际存放的是一个动态代理对象,而不是传统意义上的Bean实例。
1.2 动态代理的幕后功臣
MyBatis通过JDK动态代理为每个Mapper接口生成代理对象。这个代理对象会拦截所有接口方法调用,将其转发给MyBatis的SQL执行引擎。具体过程是这样的:
- 应用启动时,MyBatis会扫描所有Mapper接口
- 对每个接口,使用Proxy.newProxyInstance()创建代理实例
- 将这些代理实例注册到Spring容器中
- 当@Autowired触发时,Spring返回的就是这个代理对象
关键点在于:虽然接口本身不能实例化,但JDK动态代理可以创建一个"实现了该接口的代理类"的实例。这就是为什么我们能直接注入接口的根本原因。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. MyBatis-Spring整合的底层机制
2.1 自动注册的关键组件
要让Mapper接口能被自动注入,项目中必须包含MyBatis-Spring整合包。这个包提供了几个核心组件:
- MapperScannerConfigurer:负责扫描指定包下的Mapper接口
- SqlSessionFactoryBean:创建MyBatis的SqlSessionFactory
- MapperFactoryBean:为每个Mapper接口创建工厂Bean
典型的配置如下:
java复制@Configuration
public class MyBatisConfig {
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer scanner = new MapperScannerConfigurer();
scanner.setBasePackage("com.example.mapper");
return scanner;
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
return factory.getObject();
}
}
2.2 注册过程的详细拆解
当应用启动时,整个过程是这样的:
- Spring容器初始化MapperScannerConfigurer
- 扫描器开始扫描basePackage指定的包路径
- 对找到的每个Mapper接口:
- 创建一个MapperFactoryBean
- 将该FactoryBean注册到Spring容器
- Bean的名称默认是接口名首字母小写
- 当需要注入Mapper时:
- Spring发现容器中有对应名称的Bean
- 调用FactoryBean的getO
