我们经常把spring需要加载的properties文件放在java/resources下面,这样存放的问题导致properties在打包后就在jar的根目录下,所以我们的spring的配置路径就是classpath*:xxx.properties,但是这样的jar我们在被其他项目引用的时候会发现properties文件老是无法加载,就这个问题从spring的源码来找找为什么会这样.
首先properties是当做一个resource来加载的,实现加载的是org.springframework.core.io.ResourceLoader接口的一个实现,其中org.springframework.core.io.support.PathMatchingResourcePatternResolver用于解析classpath*:为前缀的资源定义.
PathMatchingResourcePatternResolver的getResources方法里面有以下内容
String CLASSPATH_ALL_URL_PREFIX = "classpath*:"; if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { // a class path resource (multiple resources for same name possible) 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())); } }
如果我们用classpath*:config-*.properties来查找资源,会进入findPathMatchingResources方法,我们看看这个方法前几行怎么写的
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException { String rootDirPath = determineRootDir(locationPattern); String subPattern = locationPattern.substring(rootDirPath.length()); Resource[] rootDirResources = getResources(rootDirPath);
这里经过determineRootDir(locationPattern)产生的rootDirPath会变成classpath*:然后就会调用到public Resource[] getResources(StringlocationPattern)
throws IOException 这个方法里面,(看代码)
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) 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())); } }
,接下来并不会再调用findPathMatchingResources方法而是调用findAllClassPathResources这个方法,参数会把”classpath*:”这个subString掉,这个时候神奇的地方来了,我们看看findAllClassPathResources这里面有什么神奇的,上代码:
protected Resource[] findAllClassPathResources(String location) throws IOException { String path = location; if (path.startsWith("/")) { path = path.substring(1); } Enumeration<URL> resourceUrls = getClassLoader().getResources(path); Set<Resource> result = new LinkedHashSet<Resource>(16); while (resourceUrls.hasMoreElements()) { URL url = resourceUrls.nextElement(); result.add(convertClassLoaderURL(url)); } return result.toArray(new Resource[result.size()]); }
代码很简单,关键就在Enumeration<URL> resourceUrls = getClassLoader().getResources(path);(注意这个时候path=“”)这一行,这里的classloader决定了我们能获取的资源,如果在tomcat下面.这个classloader是Tomcat的WebappClassloader,这个时候我们查找资源的path是一个空的字符串.这个class返回了两个URl,一个是${webapps}/WEB-INF/classes,还有一个就是${tomcat-home}/lib,奇怪了,居然${webapps}/WEB-INF/lib并不在返回的URL中,有点神奇了,let’s
look!
public Enumeration<URL> getResources(String name) throws IOException { Enumeration[] tmp = new Enumeration[2]; if (parent != null) { tmp[0] = parent.getResources(name); } else { tmp[0] = getBootstrapResources(name); } tmp[1] = findResources(name); return new CompoundEnumeration<>(tmp); }
这个方法实际是WebappClassloader直接继承了ClassLoad过来的,parentClassLoad实际查找的的时tomcat容器的classpath关键在于findResources(name)这个调用,这个调用webappCLassPath有自己的实现,继续look:
@Override public Enumeration<URL> findResources(String name) throws IOException { …..这里省略N多代码 // Looking at the JAR files synchronized (jarFiles) { if (openJARs()) { for (i = 0; i < jarFilesLength; i++) { JarEntry jarEntry = jarFiles[i].getJarEntry(name); if (jarEntry != null) { try { String jarFakeUrl = getURI(jarRealFiles[i]).toString(); jarFakeUrl = "jar:" + jarFakeUrl + "!/" + name; result.add(new URL(jarFakeUrl)); } catch (MalformedURLException e) { // Ignore } } } } } ……这里也省略掉N多代码 return Collections.enumeration(result); }
我们可以看到这个for循环里面获取JarEntry这一句,JarEntry jarEntry = jarFiles[i].getJarEntry(name);这个name其实是一个””字符串,所以返回的jarEntry就是null,这样所有WEB-INF/lib下jar包里面的配置文件全部都不会进入下一步的匹配过程中.
所以这个问题的根源在于classpath*:这种配置,在tomecat下并不适用于文件放在根目录下匹配加载的情况,所以想加载lib/xxx.jar包里面的porpertis文件的话,需要将porperties文件放在一个包路径下,我是放在config这个包路径下解决,可以参考解决一下.
特别说明jetty下没有这个情况,所以问题都是在tomcat下出现的,以后抽时间再看看jetty的classpath是如何不让这种现象出现的.