• 解决提取Mybatis多数据源公共组件“At least one base package must be specified”的问题


          在一个微服务项目中,需要把数据库配置部分做成一个公共组件给需要的子服务依赖,这个数据库公共组件包含所有的数据源配置,但是子服务可以自行选择使用部分数据源,而且要自行维护mapper,所以每个数据源上的basePackages在不同的子服务里是不同的,这就需要把basePackages的值通过占位符配置在配置文件中读取。

          上面这些就是实现思路,但是这里有个问题,@MapperScan注解功能的实现类MapperScannerRegistrar实现的是ImportBeanDefinitionRegistrar。在对@Configuration注解标记的类配置时,实现占位符功能的PropertyPlaceholderAutoConfiguration还没有开始加载,所以为了动态读取配置文件信息,需要引入Environment,实现EnvironmentAware接口。

          所以接下来我就自定义了@MapperScan和MapperScannerRegister,主要是修改MapperScannerRegister中读取@MapperScan中basePackages的逻辑,代码如下:

    for (String pkg : annoAttrs.getStringArray("basePackages")) {
        if (StringUtils.hasText(pkg)) {
            String value = parsePlaceHolder(pkg);
            if (StringUtils.hasText(value)) {
                List<String> values = Arrays.asList(value.split(","));
                for (String base : values) {
                    basePackages.add(base);
                }
            }
        }
    }
    
    
    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 (null == value) {
          LOGGER.warn("The value of property '{}' is null", pro);
        }
    
        return value;
      }
    
      return pro;
    }

    数据源上的配置:

    @Configuration
    @MapperScan(basePackages = {"${mybatis.mapperScan.basePackages.order}"}, sqlSessionTemplateRef = "orderSqlSessionTemplate")
    public class OrderDataSourceConfig {...}

    下游子服务使用,在application.yml中增加配置:

    mybatis:
      mapperScan:
        basePackages:
          order: com.abc.bc.order.mapper.order

    然后启动子服务,很无情的失败了:

    [2020-12-28 00:03:38,450][ERROR][main][org.springframework.boot.SpringApplication:821]Application run failed
    java.lang.IllegalArgumentException: At least one base package must be specified
        at org.springframework.util.Assert.notEmpty(Assert.java:372)
        at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:272)
        at org.mybatis.spring.mapper.ClassPathMapperScanner.doScan(ClassPathMapperScanner.java:181)
        at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.scan(ClassPathBeanDefinitionScanner.java:253)
        at org.mybatis.spring.mapper.MapperScannerConfigurer.postProcessBeanDefinitionRegistry(MapperScannerConfigurer.java:356)
        at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275)
        at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:125)
        at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:705)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:531)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:742)
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:389)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:311)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1213)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1202)

    这是Spring的ClassPathBeanDefinitionScanner扫描器在扫码basePackages包并注册beanDefinitions时报错了,因为我们虽然不使用所有的数据源配置,但是其他的数据源配置上也指定了basePackages,我们没有配置其值,导致doScan(String... basePackages)时参数非空校验不通过。问题找到了,这明显是要子服务把所有的数据源配置上basePackages啊,这不符合要求啊,有可能这个项目有很多个数据源,而这个子服务刚好只需要使用其中一个,它就只需要配置自己需要的那些mapper。所以最终的设想就是即使子服务引入了全部的数据源,但是对于不需要使用的就不让Spring去扫描其配置的basePackages,这也是这个数据库公共组件应该支持的。接着改之前的MapperScannerRegister,如果basePackages为空,就不交给Spring管理了。代码如下:

    /**
     * 如果没有配置basePackages,就不注册bean
    */
    if (basePackages.isEmpty()) {
        return;
    }

    重新打包启动,一切正常。

    下面是MapperScannerRegister的完整代码:

    import org.mybatis.spring.mapper.MapperFactoryBean;
    import org.mybatis.spring.mapper.MapperScannerConfigurer;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.BeanUtils;
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    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.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(MapperScannerRegistrar.class);
    
        private Environment environment;
    
        @Override
        public void setResourceLoader(ResourceLoader resourceLoader) {
            // NOP
        }
    
        @Override
        public void setEnvironment(Environment environment) {
            this.environment = environment;
        }
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            AnnotationAttributes mapperScanAttrs = AnnotationAttributes
                    .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
            if (mapperScanAttrs != null) {
                registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
            }
        }
    
        void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
    
            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()));
    
            /**
             * 修改点
             */
            for (String pkg : annoAttrs.getStringArray("basePackages")) {
                if (StringUtils.hasText(pkg)) {
                    String value = parsePlaceHolder(pkg);
                    if (StringUtils.hasText(value)) {
                        List<String> values = Arrays.asList(value.split(","));
                        for (String base : values) {
                            basePackages.add(base);
                        }
                    }
                }
            }
    
            basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
                    .collect(Collectors.toList()));
    
            /**
             * 如果没有配置basePackages,就不注册bean
             */
            if (basePackages.isEmpty()) {
                return;
            }
    
            String lazyInitialization = annoAttrs.getString("lazyInitialization");
            if (StringUtils.hasText(lazyInitialization)) {
                builder.addPropertyValue("lazyInitialization", lazyInitialization);
            }
    
            builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
    
            registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    
        }
    
        private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
            return importingClassMetadata.getClassName() + "#" + org.mybatis.spring.annotation.MapperScannerRegistrar.class.getSimpleName() + "#" + index;
        }
    
        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 (null == value) {
                    LOGGER.warn("The value of property '{}' is null", pro);
                }
    
                return value;
            }
    
            return pro;
        }
    
    }
  • 相关阅读:
    自动滑块验证登录QQ-java实现
    今日校园自动提交问卷-Java实现
    文库下载实现自动化
    测试
    软件工程结课小结
    结对项目-java生成四则运算
    JS判断对象为空的三种方法
    vue 组件间 8 大通讯方式 之三 eventBus
    vue 组件间 8 大通讯方式 之二 provide/ inject ref / refs
    vue 组件间 8 大通讯方式 之一 props / $emit $children / $parent
  • 原文地址:https://www.cnblogs.com/zhou-920644981/p/14203055.html
Copyright © 2020-2023  润新知