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/
当我尝试使用嵌套的 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()
.
发生的事情是 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/