当前位置:   article > 正文

Spring加载资源文件冲突问题

location pattern must not be null

title: Spring加载资源文件冲突问题 tags:

  • tomcat8
  • spring
  • mac
  • webjars
  • j2cache categories: spring date: 2017-11-14 16:42:08

背景

开发者在使用Spring做Properties文件载入时,这也是spring做placeholder的便捷之处。

classpath:和classpath*:想必也是大部分开发都会写到的文件schema。

百度一下classpath能检索到一大堆区别比如

classpath 和 classpath* 区别

classpath:只会到你指定的class路径中查找文件;

classpath*:不仅包含class路径,还包括jar文件中(class路径)进行查找.

OK 本次说一些不同的

源码

spring在使用时如下会这样配置比如

  1. <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  2. <property name="locations">
  3. <list>
  4. <value>classpath*:httpclient.properties</value>
  5. <value>classpath*:config.properties</value>
  6. <value>classpath*:uploadPath.properties</value>
  7. <value>classpath*:branches.properties</value>
  8. <value>classpath*:wechat.properties</value>
  9. <value>classpath*:email.properties</value>
  10. <value>classpath:j2cache.properties</value>
  11. <value>classpath:sso.properties</value>
  12. <value>classpath*:override.properties</value>
  13. </list>
  14. </property>
  15. </bean>
  16. 复制代码

我们来看一下事实上该方法接受的参数是Resource[] 但是我们传递的是String Spring做了什么魔法呢?

我们简明概要的看一下关键的源码

spring在默认情况下帮我们做了一系列的Convert。我们

此处我们需要关心的是ResourceArray

  1. /**
  2. * Treat the given text as a location pattern and convert it to a Resource array.
  3. */
  4. @Override
  5. public void setAsText(String text) {
  6. String pattern = resolvePath(text).trim();
  7. try {
  8. setValue(this.resourcePatternResolver.getResources(pattern));
  9. }
  10. catch (IOException ex) {
  11. throw new IllegalArgumentException(
  12. "Could not resolve resource location pattern [" + pattern + "]: " + ex.getMessage());
  13. }
  14. }
  15. 复制代码

看到了关键代码resourcePatternResolver.getResources

  1. public Resource[] getResources(String locationPattern) throws IOException {
  2. Assert.notNull(locationPattern, "Location pattern must not be null");
  3. if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
  4. // a class path resource (multiple resources for same name possible)
  5. if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
  6. // a class path resource pattern
  7. return findPathMatchingResources(locationPattern);
  8. }
  9. else {
  10. // all class path resources with the given name
  11. return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
  12. }
  13. }
  14. else {
  15. // Only look for a pattern after a prefix here
  16. // (to not get fooled by a pattern symbol in a strange prefix).
  17. int prefixEnd = locationPattern.indexOf(":") + 1;
  18. if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
  19. // a file pattern
  20. return findPathMatchingResources(locationPattern);
  21. }
  22. else {
  23. // a single resource with the given name
  24. return new Resource[] {getResourceLoader().getResource(locationPattern)};
  25. }
  26. }
  27. }
  28. 复制代码

很明显可以看到对于classpath*做了特殊处理

那么对于classpath:打头的呢 事实上到最后都将走到DefaultResourceLoader中getResource方法

  1. public Resource getResource(String location) {
  2. Assert.notNull(location, "Location must not be null");
  3. if (location.startsWith(CLASSPATH_URL_PREFIX)) {
  4. return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
  5. }
  6. else {
  7. try {
  8. // Try to parse the location as a URL...
  9. URL url = new URL(location);
  10. return new UrlResource(url);
  11. }
  12. catch (MalformedURLException ex) {
  13. // No URL -> resolve as resource path.
  14. return getResourceByPath(location);
  15. }
  16. }
  17. }
  18. 复制代码

对于classpath:打头的资源文件将返回classpathResource 否则返回UrlResource

