一、Mybatis集成Spring原理
Mybatis是一个单独的半ORM框架,可以与Spring集成使用,也可以单独使用,单独使用代码如下:
加载mybatis.xml的配置文件,然后解析配置文件,创建一个DefaultSqlSessionFactory的工厂对象,通过openSession()打开一个数据库连接的会话,通过getMapper()方法可以为对应的Mapper接口生成一个MapperProxy的代理对象,然后直接调用这个Mybatis的代理对象,就可以执行其中定义的SQL方法
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
System.out.println(orderMapper.selectOrder());
sqlSession.commit();
sqlSession.flushStatements();
sqlSession.close();
public interface OrderMapper {
@Select("select from 'order'")
String selectOrder();
}
从上面的Mybatis单独使用的方式来看,如果集成Spring,需要做几件事呢?
其中最重要的就是去扫描OrderMapper这样的接口,然后生成Mybatis的代理对象,由Spring进行统一管理
在Spring中要想初始Bean实例对象,首先肯定需要有BeanDefintion才行,而Spring本身在扫描指定包生成BeanDefinition的时候,只会把带有@Component注解的类生成BeanDefinition,而Mybatis中使用的都是接口,肯定无法扫描,所以在mybatis整合spring的时候,需要自己定义一套属于自己的扫描逻辑
就算是能够扫描接口了,也不能直接为接口去生成BeanDefinition,因为接口没有构造方法,没法进行实例化,这个时候,就会用到FactoryBean,为每一个Mapper接口都构造一个FactoryBean类型的BeanDefinition,这样就能通过BeanDefinition去先去实例化得到一个FactoryBean对象,然后在getBean()的时候,再去调用FactoryBean实例的getObject()方法生成mapper接口的代理对象。
从上面的说明可以看出,我们需要为每一个mapper接口来创建一个FactoryBean的BeanDefinition,但只需要定义一个FactoryBean的实现类即可,在该实现类中通过一个Class类型参数来指定mapper接口的类型,这样就可以用同一个FactoryBean来创建mapper接口的BeanDefinition,注册BeanDefinition有两种方式,分别是BeanDefinitionRegistry和ImportBeanDefinitionRegistrar接口中的registerBeanDefinition()和registerBeanDefinitions()方法,但对于mybatis来说,我只需要对mapper接口进行操作,那么在注册BeanDefinition的时候,首先需要获取mapper接口对应的包,才能生成BeanDefinition,有了这样一个要求,mybatis就只能通过ImportBeanDefinitionRegistrar来实现BeanDefinition的注册
二、扫描Mapper接口生成BeanDefinition
2.1 导入配置类
mybatis-spring中通过@MapperScan或@MapperScans来指定扫描的包,以@MapperScan为例,源码如下:
该注解最核心的点是通过@Import注解导入了MapperScannerRegistrar类,Spring在启动的时候,会把该类当作配置类进行解析,解析的时候执行该类的registerBeanDefinitions()方法
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
String[] value() default {};
String[] basePackages() 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;
String lazyInitialization() default "";
String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT;
}
2.2 配置类解析
在《配置类解析(中)》文章中,详细介绍了@Import注解的扫描,MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,所以会在每轮配置类解析完成之后,执行该类的registerBeanDefinitions()方法
MapperScannerRegistrar的方法如下:
@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));
}
}
在下面的registerBeanDefinitions()方法中,会构建一个MapperScannerConfigurer类的BeanDefinition,然后注册到BeanFactory中,其中会把@MapperScan注解中的的属性值全部加入到这个类的BeanDefinition中,其中最重要的两个属性分别是mapperFactoryBeanClass和basePackage
mapperFactoryBeanClass的值通过@MapperScan注解的factoryBean属性指定,该属性默认值是MapperFactoryBean,这个类实现了FactoryBean接口,就是在mybatis集成spring原理中介绍到的那个factoryBean
basePackage的值汇总了@MapperScan注解中value、basePackages和basePackageClasses属性的值
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
……
//获取FactoryBean
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
……
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()));
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
……
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
注:MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,在《Spring容器启动(下)》介绍了,在容器启动的时候,会扫描所有实现了BeanDefinitionRegistryPostProcessor接口的类,然后调用它的postProcessBeanDefinitionRegistry()来注册新的BeanDefinition,在mybatis-spring,主要就是用来注册mapper接口对应的FactoryBean的BeanDefinition
2.3 注册BeanDefinition
在第一部分也提到了,mybatis需要一个自定义的扫描器,来完成自定义逻辑的扫描,所以在MapperScannerConfigurer类的postProcessBeanDefinitionRegistry()方法中,首先创建一个扫描器,然后设置自定义扫描器的逻辑
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.registerFilters();
//扫描包生成BeanDefinition
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
自定义扫描器,主要的用处就在于添加自定义的IncludeFilter和ExcludeFilter
scanner.registerFilters()实现如该功能,如果没有指定只扫描哪个Mapper接口,那么acceptAllInterfaces就等于true,然后添加的IncludeFilter也是允许扫描包下的所有文件
public void registerFilters() {
boolean acceptAllInterfaces = true;
// if specified, use the given annotation and / or marker interface
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
……
if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// exclude package-info.java
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
});
}
然后调用scan()方法扫描指定包下的所有文件,生成BeanDefinition
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
三、设置BeanDefinition
调用ClassPathMapperScanner的scan()方法扫描时,会去调用doScan()方法,其中ClassPathMapperScanner的doScan()方法如下:
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 {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
ClassPathMapperScanner集成自Spring的ClassPathBeanDefinitionScanner,所以调用父类的doScan()方法进行扫描,得到mapper接口对应的BeanDefinition,而此时的BeanDefinition还只是的BeanDefinition,接下来把扫描得到的BeanDefinition进行修改,把BeanClass修改为MapperFactoryBean,把AutowireMode修改为byType,这样就变成了一个FactoryBean的BeanDefinition
processBeanDefinitions()方法最重要的工作就是改变BeanDefinition的属性,将其变成一个MapperFactoryBean的BeanDefinition,而MapperFactoryBean中需要有一个Mapper接口类型的属性值,所以MapperFactoryBean提供了通过构造方法的方式来注入mapperInterface的值
而这个值是通过definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName)
来指定的构造方法的参数值指定的,这样每一个FactoryBean的实例,都对应于一个Mapper接口的类型
同时设置MapperFactoryBean的注入方式为根据类型进行注入
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
boolean scopedProxy = false;
if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
definition = (AbstractBeanDefinition) Optional
.ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
.map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
"The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
scopedProxy = true;
}
String beanClassName = definition.getBeanClassName();
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
// 指定MapperFactoryBean构造方法的参数值,为当前BeanDefinition对应的Mapper接口的类型
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
//改变BeanDefinition为FactoryBean的BeanDefinition
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
……
// 设置根据类型进行注入
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
……
}
}
然后Spring容器启动完成之后,需要实例化非懒加载的单例Bean,这个时候,这些FactoryBean的BeanDefiniton都会进行实例化,生成一个单例的FactoryBean实例
而Mapper接口的代理实例通常是通过注入的时候才生成,在业务代码中,需要注入Mapper接口实例,操作数据库,Spring会根据依赖的类型注入实例,这时候就回去调用getBean()来生成实例,在生成实例的过程中,首先会从单例池中拿到FactoryBean的实例,然后再调用FactoryBean的getObject()方法来生成MapperProxy的代理对象
此时MapperFactoryBean实例的mapperInterface参数就是Mapper接口的类型,这样就能拿到一个MapperProxy的代理对象,执行SQL,至此mybatis整合spring的主要核心工作就做完了,剩下的就是mybatis自己的执行逻辑
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
注:mybatis整合spring还不止这些内容,其中关于sqlSession的部分,将在介绍完mybatis的源码后,用单独的文章介绍sqlSession的整合
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/153654.html