URL Java JDK 8 尝试创建 URL 时出现异常 URL

MalformedURLException in Java JDK 8 when trying to create an URL

当我尝试使用嵌套的 jars 间接寻址创建 URL 时出现异常。

例如,我有以下嵌套 jar URL:

jar:jar:file:/D://samples/File.zip!/71812_file!/myFile.properties

这是一个嵌套的 Jar URL,有两个间接。

当我尝试执行这个简单的行时:

URL url = new URL(spec);

我有以下异常:

java.net.MalformedURLException: Nested JAR URLs are not supported

它现在不再工作了 (JDK8_271),但同样的代码在旧的 JDK8 版本中工作(例如我记忆中的 92)。

JDK 中创建此异常的“违规”代码(之前 JDK 代码中不存在)似乎在 sun.net.www.protocol.jar.Handler class(源代码here):

   private String checkNestedProtocol(String spec) {
     if (spec.regionMatches(true, 0, "jar:", 0, 4)) {
       return "Nested JAR URLs are not supported";
     } else {
       return null;
     }
  }

我现在可以做些什么来解决这个问题,更新的 JDK 版本是否可行?

好的,所以这里真正的问题是标准 Java class 加载器不支持也从不支持嵌套的 JAR 文件。对此有一个长期存在的功能请求;参见 JDK-4735639 : URLClassLoader does not work with jar: URLs

这同样适用于使用 URL.openConnection().

打开嵌套 JAR

发生的事情是 JDK 7(!) 及更高版本已更改(2019 年 6 月),因此当您尝试解析 URL.

如果您无法将嵌套的 JAR 提取到文件中,那么解决方案是查找(或编写)并为 [=12] 注册第 3 方 URL 协议处理程序 class =] 理解嵌套的 JAR 文件。

一种可能是 JBoss njar: protocol handler.


It does not work anymore now (JDK8_271), but the same exact code worked in older JDK8 versions (for example 92 in my memory).

解析 URL 可能有用。但是 java.net.URL class 中的注释暗示 URL.openConnection 不适用于嵌套的 JAR URLs.

if ("jar".equalsIgnoreCase(protocol)) {
    if (handler instanceof sun.net.www.protocol.jar.Handler) {
        // URL.openConnection() would throw a confusing exception
        // so generate a better exception here instead.
        String s = ((sun.net.www.protocol.jar.Handler) handler).checkNestedProtocol(file);
        if (s != null) {
            throw new MalformedURLException(s);
        }
    }
}

嗯,我明白了!事实上,我已经创建了自己的 URLConnection,因为我没有使用 url.openConnection 代码,正是出于您解释的原因(但之前我在尝试创建 URL,因此我目前的问题)。我有以下代码:

NestableURLConnection conn = new NestableURLConnection(url); 

NestableURLConnection 是我创建的自定义 URL自定义连接。我会尝试你的解决方案,这似乎很合适!

我使用的自定义 URLConnection 与协议处理程序完美配合:

public class NestableURLConnection extends URLConnection {
  protected String urlPath;
  protected boolean firstEntry = false;
  public NestableURLConnection(URL url) {
     super(url);
     urlPath = url.toString();
  }

  public NestableURLConnection() {
     super(null);
  }

  public void setURL(URL url) {
     this.url = url;
     urlPath = url.toString();
  }

  public NestableURLConnection(URL url, boolean firstEntry) {
     this(url);
     this.firstEntry = firstEntry;
  }

  @Override
  public void connect() throws IOException {
     connected = true;
  }

  private String getNestedURL() throws IOException {
     int sep = urlPath.indexOf("!/");
     int start = urlPath.indexOf(':') + 1;

     for (int i = start, end = urlPath.indexOf("/") - 1; (i = urlPath.indexOf(":", i)) < end;) {
        int cursep = urlPath.indexOf("!/", sep + 2);
        if (cursep < 0) {
           break;
        }
        sep = cursep;
        ++i;
     }

     return urlPath.substring(start, sep);
  }

  @Override
  public InputStream getInputStream() throws IOException {
     int start = urlPath.indexOf(':') + 1;
     if (start > urlPath.length() || urlPath.charAt(start) == '/') {
        return url.openStream();
     }

     if (FileUtilities.useFileOrHTTPProtocol(urlPath)) {
        return url.openStream();
     }

     int sep = urlPath.indexOf("!/");
     if (sep < 0) {
        throw new MalformedURLException("Missing separator " + urlPath);
     }

     String nestedURL = getNestedURL();

     sep = urlPath.indexOf(nestedURL) + nestedURL.length();
     int nextSep = urlPath.indexOf("!/", sep + 2);

     // Use the default Java openStream() for a file scheme.
     InputStream inputStream;
     ZipEntry inputZipEntry;
     boolean isKnownProtocol = FileUtilities.useFileOrHTTPProtocol(nestedURL);
     if (!isKnownProtocol || (firstEntry)) {
        if (firstEntry) {
           nestedURL = urlPath;
        } else {
           if (!nestedURL.contains("!/")) {
              nestedURL = "file:" + nestedURL.substring(4);
           }
        }
        inputStream = createInputStream(nestedURL);
     } else {
        String entry = nextSep < 0 ? urlPath.substring(sep + 2) : urlPath.substring(sep + 2, nextSep);

        sep = nextSep;
        nextSep = urlPath.indexOf("!/", sep + 2);

        // Go directly to the right entry in the zip file
        final ZipFile zipFile = new  ZipFile(FileUtilities.replaceEscapedSequences(nestedURL.substring(5)));
        inputZipEntry = zipFile.getEntry(entry);
        InputStream zipEntryInputStream = inputZipEntry == null ? null :   zipFile.getInputStream(inputZipEntry);
        if (zipEntryInputStream == null) {
           zipFile.close();
           // return null instead of throwing an Exception if the URL does not exist
           return null;
        }
        inputStream = new FilterInputStream(zipEntryInputStream) {
           @Override
           public void close() throws IOException {
              super.close();
             zipFile.close();
           }
        };
     }

     // Loop over the archive paths.
     LOOP:
     while (sep > 0) {
        // The entry name to be matched.
        String entry = nextSep < 0 ? urlPath.substring(sep + 2) : urlPath.substring(sep + 2, nextSep);

        // Wrap the input stream as a zip stream to scan it's contents for a match.
        // Don't use try-with-resources here because we only want to close it if there is an internal IOException
        ZipInputStream zipInputStream = new ZipInputStream(inputStream);
        while (zipInputStream.available() >= 0) {
           ZipEntry zipEntry = zipInputStream.getNextEntry();
           if (zipEntry == null) {
              break;
           } else if (firstEntry) {
              inputZipEntry = zipEntry;
              inputStream = zipInputStream;
              sep = -1;
              continue LOOP;
           } else if (entry.equals(zipEntry.getName())) {
              inputZipEntry = zipEntry;
              inputStream = zipInputStream;

              // Skip to the next archive path and continue the loop.
              sep = nextSep;
              nextSep = urlPath.indexOf("!/", sep + 2);
              continue LOOP;
           }
        }
        throw new IOException("Archive entry not found " + urlPath);
     }

     return inputStream;
  }

  private InputStream createInputStream(String nestedURL) throws IOException {
     return new URL(nestedURL).openStream();
  }

  @Override
  public OutputStream getOutputStream() throws IOException {
     return null;
  }

我已将其添加到我的图书馆中:https://sourceforge.net/projects/mdiutilities/