对于classpath*的文件会优先做一次匹配如下

  1. /**
  2. * Find all class location resources with the given location via the ClassLoader.
  3. * @param location the absolute path within the classpath
  4. * @return the result as Resource array
  5. * @throws IOException in case of I/O errors
  6. * @see java.lang.ClassLoader#getResources
  7. * @see #convertClassLoaderURL
  8. */
  9. protected Resource[] findAllClassPathResources(String location) throws IOException {
  10. String path = location;
  11. if (path.startsWith("/")) {
  12. path = path.substring(1);
  13. }
  14. Enumeration<URL> resourceUrls = getClassLoader().getResources(path);
  15. Set<Resource> result = new LinkedHashSet<Resource>(16);
  16. while (resourceUrls.hasMoreElements()) {
  17. URL url = resourceUrls.nextElement();
  18. result.add(convertClassLoaderURL(url));
  19. }
  20. return result.toArray(new Resource[result.size()]);
  21. }
  22. /**
  23. * Convert the given URL as returned from the ClassLoader into a Resource object.
  24. * <p>The default implementation simply creates a UrlResource instance.
  25. * @param url a URL as returned from the ClassLoader
  26. * @return the corresponding Resource object
  27. * @see java.lang.ClassLoader#getResources
  28. * @see org.springframework.core.io.Resource
  29. */
  30. protected Resource convertClassLoaderURL(URL url) {
  31. return new UrlResource(url);
  32. }
  33. 复制代码

也就是返回UrlResource【从上述代码可以见到集合为LinkedHashSet】

回到classpath上来 一个classpathResource文件如何找到对应的资源呢?

一般来说都是调用classLoader的getReource方法

  1. /**
  2. * Finds the resource with the given name. A resource is some data
  3. * (images, audio, text, etc) that can be accessed by class code in a way
  4. * that is independent of the location of the code.
  5. *
  6. * <p> The name of a resource is a '<tt>/</tt>'-separated path name that
  7. * identifies the resource.
  8. *
  9. * <p> This method will first search the parent class loader for the
  10. * resource; if the parent is <tt>null</tt> the path of the class loader
  11. * built-in to the virtual machine is searched. That failing, this method
  12. * will invoke {@link #findResource(String)} to find the resource. </p>
  13. *
  14. * @param name
  15. * The resource name
  16. *
  17. * @return A <tt>URL</tt> object for reading the resource, or
  18. * <tt>null</tt> if the resource could not be found or the invoker
  19. * doesn't have adequate privileges to get the resource.
  20. *
  21. * @since 1.1
  22. */
  23. public URL getResource(String name) {
  24. URL url;
  25. if (parent != null) {
  26. url = parent.getResource(name);
  27. } else {
  28. url = getBootstrapResource(name);
  29. }
  30. if (url == null) {
  31. url = findResource(name);
  32. }
  33. return url;
  34. }
  35. 复制代码

这是一个典型的委托模型的结构 从最下面的classLoader一直回溯到BootstrapLoader 逐渐寻找

我们可以认为在tomcat容器中一个配置文件通常需要由WebappClassLoader进行加载

而在tomcat7.0.70中代码如下【我们忽略需要向parent获取资源文件的过程】

当不同jar中包含同一个名称的资源文件时那么会做何种操作呢?

我们先看一下如何加载jar的顺序的。

在tomcat7.0.70中tomcat的classloader会作如下操作

  1. NamingEnumeration<NameClassPair> enumeration = null;
  2. try {
  3. enumeration = libDir.list("");
  4. } catch (NamingException e) {
  5. IOException ioe = new IOException(sm.getString(
  6. "webappLoader.namingFailure", libPath));
  7. ioe.initCause(e);
  8. throw ioe;
  9. }
  10. 复制代码

也就是以libDir的list的结果加入对应jar

  1. /**
  2. * Enumerates the names bound in the named context, along with the class
  3. * names of objects bound to them.
  4. *
  5. * @param name the name of the context to list
  6. * @return an enumeration of the names and class names of the bindings in
  7. * this context. Each element of the enumeration is of type NameClassPair.
  8. * @exception NamingException if a naming exception is encountered
  9. */
  10. @Override
  11. public NamingEnumeration<NameClassPair> list(String name)
  12. throws NamingException {
  13. if (!aliases.isEmpty()) {
  14. AliasResult result = findAlias(name);
  15. if (result.dirContext != null) {
  16. return result.dirContext.list(result.aliasName);
  17. }
  18. }
  19. // Next do a standard lookup
  20. List<NamingEntry> bindings = doListBindings(name);
  21. // Check the alternate locations
  22. List<NamingEntry> altBindings = null;
  23. String resourceName = "/META-INF/resources" + name;
  24. for (DirContext altDirContext : altDirContexts) {
  25. if (altDirContext instanceof BaseDirContext) {
  26. altBindings = ((BaseDirContext) altDirContext).doListBindings(resourceName);
  27. }
  28. if (altBindings != null) {
  29. if (bindings == null) {
  30. bindings = altBindings;
  31. } else {
  32. bindings.addAll(altBindings);
  33. }
  34. }
  35. }
  36. if (bindings != null) {
  37. return new NamingContextEnumeration(bindings.iterator());
  38. }
  39. // Really not found
  40. throw new NameNotFoundException(
  41. sm.getString("resources.notFound", name));
  42. }
  43. 复制代码

