Java
开发领域, MyBatis
作为一款优秀的持久层框架,凭借其灵活的 SQL
映射功能,受到众多开发者的青睐。然而,在纯 MyBatis
项目中, Mapper
接口的使用却存在诸多痛点。随着 MyBatis
与 Spring
框架的整合,这些问题得到了有效解决,尤其是 Mapper
接口的自动化注册机制,极大提升了开发效率与代码规范性。接下来,我们深入探讨纯 MyBatis
项目 Mapper
接口使用的困境,以及 MyBatis-Spring
如何实现接口的自动化注册与便捷调用
在纯 MyBatis
项目中,使用 Mapper
接口时,开发者需要手动通过SqlSession
获取对应的 Mapper
实例,代码示例如下:
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectById(1);
}
上述代码中,每次使用 Mapper
接口都需要重复获取SqlSession
和 Mapper
实例的过程,不仅增加了代码量,还容易导致资源管理混乱。特别是在大型项目中,大量的 Mapper
接口调用会使代码变得冗长且难以维护。
由于获取 Mapper
实例的操作分散在各个业务逻辑中,不同开发者的编码习惯和风格差异,会导致 Mapper
接口的使用方式不统一。有些开发者可能会在多个地方重复编写获取SqlSession
和 Mapper
的代码,有些则可能在资源关闭时出现异常处理不规范的情况。这种不规范的代码编写方式,增加了团队协作的难度,也给后期的代码维护和调试带来了困扰。
纯 MyBatis
项目中,Mapper
接口与SqlSession
紧密耦合,使得代码的依赖关系变得复杂。在进行单元测试或模块重构时,由于 Mapper
接口依赖于SqlSession
的创建和管理,难以实现独立的测试和模块拆分,降低了代码的可测试性和可维护性。
在传统的 MyBatis
项目中,获取 Mapper
接口实例需要通过SqlSession.getMapper()
方法,这一过程繁琐且不够优雅。而 MyBatis-Spring
整合后,开发者可以直接将 Mapper
接口作为 Bean
注入到 Spring
容器中使用
通过MapperScannerConfigurer
、ClassPathMapperScanner
和MapperFactoryBean
等关键类的协同工作,MyBatis-Spring
实现了 Mapper
接口的自动化注册,让开发者摆脱了繁琐的SqlSession.getMapper()
调用,极大提升了开发效率和代码的简洁性。
MapperScannerConfigurer
实现了BeanDefinitionRegistryPostProcessor
接口,在 Spring
容器加载 Bean
定义阶段,它会扫描指定包下的 Mapper
接口,并将其注册为 Spring Bean
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// 创建ClassPathMapperScanner扫描器
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.registerFilters();
// 执行扫描,将Mapper接口注册为BeanDefinition
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
public int scan(String... basePackages) {
// 记录扫描开始前的bean定义总数
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 执行实际的扫描逻辑
doScan(basePackages);
// 如果启用了注解配置处理,注册必要的注解处理器
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
// 返回本次扫描新增的bean定义数量
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
上述代码中,postProcessBeanDefinitionRegistry
方法是核心。它创建ClassPathMapperScanner
实例,配置扫描器的各项属性,然后调用scan
方法扫描指定包路径下的 Mapper
接口,并将其注册为 Spring BeanDefinition
ClassPathMapperScanner
继承自ClassPathBeanDefinitionScanner
,重写了isCandidateComponent
和doScan
方法,用于筛选和处理 Mapper
接口。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
// 省略构造函数等代码
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// 检查类不是接口且不依赖于封闭类
return (beanDefinition.getMetadata().isInterface() &&!beanDefinition.getMetadata().isAbstract());
}
@Override
public 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 {
// 处理扫描到的BeanDefinition
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
// 处理作用域代理情况
if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
// ...
}
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// 【核心代码】设置Mapper接口作为构造函数参数(将 Mapper 接口的类名作为参数传递给MapperFactoryBean的构造函数)
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 设置mapperInterface属性,用于Spring Native支持
try {
definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
} catch (ClassNotFoundException ignore) {
// ...
}
// 【核心代码】设置Bean类为MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
// 设置addToConfig属性
definition.getPropertyValues().add("addToConfig", this.addToConfig);
// 设置MockitoPostProcessor需要的属性
definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
// 配置SqlSessionFactory或SqlSessionTemplate
boolean explicitFactoryUsed = false;
// ...
}
}
// 省略其他方法
}
doScan
方法是扫描过程的核心入口,它首先调用父类的doScan
方法获取候选组件,然后通过processBeanDefinitions
方法对这些组件进行进一步处理
processBeanDefinitions
方法是 Mapper
接口注册的关键环节,它执行以下重要操作:
BeanClass
设置为MapperFactoryBean
,这意味着 Spring
在创建 Bean
实例时,会使用MapperFactoryBean
的getObject()
方法来获取实例BeanDefinition
中,用于后续MapperFactoryBean
初始化时的mapperInterface
属性Autowire
模式为 byType
,确保 Spring
能够正确注入依赖SqlSessionFactory
或SqlSessionTemplate
属性,为MapperFactoryBean
提供必要的依赖当 Mapper
接口被注册为 Bean
后,其对应的 Bean
实例是通过MapperFactoryBean
创建的。MapperFactoryBean
实现了FactoryBean
接口,用于生成 Mapper
接口的代理实例。
getObject
方法是核心,它通过SqlSessionTemplate
获取 Mapper
接口的代理实例,最终实现了将 Mapper
接口作为 Bean
注入到 Spring
容器中使用的功能。
public class MapperFactoryBean<T> implements FactoryBean<T>, InitializingBean, ApplicationContextAware {
private Class<T> mapperInterface;
/**
* 获取Mapper接口的代理实现实例。
* 此方法通过SqlSession获取指定Mapper接口的代理实现,
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
protected void checkDaoConfig() {
// ...
}
}
对于BeanFactoryPostProcessor
、FactoryBean
不熟悉的同学可以参考一下下面文章:
【Spring BeanFactoryPostProcessor:机制解读与代码实践】
【浅析 Spring 中 FactoryBean 的实现与使用】