mybatis-spring 自动扫描详解

目录

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的地方了.

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.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>

总结

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

猜你喜欢

转载自blog.csdn.net/mz4138/article/details/81063059