META-INF/resource多么熟悉的文件夹,这也是为啥出现了WebJar这类的jar的原因 通过META-INF/resource的映射可以在对应地方找到资源文件

这样我们也可以找到WebJar的代码

继续查看如下

  1. /**
  2. * Enumerates the names bound in the named context, along with the
  3. * objects bound to them. The contents of any subcontexts are not
  4. * included.
  5. * <p>
  6. * If a binding is added to or removed from this context, its effect on
  7. * an enumeration previously returned is undefined.
  8. *
  9. * @param name the name of the context to list
  10. * @return an enumeration of the bindings in this context.
  11. * Each element of the enumeration is of type Binding.
  12. * @exception NamingException if a naming exception is encountered
  13. */
  14. @Override
  15. protected List<NamingEntry> doListBindings(String name)
  16. throws NamingException {
  17. File file = file(name);
  18. if (file == null)
  19. return null;
  20. return list(file);
  21. }
  22. /**
  23. * Return a File object representing the specified normalized
  24. * context-relative path if it exists and is readable. Otherwise,
  25. * return <code>null</code>.
  26. *
  27. * @param name Normalized context-relative path (with leading '/')
  28. */
  29. protected File file(String name) {
  30. File file = new File(base, name);
  31. if (file.exists() && file.canRead()) {
  32. if (allowLinking)
  33. return file;
  34. // Check that this file belongs to our root path
  35. String canPath = null;
  36. try {
  37. canPath = file.getCanonicalPath();
  38. } catch (IOException e) {
  39. // Ignore
  40. }
  41. if (canPath == null)
  42. return null;
  43. // Check to see if going outside of the web application root
  44. if (!canPath.startsWith(absoluteBase)) {
  45. return null;
  46. }
  47. // Case sensitivity check - this is now always done
  48. String fileAbsPath = file.getAbsolutePath();
  49. if (fileAbsPath.endsWith("."))
  50. fileAbsPath = fileAbsPath + "/";
  51. String absPath = normalize(fileAbsPath);
  52. canPath = normalize(canPath);
  53. if ((absoluteBase.length() < absPath.length())
  54. && (absoluteBase.length() < canPath.length())) {
  55. absPath = absPath.substring(absoluteBase.length() + 1);
  56. if (absPath.equals(""))
  57. absPath = "/";
  58. canPath = canPath.substring(absoluteBase.length() + 1);
  59. if (canPath.equals(""))
  60. canPath = "/";
  61. if (!canPath.equals(absPath))
  62. return null;
  63. }
  64. } else {
  65. return null;
  66. }
  67. return file;
  68. }
  69. /**
  70. * List the resources which are members of a collection.
  71. *
  72. * @param file Collection
  73. * @return Vector containing NamingEntry objects
  74. */
  75. protected List<NamingEntry> list(File file) {
  76. List<NamingEntry> entries = new ArrayList<NamingEntry>();
  77. if (!file.isDirectory())
  78. return entries;
  79. String[] names = file.list();
  80. if (names==null) {
  81. /* Some IO error occurred such as bad file permissions.
  82. Prevent a NPE with Arrays.sort(names) */
  83. log.warn(sm.getString("fileResources.listingNull",
  84. file.getAbsolutePath()));
  85. return entries;
  86. }
  87. Arrays.sort(names); // Sort alphabetically
  88. NamingEntry entry = null;
  89. for (int i = 0; i < names.length; i++) {
  90. File currentFile = new File(file, names[i]);
  91. Object object = null;
  92. if (currentFile.isDirectory()) {
  93. FileDirContext tempContext = new FileDirContext(env);
  94. tempContext.setDocBase(currentFile.getPath());
  95. tempContext.setAllowLinking(getAllowLinking());
  96. object = tempContext;
  97. } else {
  98. object = new FileResource(currentFile);
  99. }
  100. entry = new NamingEntry(names[i], object, NamingEntry.ENTRY);
  101. entries.add(entry);
  102. }
  103. return entries;
  104. }
  105. 复制代码

