Spring提供了一个工具类可以加载classpath下的文件,一般情况下无任何问题,但是当它作为公共的jar包中的工具来加载jar包中的文件时则报出找不到文件的错误.
点开看了一下这个工具类ResouceUtils.getFile()方法的源码:
public static File getFile(String resourceLocation) throws FileNotFoundException { Assert.notNull(resourceLocation, "Resource location must not be null"); if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) { String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length()); String description = "class path resource [" + path + "]"; ClassLoader cl = ClassUtils.getDefaultClassLoader(); URL url = (cl != null ? cl.getResource(path) : ClassLoader.getSystemResource(path)); if (url == null) { throw new FileNotFoundException(description + " cannot be resolved to absolute file path because it does not exist"); } return getFile(url, description); } try { // try URL return getFile(new URL(resourceLocation)); } catch (MalformedURLException ex) { // no URL -> treat as file path return new File(resourceLocation); } }
看了一下代码结构简单逻辑清晰,可能有问题的也就是上图标红的2处.这里我第一印象是类加载器加载资源的时候没加载到.Debug了一下cl.getResource(path)用的类加载器是
WebAppClassLoader,想看一下内部实现,但是到这里就跟不进去了,然后百度了一下发现这个是Jetty实现的自己的ClassLoader,截取部分关键的加载源码:
public void addJars(Resource lib) { if (lib.exists() && lib.isDirectory()) { String[] files=lib.list(); for (int f=0;files!=null && f<files.length;f++) { try { Resource fn=lib.addPath(files[f]); String fnlc=fn.getName().toLowerCase(); if (fnlc.endsWith(".jar") || fnlc.endsWith(".zip")) { String jar=fn.toString(); jar=StringUtil.replace(jar, ",", "%2C"); jar=StringUtil.replace(jar, ";", "%3B"); addClassPath(jar); } } catch (Exception ex) { Log.warn(Log.EXCEPTION,ex); } } } }
上面这块是把jar和zip的path加到类加载器路径中的部分源码.继续debug得到
URL url = (cl != null ? cl.getResource(path) : ClassLoader.getSystemResource(path));
上面的url结果:
实际上取到了要加载的文件路径,由于在jar包中,协议字段被标识为jar.到这里看来并非类加载器导致的加载文件失败.那只好继续debug往下看getFile()的源码:
public static File getFile(URL resourceUrl, String description) throws FileNotFoundException { Assert.notNull(resourceUrl, "Resource URL must not be null"); if (!URL_PROTOCOL_FILE.equals(resourceUrl.getProtocol())) { //URL_PROTOCOL_FILE="file" throw new FileNotFoundException( description + " cannot be resolved to absolute file path " + "because it does not reside in the file system: " + resourceUrl); } try { return new File(toURI(resourceUrl).getSchemeSpecificPart()); } catch (URISyntaxException ex) { // Fallback for URLs that are not valid URIs (should hardly ever happen). return new File(resourceUrl.getFile()); } }
看到这里有点无语了,上面红色字体的部分实际判断资源路径的协议是否为file,由于在jar包中,协议是jar,故到此处直接抛出文件未找到的异常,
顿时觉得自己好傻,debug这么久,又理了一遍类加载的过程,其实问题是很简单的一个问题,ResouceUtils.getFile()是专门用来加载非压缩和Jar包文件类型的资源,所以它根本不会
去尝试加载Jar中的文件,要想加载Jar中的文件,只要用可以读取jar中文件的方式加载即可,比如 xx.class.getClassLoader().getResouceAsStream()这种以流的形式读取文件的方式.