mybatis-spring 自动扫描详解

目录

mybatis spring 扫描分析

名称版本
spring5.0.0
mybatis3.4.6
mybatis-spring1.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的地方了.

mapperLocattions调用堆栈

在这个栈的代码中,参数是通配符,结果是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.InputStreamInputStreamEditor
java.io.ReaderReaderEditor
java.lang.Class[]ClassArrayEditor
java.nio.file.PathPathEditor
org.springframework.core.io.ResourceResourceEditor
java.lang.ClassClassEditor
org.xml.sax.InputSourceInputSourceEditor
java.net.URIURIEditor
java.net.URLURLEditor
org.springframework.core.io.Resource[]ResourceArrayPropertyEditor
org.springframework.core.io.ContextResourceResourceEditor

那么接下来就去 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>

总结

  1. 使用spring 默认的 PropertyEditor 给mapperLocations 赋值
  2. 自定义setXXX 的参数类型为Resource[]时,可以使用通配符
  3. 使用 PathMatchingResourcePatternResolver 去解析通配符获得包名
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值