一个Arrays.sort(names); 可以看出下面返回的结果将是按照字典序进行排列的。

那么对应的在tomcat中调用classLoader如下

  1. /**
  2. * Find the specified resource in our local repository, and return a
  3. * <code>URL</code> referring to it, or <code>null</code> if this resource
  4. * cannot be found.
  5. *
  6. * @param name Name of the resource to be found
  7. */
  8. @Override
  9. public URL findResource(final String name) {
  10. if (log.isDebugEnabled())
  11. log.debug(" findResource(" + name + ")");
  12. URL url = null;
  13. String path = nameToPath(name);
  14. if (hasExternalRepositories && searchExternalFirst)
  15. url = super.findResource(name);
  16. if (url == null) {
  17. ResourceEntry entry = resourceEntries.get(path);
  18. if (entry == null) {
  19. if (securityManager != null) {
  20. PrivilegedAction<ResourceEntry> dp =
  21. new PrivilegedFindResourceByName(name, path, false);
  22. entry = AccessController.doPrivileged(dp);
  23. } else {
  24. entry = findResourceInternal(name, path, false);
  25. }
  26. }
  27. if (entry != null) {
  28. url = entry.source;
  29. }
  30. }
  31. if ((url == null) && hasExternalRepositories && !searchExternalFirst)
  32. url = super.findResource(name);
  33. if (log.isDebugEnabled()) {
  34. if (url != null)
  35. log.debug(" --> Returning '" + url.toString() + "'");
  36. else
  37. log.debug(" --> Resource not found, returning null");
  38. }
  39. return (url);
  40. }
  41. 复制代码

