解決提取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;
    }

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM