美文网首页
PathMatchingResourcePatternResol

PathMatchingResourcePatternResol

作者: 西城丶 | 来源:发表于2020-05-18 11:33 被阅读0次

问题描述

在spring boot项目中,需要自动化配置某些类的时候,可以自定义一个类,并且在resources目录下META/INF下增加spring.factories,项目启动时,会扫描这个类下的内容加载配置。

搭建dubbo分布式服务的时候,通常将web和service分离,并且增加一个facade包暴露接口给两方使用,dubbo的公共注册信息写在facade包下的资源目录下。

增加了一个类去读取这个配置文件的信息,这个配置文件也可以处理一些参数格式化的逻辑,比如说数据库密码在配置文件是加密的,通过这个类进行解密加载到环境变量。

代码实现

spring.factories文件

org.springframework.boot.env.EnvironmentPostProcessor=\
包路径.config.Configs
@Configuration
@Slf4j
public class Configs implements EnvironmentPostProcessor, Ordered {
    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        MutablePropertySources propertySources = environment.getPropertySources();
        // 根据当前环境变量(dev/prod/test)获取配置文件
        String[] profiles = environment.getActiveProfiles();
        Properties props = getConfig(profiles);
        propertySources.addLast(new PropertiesPropertySource("thirdEnv", props));
    }
    
    private Properties getConfig(String[] profiles) {
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        List<Resource> resourceList = new ArrayList<>();
        addResources(resolver, resourceList, "classpath*:config/*.properties");
        if (profiles != null) {
            for (String profile : profiles) {
                String path = "classpath*:config"
                        + File.separator
                        + profile
                        + File.separator
                        + "*.properties";
                addResources(resolver, resourceList, path);
            }
        }
        try {
            PropertiesFactoryBean config = new PropertiesFactoryBean();
            config.setLocations(resourceList.toArray(new Resource[0]));
            config.afterPropertiesSet();
            return config.getObject();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

问题出现

在本地开发的时候,上面的代码是没有问题的,但是将项目打包成war包之后,加载配置文件出现了问题,无法加载到配置文件的配置,从而其他地方引用到这些变量的地方报了错,项目启动失败。

问题定位

将打包后的war包丢到idea的tomcat里运行,进行调试

加载资源文件代码

private void addResources(PathMatchingResourcePatternResolver resolver, List<Resource> resourceList, String path) {
     try {
         Resource[] resources = resolver.getResources(path);
resourceList.addAll(Arrays.asList(resources));
     } catch (Exception e) {
         log.error("获取配置文件失败", e);
     }
 }

PathMatchingResourcePatternResolver类278行代码

public Resource[] getResources(String locationPattern) throws IOException {
   Assert.notNull(locationPattern, "Location pattern must not be null");
   if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
      // a class path resource (multiple resources for same name possible)
       // 如果是classpath*开头的,会扫描到多个文件并返回
      if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
         // a class path resource pattern
         return findPathMatchingResources(locationPattern);
      }
      else {
         // all class path resources with the given name
         return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
      }
   }
   }

PathMatchingResourcePatternResolver类490行代码

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
   String rootDirPath = determineRootDir(locationPattern);
   String subPattern = locationPattern.substring(rootDirPath.length());
   Resource[] rootDirResources = getResources(rootDirPath);
   Set<Resource> result = new LinkedHashSet<>(16);
   for (Resource rootDirResource : rootDirResources) {
      rootDirResource = resolveRootDirResource(rootDirResource);
      URL rootDirUrl = rootDirResource.getURL();
      if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
         URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
         if (resolvedUrl != null) {
            rootDirUrl = resolvedUrl;
         }
         rootDirResource = new UrlResource(rootDirUrl);
      }
      if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
         result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
      }
       // 如果是jar包,执行这里的代码
      else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
         result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
      }
      else {
         result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
      }
   }
   if (logger.isTraceEnabled()) {
      logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
   }
   return result.toArray(new Resource[0]);
}

扫描jar包下面的文件

PathMatchingResourcePatternResolver类646行代码

try {
   if (logger.isTraceEnabled()) {
      logger.trace("Looking for matching resources in jar file [" + jarFileUrl + "]");
   }
   if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
      // Root entry path must end with slash to allow for proper matching.
      // The Sun JRE does not return a slash here, but BEA JRockit does.
      rootEntryPath = rootEntryPath + "/";
   }
   Set<Resource> result = new LinkedHashSet<>(8);
   for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
      JarEntry entry = entries.nextElement();
      String entryPath = entry.getName();
      if (entryPath.startsWith(rootEntryPath)) {
         String relativePath = entryPath.substring(rootEntryPath.length());
         if (getPathMatcher().match(subPattern, relativePath)) {
            result.add(rootDirResource.createRelative(relativePath));
         }
      }
   }
   return result;
}

调试到这里,问题就定位到了

  • 如果是jar包,路径的分隔符默认是"/",不存在不同系统不同分隔符的问题
  • 如果是本地运行,扫描的是当前系统下的路径,用File.separator扫描不会出现问题

所以在加载配置文件的时候,传的路径是

classpath*:config" + File.separator
+ profile
+ File.separator
+ "*.properties"

的时候,扫描jar包的这个路径是扫描不到的,所以配置文件无法加载。

问题解决

知道这个问题就好办了,直接将File.separator修改为"/"就好了

相关文章

  • PathMatchingResourcePatternResol

    项目中,有时需要通过特定的字符寻找特定的类,如mybatis,可以通过适配符寻找要扫描的目录。我们自己也可以通过适...

  • PathMatchingResourcePatternResol

    问题描述 在spring boot项目中,需要自动化配置某些类的时候,可以自定义一个类,并且在resources目...

网友评论

      本文标题:PathMatchingResourcePatternResol

      本文链接:https://www.haomeiwen.com/subject/iwcuohtx.html