而在对应的findResourceInternal方法

  1. /**
  2. * Find specified resource in local repositories.
  3. *
  4. * @return the loaded resource, or null if the resource isn't found
  5. */
  6. protected ResourceEntry findResourceInternal(final String name, final String path,
  7. final boolean manifestRequired) {
  8. if (!started) {
  9. log.info(sm.getString("webappClassLoader.stopped", name));
  10. return null;
  11. }
  12. if ((name == null) || (path == null))
  13. return null;
  14. JarEntry jarEntry = null;
  15. // Need to skip the leading / to find resoucres in JARs
  16. String jarEntryPath = path.substring(1);
  17. ResourceEntry entry = resourceEntries.get(path);
  18. if (entry != null) {
  19. if (manifestRequired && entry.manifest == MANIFEST_UNKNOWN) {
  20. // This resource was added to the cache when a request was made
  21. // for the resource that did not need the manifest. Now the
  22. // manifest is required, the cache entry needs to be updated.
  23. synchronized (jarFiles) {
  24. if (openJARs()) {
  25. for (int i = 0; i < jarFiles.length; i++) {
  26. jarEntry = jarFiles[i].getJarEntry(jarEntryPath);
  27. if (jarEntry != null) {
  28. try {
  29. entry.manifest = jarFiles[i].getManifest();
  30. } catch (IOException ioe) {
  31. // Ignore
  32. }
  33. break;
  34. }
  35. }
  36. }
  37. }
  38. }
  39. return entry;
  40. }
  41. int contentLength = -1;
  42. InputStream binaryStream = null;
  43. boolean isClassResource = path.endsWith(CLASS_FILE_SUFFIX);
  44. boolean isCacheable = isClassResource;
  45. if (!isCacheable) {
  46. isCacheable = path.startsWith(SERVICES_PREFIX);
  47. }
  48. int jarFilesLength = jarFiles.length;
  49. int repositoriesLength = repositories.length;
  50. int i;
  51. Resource resource = null;
  52. boolean fileNeedConvert = false;
  53. for (i = 0; (entry == null) && (i < repositoriesLength); i++) {
  54. try {
  55. String fullPath = repositories[i] + path;
  56. Object lookupResult = resources.lookup(fullPath);
  57. if (lookupResult instanceof Resource) {
  58. resource = (Resource) lookupResult;
  59. }
  60. // Note : Not getting an exception here means the resource was
  61. // found
  62. ResourceAttributes attributes =
  63. (ResourceAttributes) resources.getAttributes(fullPath);
  64. contentLength = (int) attributes.getContentLength();
  65. String canonicalPath = attributes.getCanonicalPath();
  66. if (canonicalPath != null) {
  67. // we create the ResourceEntry based on the information returned
  68. // by the DirContext rather than just using the path to the
  69. // repository. This allows to have smart DirContext implementations
  70. // that "virtualize" the docbase (e.g. Eclipse WTP)
  71. entry = findResourceInternal(new File(canonicalPath), "");
  72. } else {
  73. // probably a resource not in the filesystem (e.g. in a
  74. // packaged war)
  75. entry = findResourceInternal(files[i], path);
  76. }
  77. entry.lastModified = attributes.getLastModified();
  78. if (resource != null) {
  79. try {
  80. binaryStream = resource.streamContent();
  81. } catch (IOException e) {
  82. return null;
  83. }
  84. if (needConvert) {
  85. if (path.endsWith(".properties")) {
  86. fileNeedConvert = true;
  87. }
  88. }
  89. // Register the full path for modification checking
  90. // Note: Only syncing on a 'constant' object is needed
  91. synchronized (allPermission) {
  92. int j;
  93. long[] result2 =
  94. new long[lastModifiedDates.length + 1];
  95. for (j = 0; j < lastModifiedDates.length; j++) {
  96. result2[j] = lastModifiedDates[j];
  97. }
  98. result2[lastModifiedDates.length] = entry.lastModified;
  99. lastModifiedDates = result2;
  100. String[] result = new String[paths.length + 1];
  101. for (j = 0; j < paths.length; j++) {
  102. result[j] = paths[j];
  103. }
  104. result[paths.length] = fullPath;
  105. paths = result;
  106. }
  107. }
  108. } catch (NamingException e) {
  109. // Ignore
  110. }
  111. }
  112. if ((entry == null) && (notFoundResources.containsKey(name)))
  113. return null;
  114. synchronized (jarFiles) {
  115. try {
  116. if (!openJARs()) {
  117. return null;
  118. }
  119. for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
  120. jarEntry = jarFiles[i].getJarEntry(jarEntryPath);
  121. if (jarEntry != null) {
  122. entry = new ResourceEntry();
  123. try {
  124. entry.codeBase = getURI(jarRealFiles[i]);
  125. entry.source =
  126. UriUtil.buildJarUrl(entry.codeBase.toString(), jarEntryPath);
  127. entry.lastModified = jarRealFiles[i].lastModified();
  128. } catch (MalformedURLException e) {
  129. return null;
  130. }
  131. contentLength = (int) jarEntry.getSize();
  132. try {
  133. if (manifestRequired) {
  134. entry.manifest = jarFiles[i].getManifest();
  135. } else {
  136. entry.manifest = MANIFEST_UNKNOWN;
  137. }
  138. binaryStream = jarFiles[i].getInputStream(jarEntry);
  139. } catch (IOException e) {
  140. return null;
  141. }
  142. // Extract resources contained in JAR to the workdir
  143. if (antiJARLocking && !(path.endsWith(CLASS_FILE_SUFFIX))) {
  144. byte[] buf = new byte[1024];
  145. File resourceFile = new File(loaderDir, jarEntry.getName());
  146. if (!resourceFile.exists()) {
  147. Enumeration<JarEntry> entries = jarFiles[i].entries();
  148. while (entries.hasMoreElements()) {
  149. JarEntry jarEntry2 = entries.nextElement();
  150. if (!(jarEntry2.isDirectory()) &&
  151. (!jarEntry2.getName().endsWith(CLASS_FILE_SUFFIX))) {
  152. resourceFile = new File(loaderDir, jarEntry2.getName());
  153. try {
  154. if (!resourceFile.getCanonicalPath().startsWith(
  155. canonicalLoaderDir)) {
  156. throw new IllegalArgumentException(
  157. sm.getString("webappClassLoader.illegalJarPath",
  158. jarEntry2.getName()));
  159. }
  160. } catch (IOException ioe) {
  161. throw new IllegalArgumentException(
  162. sm.getString("webappClassLoader.validationErrorJarPath",
  163. jarEntry2.getName()), ioe);
  164. }
  165. File parentFile = resourceFile.getParentFile();
  166. if (!parentFile.mkdirs() && !parentFile.exists()) {
  167. // Ignore the error (like the IOExceptions below)
  168. }
  169. FileOutputStream os = null;
  170. InputStream is = null;
  171. try {
  172. is = jarFiles[i].getInputStream(jarEntry2);
  173. os = new FileOutputStream(resourceFile);
  174. while (true) {
  175. int n = is.read(buf);
  176. if (n <= 0) {
  177. break;
  178. }
  179. os.write(buf, 0, n);
  180. }
  181. resourceFile.setLastModified(jarEntry2.getTime());
  182. } catch (IOException e) {
  183. // Ignore
  184. } finally {
  185. try {
  186. if (is != null) {
  187. is.close();
  188. }
  189. } catch (IOException e) {
  190. // Ignore
  191. }
  192. try {
  193. if (os != null) {
  194. os.close();
  195. }
  196. } catch (IOException e) {
  197. // Ignore
  198. }
  199. }
  200. }
  201. }
  202. }
  203. }
  204. }
  205. }
  206. if (entry == null) {
  207. synchronized (notFoundResources) {
  208. notFoundResources.put(name, name);
  209. }
  210. return null;
  211. }
  212. /* Only cache the binary content if there is some content
  213. * available one of the following is true:
  214. * a) It is a class file since the binary content is only cached
  215. * until the class has been loaded
  216. * or
  217. * b) The file needs conversion to address encoding issues (see
  218. * below)
  219. * or
  220. * c) The resource is a service provider configuration file located
  221. * under META=INF/services
  222. *
  223. * In all other cases do not cache the content to prevent
  224. * excessive memory usage if large resources are present (see
  225. * https://bz.apache.org/bugzilla/show_bug.cgi?id=53081).
  226. */
  227. if (binaryStream != null && (isCacheable || fileNeedConvert)) {
  228. byte[] binaryContent = new byte[contentLength];
  229. int pos = 0;
  230. try {
  231. while (true) {
  232. int n = binaryStream.read(binaryContent, pos,
  233. binaryContent.length - pos);
  234. if (n <= 0)
  235. break;
  236. pos += n;
  237. }
  238. } catch (IOException e) {
  239. log.error(sm.getString("webappClassLoader.readError", name), e);
  240. return null;
  241. }
  242. if (fileNeedConvert) {
  243. // Workaround for certain files on platforms that use
  244. // EBCDIC encoding, when they are read through FileInputStream.
  245. // See commit message of rev.303915 for details
  246. // http://svn.apache.org/viewvc?view=revision&revision=303915
  247. String str = new String(binaryContent,0,pos);
  248. try {
  249. binaryContent = str.getBytes(CHARSET_UTF8);
  250. } catch (Exception e) {
  251. return null;
  252. }
  253. }
  254. entry.binaryContent = binaryContent;
  255. // The certificates are only available after the JarEntry
  256. // associated input stream has been fully read
  257. if (jarEntry != null) {
  258. entry.certificates = jarEntry.getCertificates();
  259. }
  260. }
  261. } finally {
  262. if (binaryStream != null) {
  263. try {
  264. binaryStream.close();
  265. } catch (IOException e) { /* Ignore */}
  266. }
  267. }
  268. }
  269. if (isClassResource && entry.binaryContent != null &&
  270. this.transformers.size() > 0) {
  271. // If the resource is a class just being loaded, decorate it
  272. // with any attached transformers
  273. String className = name.endsWith(CLASS_FILE_SUFFIX) ?
  274. name.substring(0, name.length() - CLASS_FILE_SUFFIX.length()) : name;
  275. String internalName = className.replace(".", "/");
  276. for (ClassFileTransformer transformer : this.transformers) {
  277. try {
  278. byte[] transformed = transformer.transform(
  279. this, internalName, null, null, entry.binaryContent
  280. );
  281. if (transformed != null) {
  282. entry.binaryContent = transformed;
  283. }
  284. } catch (IllegalClassFormatException e) {
  285. log.error(sm.getString("webappClassLoader.transformError", name), e);
  286. return null;
  287. }
  288. }
  289. }
  290. // Add the entry in the local resource repository
  291. synchronized (resourceEntries) {
  292. // Ensures that all the threads which may be in a race to load
  293. // a particular class all end up with the same ResourceEntry
  294. // instance
  295. ResourceEntry entry2 = resourceEntries.get(path);
  296. if (entry2 == null) {
  297. resourceEntries.put(path, entry);
  298. } else {
  299. entry = entry2;
  300. }
  301. }
  302. return entry;
  303. }
  304. 复制代码

