问题描述
在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修改为"/"就好了
网友评论