关于tomcat下spring无法加载依赖jar中properties文件的原因分析

我们经常把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是如何不让这种现象出现的.

时间: 2024-10-07 10:02:57

关于tomcat下spring无法加载依赖jar中properties文件的原因分析的相关文章

在tomcat下使用jni加载本地库的问题总结——处理UnsatisfiedLinkError错误

最近想使用ICTCLAS分词系统,需要在myeclipse中加载dll(本地库),查阅资料使用jni,ICTCLAS官方也提供了很全面的加载方法,虽然在run as java application下运行没有问题,但是在tomcat下报UnsatisfiedLinkError的错误,错误来自System.loadLibrary("ICTCLAS50"),是jni加载本地库的语句. 之前其实为了省事将dll放在根目录下,显然也不符合tomcat的要求,重新移动到webapps下,想将IC

Android下将图片加载到内存中

Android的系统的标准默认每个应用程序分配的内存是16M.所以来说是非常宝贵的,在创建应用的时候要尽可能的去节省内存,但是在加载一些大的文件的时候,比如图片是相当耗内存的,一个1.3M的图片,分辨率是2560X1920(宽X高)图片当加载到手机内存的时候就会请求19M的一块内存,这是远远超出了系统自带的内存空间,这时候应用程序就会挂掉,所以我们要进行图片的缩放处理,下面我就来带大家创建一个用来图片缩放的应用: 应用效果图如下: 核心代码的实现: package com.examp.loadp

Java ClassLoader基础及加载不同依赖 Jar 中的公共类

转载自:最新内容及最清晰格式请见 http://www.trinea.cn/android/java-loader-common-class/ 本文主要介绍 ClassLoader 的基础知识,ClassLoader 如何动态加载 Jar,ClassLoader 隔离问题及如何加载不同 Jar 中的公共类. 本文工程开源地址见:Java Dynamic Load [email protected],Clone 以后直接以 Java Application去运行 java-dynamic-load

XML文档部署到Tomcat服务器上总是加载出错

config.xnl 起初文档路径是在src/Dao/config.xml 在Dao目录下BaseDao类中,解析config.xml文件路径 path="/Dao/config.xml",似乎正常,可运行Tomcat总是报错,系统找不到指定文件路径 我又将其放在src/WEB-INFO等文件根目录下,依然显示系统找不到指定文件路径.百度一番,各种建议均无效后,花两小时调试后,总结两种方法 第一种:通过url可以直接请求到服务器资源,那么就将config.xml文件放在Web根目录下,

spring项目加载非常慢

请检查spring是否运行在debug模式下,是跳转到2 否则跳转到3 查看spring在run模式下是否运行依旧缓慢 是跳转到3,否则跳转到4 请检验是否spring bean加载了多次(quartz加载很有可能导致部分bean被是实例两次) 是跳转到 http://blog.csdn.net/chaijunkun/article/details/6925889 否则跳转到 http://jinnianshilongnian.iteye.com/blog/1883013 按照步骤检验 请将代码

Spring如何加载XSD文件(org.xml.sax.SAXParseException: Failed to read schema document错误的解决方法)

本文原文连接: http://blog.csdn.net/bluishglc/article/details/7596118 ,转载请注明出处! 有时候你会发现过去一直启动正常的系统,某天启动时会报出形如下面的错误: [plain] view plaincopy org.xml.sax.SAXParseException: schema_reference.4: Failed to read schema document 'http://www.springframework.org/sche

spring容器加载完成执行某个方法

今天遇到一个问题,需要在项目中加入一个定时器,需要在项目启动后就执行,所以就在网上找spring容器初始化之后就执行的方法有什么: 查了很多资料,总共找到了两个符合条件的方法,都是用了一下之后,结合网上的信息说说我个人的理解: 1.写一个类,实现BeanPostProcessor,这个接口有两个方法: (1)postProcessBeforeInitialization方法,在spring中定义的bean初始化前调用这个方法: (2)postProcessAfterInitialization方

重温.NET下Assembly的加载过程

原文:重温.NET下Assembly的加载过程 最近在工作中牵涉到了.NET下的一个古老的问题:Assembly的加载过程.虽然网上有很多文章介绍这部分内容,很多文章也是很久以前就已经出现了,但阅读之后发现,并没能解决我的问题,有些点写的不是特别详细,让人看完之后感觉还是云里雾里.最后,我决定重新复习一下这个经典而古老的问题,并将所得总结于此,然后会有一个实例对这个问题进行演示,希望能够帮助到大家. .NET下Assembly的加载过程 .NET下Assembly的加载,最主要的一步就是确定As

Spring Boot加载配置文件

问题1:Spring如何加载配置,配置文件位置? 1.默认位置: Spring Boot默认的配置文件名称为application.properties,SpringApplication将从以下位置加载application.properties文件,并把它们添加到Spring Environment中: 当前目录下的/config子目录, 当前目录. 一个classpath下的/config包 classpath根路径(root) 这个列表是按优先级排序的(列表中位置高的将覆盖位置低的).并