很明显其实按照之前的file的顺序来做的遍历 返回第一个即可。

在mac新版本系统【macos high-sierra】和tomcat8的组合中发生了资源文件加载覆盖的问题【目前tomcat7无问题】 初步怀疑tomcat8中classloader更换了实现

同时mac新版本在升级时也更换了实现导致出现聪明的资源文件时加载顺序不一致 出现部分资源定义无法加载的错误

tomcat8中未作排序

  1. @Override
  2. public String[] list(String path) {
  3. checkPath(path);
  4. String webAppMount = getWebAppMount();
  5. if (path.startsWith(webAppMount)) {
  6. File f = file(path.substring(webAppMount.length()), true);
  7. if (f == null) {
  8. return EMPTY_STRING_ARRAY;
  9. }
  10. String[] result = f.list();
  11. if (result == null) {
  12. return EMPTY_STRING_ARRAY;
  13. } else {
  14. return result;
  15. }
  16. } else {
  17. if (!path.endsWith("/")) {
  18. path = path + "/";
  19. }
  20. if (webAppMount.startsWith(path)) {
  21. int i = webAppMount.indexOf('/', path.length());
  22. if (i == -1) {
  23. return new String[] {webAppMount.substring(path.length())};
  24. } else {
  25. return new String[] {
  26. webAppMount.substring(path.length(), i)};
  27. }
  28. }
  29. return EMPTY_STRING_ARRAY;
  30. }
  31. }
  32. 复制代码

