SpringBoot实现MapperScan添加动态配置(占位符)

 更新时间:2022年01月10日 10:03:14   作者:BUse  
这篇文章主要介绍了SpringBoot实现MapperScan添加动态配置(占位符),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教。

MapperScan添加动态配置(占位符)

在对Mybatis自动扫描配置中,使用注解配置时,@MapperScan中的配置,通常配置如下:

@MapperScan(basePackages = {"com.aa.**.mapper","com.bb.**.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory")

不支持在yml或properties文件中动态配置。因为MapperScan注解功能的实现类MapperScannerRegistrar实现的是ImportBeanDefinitionRegistrar。在对@Configuration注解标记的类配置时,实现占位符功能的PropertyPlaceholderAutoConfiguration还没有开始加载。

MapperScannerRegistrard的核心代码如下

List<String> basePackages = new ArrayList();
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
        scanner.registerFilters();
        scanner.doScan(StringUtils.toStringArray(basePackages));

从上面代码中可以看出

为了拓展支持占位符动态配置,只需把basePackages的加载改写即可。

1、参考MapperScannerRegistrard,实现 ImportBeanDefinitionRegistrar和ResourceLoaderAware两个接口

2、为了动态读取配置文件信息,需要引入Environment,所以实现EnvironmentAware接口

3、代码

MapperScannerRegistrar.java

import org.mybatis.spring.mapper.ClassPathMapperScanner;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils; 
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.stream.Collectors;
 
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware,EnvironmentAware {
    private Environment environment;
    private ResourceLoader resourceLoader;
    private  static  final Logger logger = LoggerFactory.getLogger(MapperScannerRegistrar. class);
 
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
 
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
 
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 
        AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScanner.class.getName()));
        if (mapperScanAttrs != null) {
            this.registerBeanDefinitions(mapperScanAttrs, registry);
        }
    }
 
    void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        Optional var10000 = Optional.ofNullable(this.resourceLoader);
        Objects.requireNonNull(scanner);
        Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
        if (!Annotation.class.equals(annotationClass)) {
            scanner.setAnnotationClass(annotationClass);
        }
 
        Class<?> markerInterface = annoAttrs.getClass("markerInterface");
        if (!Class.class.equals(markerInterface)) {
            scanner.setMarkerInterface(markerInterface);
        }
 
        Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
        if (!BeanNameGenerator.class.equals(generatorClass)) {
            scanner.setBeanNameGenerator((BeanNameGenerator) BeanUtils.instantiateClass(generatorClass));
        }
 
        Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
        if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
            scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass);
        }
 
        scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
        scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
        List<String> basePackages = new ArrayList<String>();
        basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
        for (String pkg : annoAttrs.getStringArray("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                String value = parsePlaceHolder(pkg);
                if(StringUtils.hasText(value)){
                    List<String> valueList = Arrays.asList(value.split(","));
                    for(String base : valueList){
                        basePackages.add(base);
                    }
                }
            }
        }
        basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
        scanner.registerFilters();
        scanner.doScan(StringUtils.toStringArray(basePackages));
    }
 
    private String parsePlaceHolder(String pro) {
        if (StringUtils.hasText(pro) && pro.contains(PropertySourcesPlaceholderConfigurer.DEFAULT_PLACEHOLDER_PREFIX)) {
            String value = environment.getProperty(pro.substring(2, pro.length() - 1));
 
            if (logger.isDebugEnabled()) {
                logger.debug("find placeholder value " + value + " for key " + pro);
            }
 
            if (null == value) {
                throw new IllegalArgumentException("property " + pro + " can not find!!!");
            }
            return value;
        }
        return pro;
    }
}

MapperScanner.java

import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.Import; 
import java.lang.annotation.*;
 
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
public @interface MapperScanner {
    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;
}

4、使用MapperScanner,占位符扫描配置

@MapperScanner(basePackages = { "${mybatis.mapperScanner.basePackage}" }, sqlSessionFactoryRef = "sqlSessionFactory")
mybatis:
  mapperScanner:
    basePackage: com.aa.**.mapper,com.bb.**.mapper

关于@MapperScan配置

问题

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): top.yangzefeng.usercenter.user.service.UserService.getById

解决方案

常用配置:

@MapperScan("top.yangzefeng.usercenter.**.mapper")
  • @MapperScan("com.demo.mapper"):扫描指定包中的接口
  • @MapperScan("com.demo.*.mapper"):一个代表任意字符串,但只代表一级包,比如可以扫到com.demo.aaa.mapper,不能扫到com.demo.aaa.bbb.mapper
  • @MapperScan("com.demo.**.mapper"):两个代表任意个包,比如可以扫到com.demo.aaa.mapper,也可以扫到com.demo.aaa.bbb.mapper

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • SpringBoot使用AOP统一日志管理的方法详解

    SpringBoot使用AOP统一日志管理的方法详解

    这篇文章主要为大家分享一个干货:超简洁SpringBoot使用AOP统一日志管理,文中的示例代码讲解详细,感兴趣的小伙伴快跟随小编一起学习学习吧
    2022-05-05
  • Java生成订单号或唯一id的高并发方案(4种方法)

    Java生成订单号或唯一id的高并发方案(4种方法)

    本文主要介绍了Java生成订单号或唯一id的高并发方案,包括4种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-01-01
  • mybatis Example Criteria like 模糊查询问题

    mybatis Example Criteria like 模糊查询问题

    这篇文章主要介绍了mybatis Example Criteria like 模糊查询问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • 使用jsoup解析html的table中的文本信息实例

    使用jsoup解析html的table中的文本信息实例

    今天小编就为大家分享一篇使用jsoup解析html的table中的文本信息实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-05-05
  • 解决Shiro 处理ajax请求拦截登录超时的问题

    解决Shiro 处理ajax请求拦截登录超时的问题

    这篇文章主要介绍了解决Shiro 处理ajax请求拦截登录超时的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • java实现航空用户管理系统

    java实现航空用户管理系统

    这篇文章主要为大家详细介绍了java实现航空用户管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • 使用FeignClient设置动态Url

    使用FeignClient设置动态Url

    这篇文章主要介绍了使用FeignClient设置动态Url方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • SpringBoot自定义注解使用读写分离Mysql数据库的实例教程

    SpringBoot自定义注解使用读写分离Mysql数据库的实例教程

    这篇文章主要给大家介绍了关于SpringBoot自定义注解使用读写分离Mysql数据库的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • springboot如何使用vue打包过的页面资源

    springboot如何使用vue打包过的页面资源

    这篇文章主要介绍了springboot如何使用vue打包过的页面资源,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • Java设计模式中的设计原则之合成复用原则详解

    Java设计模式中的设计原则之合成复用原则详解

    这篇文章主要介绍了Java设计模式中的设计原则之合成复用原则详解,原则是尽量使用合成/聚合的方式,而不是使用继承聚合关系表示的是整体和部分的关系,整体与部分可以分开,可以理解为成员变量和当前类的关系就是聚合关系,需要的朋友可以参考下
    2023-11-11

最新评论