目录
mybatis spring 扫描分析
名称 | 版本 |
spring | 5.0.0 |
mybatis | 3.4.6 |
mybatis-spring | 1.3.0 |
简介
重点内容
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据源 -->
<property name="dataSource" ref="datasource"></property>
<!-- sql映射文件路径 -->
<property name="mapperLocations" value="classpath*:mapper/*Mapper.xml"></property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
<property name="basePackage" value="com.aya.mapper"></property>
</bean>
这里会有以下几个问题
1. mapperLocations 明明是一个Resource[] ,为什么可以通过通配符
转换为Resource[]
2. basePackage 怎么做通配符设置
mapperLocations配置
既然要分析如何将通配符
转换为Resource[]
,那么不管三七二十一,首先就去 setMapperLocations 下个断点,然后再根据堆栈去回溯
事实上,到这里已经是spring-core的地方了.
在这个栈的代码中,参数是通配符
,结果是Resource[]
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
// 删除了所有和主流程无关的代码
for (PropertyValue pv : original) {
convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
deepCopy.add(pv);
}
bw.setPropertyValues(new MutablePropertyValues(deepCopy));
}
接下来只要分析 convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
就知道如何转换的了.
然后从 convertForProperty 一层一层的跟进,跟进后,有两个值的注意的代码
1. findDefaultEditor 查找 Resource[]
的属性赋值器 PropertyEditor
2. doConvertValue 用 PropertyEditor 去设置值
private PropertyEditor findDefaultEditor(@Nullable Class<?> requiredType) {
// 省略不相关的代码
return this.propertyEditorRegistry.getDefaultEditor(requiredType);
}
private Object doConvertValue(@Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<?> requiredType, @Nullable PropertyEditor editor) {
// 省略不相关的代码
return doConvertTextValue(oldValue, newTextValue, editor);
}
private Object doConvertTextValue(@Nullable Object oldValue, String newTextValue, PropertyEditor editor) {
// 省略不相关的代码
editor.setAsText(newTextValue);
return editor.getValue();
}
那么 propertyEditorRegistry 默认提供了哪些属性赋值器呢? 这里不对属性赋值器进行详解,只是简单的罗列出来
类型 | 属性赋值器 |
java.io.InputStream | InputStreamEditor |
java.io.Reader | ReaderEditor |
java.lang.Class[] | ClassArrayEditor |
java.nio.file.Path | PathEditor |
org.springframework.core.io.Resource | ResourceEditor |
java.lang.Class | ClassEditor |
org.xml.sax.InputSource | InputSourceEditor |
java.net.URI | URIEditor |
java.net.URL | URLEditor |
org.springframework.core.io.Resource[] | ResourceArrayPropertyEditor |
org.springframework.core.io.ContextResource | ResourceEditor |
那么接下来就去 Resource[]
对应的 ResourceArrayPropertyEditor 去分析,是如何进行转换的
public void setAsText(String text) {
String pattern = resolvePath(text).trim();
try {
setValue(this.resourcePatternResolver.getResources(pattern));
}
catch (IOException ex) {
throw new IllegalArgumentException(
"Could not resolve resource location pattern [" + pattern + "]: " + ex.getMessage());
}
}
跟进 AbstractApplicationContext.getResources 发现关键在于resourcePatternResolver
public Resource[] getResources(String locationPattern) throws IOException {
return this.resourcePatternResolver.getResources(locationPattern);
}
去寻找 resourcePatternResolver 实际的对象
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
resourcePatternResolver 在构造时,被初始化为:PathMatchingResourcePatternResolver
这里不对 PathMatchingResourcePatternResolver 进行深入详解,可以见名知意的理解为: 路径匹配资源的规则解析器
那么也就是说,当函数 setXXX 的参数类型为 Resource[] 时,
spring会自动使用ResourceArrayPropertyEditor去赋值,我们就可以直接写通配符
basePackage通配符配置
开发方式从
- com.company.mapper/HandlerAMapper
- com.company.mapper/HandlerBMapper
转换为
- com.company.handlera/mapper/HandlerAMapper
- com.company.handlerb/mapper/HandlerAMapper
会有一个好处就是,通模块的Mapper,Service等各种代码都在一起,不用到处找包了.
问题在于 basePackage 它是不支持通配符扫描的,但是支持,
分割来配置多个包
那么我们要做的就是,将通配符转换为用,
分割的多个包就行了
接下来用 ScanBasePackage 将通配符
转换为用逗号分割的包名
public class ScanBasePackage {
private String wildcard;
public void setWildcard(String wildcard) {
this.wildcard = wildcard;
}
public String getPack() throws IOException, ClassNotFoundException {
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(resolver);
Set<String> stringList = new HashSet<>();
Resource[] resources = resolver.getResources("classpath*:"+wildcard+".class");
for(Resource resource:resources){
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
stringList.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
}
return stringList.stream().collect(Collectors.joining(","));
}
}
然后将xml配置部分的 basePackage 改为 #{scanPackageBean.pack} 占位符的形式
<bean id="scanPackageBean" class="com.aya.config.ScanBasePackage">
<property name="wildcard" value="com/**/*Mapper"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
<property name="basePackage" value="#{scanPackageBean.pack}"></property>
</bean>
这样就可以不用自己去拼接所有包名了
第二版扫描包
那么根据设置setXXX 参数为通配符的理论: 我们把 ScanBasePackage 改成下面这个样子
public class ScanBasePackage {
private Resource [] wildcard;
public void setWildcard(Resource [] wildcard) {
this.wildcard = wildcard;
}
public String getPack() throws IOException, ClassNotFoundException {
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
Set<String> stringList = new HashSet<>();
for(Resource resource:wildcard){
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
stringList.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
}
return stringList.stream().collect(Collectors.joining(","));
}
}
xml 文件
<bean id="scanPackageBean" class="com.aya.config.ScanBasePackage">
<property name="wildcard" value="classpath*:com/**/*Mapper.class"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
<property name="basePackage" value="#{scanPackageBean.pack}"></property>
</bean>
总结
- 使用spring 默认的 PropertyEditor 给mapperLocations 赋值
- 自定义setXXX 的参数类型为Resource[]时,可以使用通配符
- 使用 PathMatchingResourcePatternResolver 去解析通配符获得包名