tomcat8直接调用f.list 并未作排序

  1. /**
  2. * Returns an array of strings naming the files and directories in the
  3. * directory denoted by this abstract pathname.
  4. *
  5. * <p> If this abstract pathname does not denote a directory, then this
  6. * method returns {@code null}. Otherwise an array of strings is
  7. * returned, one for each file or directory in the directory. Names
  8. * denoting the directory itself and the directory's parent directory are
  9. * not included in the result. Each string is a file name rather than a
  10. * complete path.
  11. *
  12. * <p> There is no guarantee that the name strings in the resulting array
  13. * will appear in any specific order; they are not, in particular,
  14. * guaranteed to appear in alphabetical order.
  15. *
  16. * <p> Note that the {@link java.nio.file.Files} class defines the {@link
  17. * java.nio.file.Files#newDirectoryStream(Path) newDirectoryStream} method to
  18. * open a directory and iterate over the names of the files in the directory.
  19. * This may use less resources when working with very large directories, and
  20. * may be more responsive when working with remote directories.
  21. *
  22. * @return An array of strings naming the files and directories in the
  23. * directory denoted by this abstract pathname. The array will be
  24. * empty if the directory is empty. Returns {@code null} if
  25. * this abstract pathname does not denote a directory, or if an
  26. * I/O error occurs.
  27. *
  28. * @throws SecurityException
  29. * If a security manager exists and its {@link
  30. * SecurityManager#checkRead(String)} method denies read access to
  31. * the directory
  32. */
  33. public String[] list() {
  34. SecurityManager security = System.getSecurityManager();
  35. if (security != null) {
  36. security.checkRead(path);
  37. }
  38. return fs.list(this);
  39. }
  40. 复制代码

而list方法写的比较清楚这个顺序不受保证【通常意义是字典序但是不保证】

  1. There is no guarantee that the name strings in the resulting array
  2. will appear in any specific order; they are not, in particular,
  3. guaranteed to appear in alphabetical order.
  4. 复制代码

因此部分小伙伴升级mac新系统使用tomcat8时出现了加载顺序不一致

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/66391
推荐阅读
相关标签
  

闽ICP备14008679号