上篇博客中笔者介绍怎么Import注解中的类给变成一个BeanDefinition
添加到Spring
的IOC的容器中,主要调用的是这个类(MapperScannerRegistrar
)的registerBeanDefinitions()
,由于篇幅的原因,笔者只介绍了如何调用的MapperScannerRegistrar
类中的registerBeanDefinitions()
的方法。具体方法中的逻辑还没有讲,由于Mybatis-Spring这个有两个版本,所以笔者今天就打算讲下这两个版本的实现逻辑和原理。
由于上篇博客笔者已经介绍了怎么调用到对应的registerBeanDefinitions()
,笔者这篇博客就不赘述了。直接来看registerBeanDefinitions()
方法的代码,具体的代码如下:
//importingClassMetadata 注解的元数据
//registry DefaultListableBeanFactory
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//获取MapperScan注解数据
AnnotationAttributes annoAttrs =
AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
//ClassPathMapperScanner继承的是ClassPathBeanDefinitionScanner有篇博客中笔者介绍了自定义注解的时候,介绍过这个类
//这个类笔者等下说 自定义的扫描规则
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
// 这个属性在上篇博客中实例化MapperScannerRegistrar的时候设置的
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
//此属性指定扫描程序将扫描的注解,一般都是默认的Annotation。
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
//此属性指定扫描器将扫描的父对象。一般默认都是Class
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
//名字生成策略,一般都是默认的BeanNameGenerator
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
//指定一个自定义MapperFactoryBean以将mybatis代理返回为spring bean 一般默认就是MapperFactoryBean
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
//指定在spring上下文中有多个数据源的情况下使用哪个SqlSessionTemplate。通常只有在您拥有多个数据源时才需要这样做,默认是“”
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
//指定在spring上下文中有多个数据源的情况下使用哪个SqlSessionFactory。通常只有在您拥有多个数据源时才需要这样做。默认是“”
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
//扫描的路径
List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
//扫描的路径
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
//指定扫描的类
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
//通过自定义的扫描器,注册对应的过滤器
scanner.registerFilters();
//通过自定义的扫描器,然后进行扫描
scanner.doScan(StringUtils.toStringArray(basePackages));
}
上面的代码主要就是解析对应的@MapperScan
中的数据,同时将这些数据填充到一个自定义的扫描器中去,主要是ClassPathMapperScanner
这个类,这个类是继承ClassPathBeanDefinitionScanner
类,有篇博客笔者介绍了自定义注解,具体是Spring源码系列(四)Spring扫描机制(一),读者可以参考一下这篇博客,这样看ClassPathMapperScanner
类的代码会比较好理解点,这个时候我们直接来看ClassPathMapperScanner
的代码,具体的代码如下:
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
//三个构造函数,真好有BeanDefinitionRegistry,所以就调用这个构造方法
public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
super(registry, false);
}
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;
}
// override AssignableTypeFilter to ignore matches on the actual marker interface
// 覆盖AssignableTypeFilter(简单的过滤器)也是添加对应的过滤器
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
//如果上面设置annotation或者是markerInterface就不会采用默认的过滤机制
if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
}
// exclude package-info.java
// 排除 package-info.java
addExcludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
}
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//调用父类的扫描的方法,前面的博客已经说过了,这儿会执行两个过滤的方法,过滤的规则我们上面已经写了,这儿第一次过滤扫描出来所有的class
//第二次过滤会扫描出来是接口的类,在下面我们重写了对应的方法。
//由于返回的set集合,引用,我们对set集合中的BeanDefinition修改同样也会作用到spring的容器中去。
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进行相应的处理,最后添加到spring容器中去
processBeanDefinitions(beanDefinitions);
}
//返回对应的BeanDefinition
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
//遍历所有的BeanDefinition
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
// 设置对应的构造参数
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
// 设置对应的BeanClass
definition.setBeanClass(this.mapperFactoryBean.getClass());
//设置对应的属性值 默认为true
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
//判断这些参数有没有设置,如果有直接设置进去
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
//如果上面有一个参数设置了值,注入的模型就不是byType
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
//第二次过滤,返回的是接口同时是独立的类
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
}
上面的代码就是mybatis重写了ClassPathBeanDefinitionScanner
类中的方法,默认的情况下,将第一次过滤的条件改成了所有的class
都会扫描出来。同时重写了第二次过滤的方法,将第二次过滤的条件改成了是接口同时也是一个独立类的情况。最后扫描出来的BeanDefinition
已经注册到Spring
容器中去了,由于返回的是set集合,mybatis又写了一个方法processBeanDefinitions
,将原来的BeanDefinition
进行了修改。以达到能对接口能代理的目的。而这里注册BeanDefinition
中BeanClass
的值是MapperFactoryBean
类型,这个类继承了FactoryBean
,至于Spring怎么将这个继承FactoryBean
的类转换成Bean
,后面的博客会讲到。然后后面的代理就是mybatis的事,至于怎么代理的,可以看Mybatis源码解析的博客。至此老版本的Mybatis-Spring的原理就讲完了。
还是和上面一样,我们走来先看registerBeanDefinitions()
方法,具体的代码如下:
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//获取MapperScan的注解的元数据
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) {
//调用spring写的BeanDefinition的生成器,直接将MapperScannerConfigurer生成一个BeanDefinition,这个MapperScannerConfigurer实现了
//BeanDefinitionRegistryPostProcessor类
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
//添加对应的属性
builder.addPropertyValue("processPropertyPlaceHolders", true);
//添加对应的属性和前面一样的道理
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
//添加对应的属性
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}
//名字生成的规则
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
//对应的属性
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
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("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()));
//如果获取的值都是为空的话,直接将当前加了@MapperScan注解的类的包名添加进去。
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
String lazyInitialization = annoAttrs.getString("lazyInitialization");
if (StringUtils.hasText(lazyInitialization)) {
builder.addPropertyValue("lazyInitialization", lazyInitialization);
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
//将这个BeanDefinition注册到容器中去
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
//生成对应的名字,生成的规则就是加了这个的注解的全类名#MapperScannerRegistrar#0
private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
return importingClassMetadata.getClassName() + "#" + MapperScannerRegistrar.class.getSimpleName() + "#" + index;
}
private static String getDefaultBasePackage(AnnotationMetadata importingClassMetadata) {
return ClassUtils.getPackageName(importingClassMetadata.getClassName());
}
上面就是代码就是注册了一个BeanDefinition
到容器中去,而这个BeanDefinition
对应的BeanClass
是MapperScannerConfigurer
类实现了BeanDefinitionRegistryPostProcessor
这个接口,这个接口的实现类的执行时机,笔者在Spring源码系列(五)Spring扫描机制(二)博客中有讲到,这儿就不阐述了。可以去看下那篇博客,然后Spring会调用到MapperScannerConfigurer
类中的postProcessBeanDefinitionRegistry
方法,具体的代码如下:
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
//这个值为true,前面传入进来的
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));
}
//和老的版本是一样的
scanner.registerFilters();
//最终会调用到子类的doScan的方法,就是和上面的是一样的
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
//BeanDefinitionRegistries在应用程序启动的早期,即BeanFactoryPostProcessors之前被调用。
//这意味着PropertyResourceConfigurers将不会被加载,并且此类属性的任何属性替换都将失败。
//为了避免这种情况,请找到上下文中定义的所有PropertyResourceConfigurers并在此类的bean定义上运行它们。然后更新值
private void processPropertyPlaceHolders() {
Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class,
false, false);
//一般的情况下都是为空
if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory()
.getBeanDefinition(beanName);
// PropertyResourceConfigurer does not expose any methods to explicitly perform
// property placeholder substitution. Instead, create a BeanFactory that just
// contains this mapper scanner and post process the factory.
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition(beanName, mapperScannerBean);
for (PropertyResourceConfigurer prc : prcs.values()) {
prc.postProcessBeanFactory(factory);
}
PropertyValues values = mapperScannerBean.getPropertyValues();
this.basePackage = updatePropertyValue("basePackage", values);
this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
this.lazyInitialization = updatePropertyValue("lazyInitialization", values);
}
//强制设置对应的属性
this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName)
.map(getEnvironment()::resolvePlaceholders).orElse(null);
this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName)
.map(getEnvironment()::resolvePlaceholders).orElse(null);
this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders)
.orElse(null);
}
上面的代码就是通过spring的机制最后执行postProcessBeanDefinitionRegistry
方法,最后在这个方法中完成了扫描和注册。
新老版本的区别:
MapperScannerRegistrar
的registerBeanDefinitions
方法的时候完成扫描和将接口转成对应的BeanDefinition
注册到spring的IOC容器中。MapperScannerRegistrar
的registerBeanDefinitions
方法的时候添加了一个BeanClass
为MapperScannerConfigurer
的BeanDefinition
,然后利用了spring调用BeanDefinitionRegistryPostProcessor
接口实现类的机制,在MapperScannerConfigurer
中的postProcessBeanDefinitionRegistry
的方法中完成了扫描和将接口转成对应的BeanDefinition
注册到spring的IOC容器中。BeanDefinition
的注册比老版本的接口对应的BeanDefinition
注册要晚点。至此MapperScan注解的原理就讲完了。这种方式,为我们以后开发我们自己的插件整合spring,提供思路。至于新版本的优点是什么?笔者自己也不是很清楚,如果有人知道,可以告诉笔者。下篇博客笔者可能会讲用dbutils模拟Mybatis整合spring。