介绍
目前大部分的 Java 互联网项目,都是用 Spring MVC + Spring + MyBatis 搭建平台的。使用 Spring IoC 可以有效的管理各类的 Java 资源,达到即插即拔的功能;通过 Spring AOP 框架,数据库事务可以委托给 Spring 管理,消除很大一部分的事务代码,配合 MyBatis 的高灵活、可配置、可优化 SQL 等特性,完全可以构建高性能的大型网站。
在 Spring 环境中使用 MyBatis 也更加简单,节省了不少代码,甚至可以不用 SqlSessionFactory、 SqlSession 等对象,因为 MyBatis-Spring 为我们封装了它们。
思考
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
Mybatis通过解析配置文件生成SqlSessionFactory,然后通过openSession()获得SqlSession对象,最终可根据getMapper(Class)得到Mapper的实现类。
@Component
public interface CommodityDao {
List<CmmdtyInfo> findByCommodityCode(String code);
}
我们一般在spring中运用Mybatis,会如上面代码写个接口类,然后在service中通过注入的接口类直接调用,所以我们可以思考,是不是Spring在中间帮我们完成了接口类的实现?因为service中注入的属性一定是接口类的实现类。
带着这样的疑问,我们走进源码世界。
源码解析
Spring中使用Mybatis,我们一般用到@MapperScan(“扫描路径”)注解,表示Spring需要扫描Mapper存放的路径,我们从这个注解开始看起。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends Annotation> annotationClass() default Annotation.class;
Class<?> markerInterface() default Class.class;
String sqlSessionTemplateRef() default "";
String sqlSessionFactoryRef() default "";
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
MapperScan 首先是个注解类,其中比较重要的属性value、basePackages、basePackageClasses等,表示基础路径或者类,接下来Spring会这这些路径和其他配置进行扫描。
其他,我们看MapperScan类上有个@Import(MapperScannerRegistrar.class),关于@Import前面博客有专门讲解,有兴趣可翻阅。这边Spring在执行到@MapperScan注解时,会继续执行@Import注解里方法。
我们继续看MapperScannerRegistrar类,实现了ImportBeanDefinitionRegistrar类。@Import注解的执行时期是Spring生成所有BeanDefinition、在bean生成之前的时期,所有@Import可以对BeanDefinition进行新增、修改、删除等操作。
@Import支持 三种方式
1.带有@Configuration的配置类
2.ImportSelector 的实现
3.ImportBeanDefinitionRegistrar 的实现
Spring与Mybatis的整合便使用的ImportBeanDefinitionRegistrar的实现类。这边说下registerBeanDefinitions方法,实现ImportBeanDefinitionRegistrar类必须实现的方法。有两个参数,importingClassMetadata是@Import注解的原信息,即注解相关属性;还有一个registry,是存放BeanDefinition的地方,可通过registry对BeanDefinition进行修改。
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//通过importingClassMetadata获取MapperScan的注解信息
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
//传入registry构造ClassPathMapperScanner对象
//ClassPathMapperScanner是mybatis实现的扫描器
//继承Spring的扫描器ClassPathBeanDefinitionScanner
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
//省略中间不重要代码
List<String> basePackages = new ArrayList<String>();
//获取value属性,存入基础扫描路径
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
//获取basePackages属性,存入基础扫描路径
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
//获取basePackageClasses属性,存入基础扫描路径
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
//调用doScan方法执行扫描操作
scanner.doScan(StringUtils.toStringArray(basePackages));
}
}
ClassPathMapperScanner.doScan方法,首先会调用父类Spring的扫描方法
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//调用Spring的扫描逻辑
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
//扫描结果为空,打印日志后返回
//否则调用processBeanDefinitions方法,对BeanDefinition修改
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
ClassPathMapperScanner.processBeanDefinitions方法,对BeanDefinition修改。
这边说明下,Spring创建bean的过程,先根据配置文件或者注解信息生成BeanDefinition,然后根据BeanDefinition生成bean。需要我们通过修改BeanDefinition,可以得到我们需要的bean。
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
//BeanDefinitionHolder是BeanDefinition的包装类,持有BeanDefinition
for (BeanDefinitionHolder holder : beanDefinitions) {
//强转成GenericBeanDefinition
definition = (GenericBeanDefinition) holder.getBeanDefinition();
//definition的构造函数传值为definition的class名称,即Mapper接口名
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
//设置definition的BeanClsss为mapperFactoryBean的Class类型
definition.setBeanClass(this.mapperFactoryBean.getClass());
//省略下面特殊场景代码,无关紧要
}
}
processBeanDefinitions方法主要做了两件事,传入构造函数的属性,修改bean的Class类型为mapperFactoryBean。所以我们要继续研究mapperFactoryBean究竟做了上面?
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
public MapperFactoryBean() {
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
}
我们看MapperFactoryBean,继承SqlSessionDaoSupport ,实现FactoryBean接口。实现FactoryBean接口需实现getObjectType和getObject方法,即类型和对象。
这边先说下FactoryBean,FactoryBean是个特殊的bean。在Spring生成FactoryBean的bean时,会往Spring容器中添加两个Bean,一个是FactoryBean本身的bean,一个是实现getObjectType和getObject方法返回的bean。
MapperFactoryBean有两个构造函数,一个是空的构造,一个是传入Class类型的构造。由于processBeanDefinitions方法中往BeanDefinition中构造函数中传入了Class类型的参数,Spring在实例化Bean时推断构造方法会使用传入Class类型的构造。
MapperFactoryBean的getObjectType方法返回Mapper接口的类型,getObject方法则根据getSqlSession().getMapper(Class c)方法获取对象,getSqlSession().getMapper()是不是很眼熟,Mybatis原生的获取Mapper实现类的方法。
总结
Spring与Mybatis整合步骤:
1、通过@MapperScan注解的配置获取Mapper接口存放路径
2、调用@Import(MapperScannerRegistrar.class)注解,做两件事,即3、4步骤
3、调用Spring的包扫描逻辑将Mapper接口转换成BeanDefiniton,存放在BeanDefinitionRegistry中
4、循环所有Mapper接口对应的BeanDefiniton,修改BeanDefiniton的构造函数入参和BeanDefiniton的BeanClass类型为mapperFactoryBean类型
5、mapperFactoryBean中getObejct方法返回Mybatis的sqlSession.getMapper方法结果
6、Spring根据BeanDefiniton生成bean,即bean本质为Mybatis生成的代理Bean
什么是FactoryBean?
1、FactoryBean是接口,实现FactoryBean需实现接口中的getObjectType和getObject方法
2、FactoryBean的实现类在Spring中,会生成两个Baen
3、根据FactoryBeanName获取getObject的bean
4、根据&+FactoryBeanName获取FactoryBean本身的bean
@Import注解做了什么?
支持三种方式:
1.带有@Configuration的配置类
2.ImportSelector 的实现
3.ImportBeanDefinitionRegistrar 的实现
在Spring生成BeanDifinition、未实例化bean之前,可对BeanDifinition进行添加、删除、修改操作。