【发布时间】:2014-03-27 13:36:07
【问题描述】:
我们知道 jar 软链接器的问题 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6967414 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6805618
并使用了以下类(在网络上找到并修改以处理 JAVA 7。)
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A utility class for working around the java webstart jar signing/security bug
* <p/>
* see http://bugs.sun.com/view_bug.do?bug_id=6967414 and http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6805618
*
* @author Scott Chan
*/
public class JarSignersHardLinker {
private static final String JRE_1_DOT = "1.";
private static final String DOT_ZERO_UNDERSCORE = ".0_";
/**
* the 1.6.0 update where this problem first occurred
*/
private static final int PROBLEM_JRE_UPDATE = 19;
private static final int PROBLEM_JRE_MAJOR_VERSION = 6;
public static final List sm_hardRefs = new ArrayList();
protected static void makeHardSignersRef(JarFile jar) throws java.io.IOException {
if (jar != null && jar.getClass().getName().equals("com.sun.deploy.cache.CachedJarFile")) {
Logger.info("Making hard refs for: " + jar.getName());
//lets attempt to get at the each of the soft links.
//first need to call the relevant no-arg method to ensure that the soft ref is populated
//then we access the private member, resolve the softlink and throw it in a static list.
callNoArgMethod("getSigners", jar);
makeHardLink("signersRef", jar);
callNoArgMethod("getSignerMap", jar);
makeHardLink("signerMapRef", jar);
// callNoArgMethod("getCodeSources", jar);
// makeHardLink("codeSourcesRef", jar);
callNoArgMethod("getCodeSourceCache", jar);
makeHardLink("codeSourceCacheRef", jar);
}
}
/**
* if the specified field for the given instance is a Softreference
* That soft reference is resolved and the returned ref is stored in a static list,
* making it a hard link that should never be garbage collected
*
* @param fieldName
* @param instance
*/
private static void makeHardLink(String fieldName, Object instance) {
//System.out.println("attempting hard ref to " + instance.getClass().getName() + "." + fieldName);
try {
Field signersRef = instance.getClass().getDeclaredField(fieldName);
signersRef.setAccessible(true);
Object o = signersRef.get(instance);
if (o instanceof SoftReference) {
SoftReference r = (SoftReference) o;
Object o2 = r.get();
sm_hardRefs.add(o2);
} else {
Logger.warn(fieldName + ": is not an instance of soft reference");
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
return;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* Call the given no-arg method on the given instance
*
* @param methodName
* @param instance
*/
private static void callNoArgMethod(String methodName, Object instance) {
// System.out.println("calling noarg method hard ref to " + instance.getClass().getName() + "." + methodName + "()");
try {
Method m = instance.getClass().getDeclaredMethod(methodName);
m.setAccessible(true);
m.invoke(instance);
} catch (SecurityException e1) {
e1.printStackTrace();
} catch (NoSuchMethodException e1) {
e1.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* is the preloader enabled. ie: will the preloader run in the current environment
*
* @return
*/
public static boolean isHardLinkerEnabled() {
boolean isHardLinkerDisabled = false; //change this to use whatever mechanism you use to enable or disable the preloader
return !isHardLinkerDisabled && isRunningOnJre1_6_0_19OrHigher() && isRunningOnWebstart();
}
/**
* is the application currently running on webstart
* <p/>
* detect the presence of a JNLPclassloader
*
* @return
*/
public static boolean isRunningOnWebstart() {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
while (cl != null) {
if (cl.getClass().getName().equals("com.sun.jnlp.JNLPClassLoader")) {
return true;
}
cl = cl.getParent();
}
return false;
}
/**
* Is the JRE 1.6.0_19 or higher?
* TBFI-5349: Java has a bug, sometimes Jars get garbage collected. To resolve this we are making
* hard references to the Jars.
*
* This method checks for java version. The bug is in 1.6.0_19 and above release hence checking for version 19
* @return
*/
public static boolean isRunningOnJre1_6_0_19OrHigher() {
String javaVersion = System.getProperty("java.version");
String updateStr = null;
String javaMajorVersionStr = null;
boolean isHardReferenceRequired = false;
// Problem persist in JAVA 7 and probable in JAVA 8 as well. So changing the patter.
Pattern pattern = Pattern.compile(JRE_1_DOT + "([6-9]+)" + DOT_ZERO_UNDERSCORE + "([0-9]+)(.*)");
Matcher matcher = pattern.matcher(javaVersion);
while (matcher.find()) {
javaMajorVersionStr = matcher.group(1);
updateStr = matcher.group(2);
break;
}
Logger.info("Java version: " + javaMajorVersionStr + " update string: " + updateStr);
try {
if (javaMajorVersionStr != null) {
int java_version = Integer.parseInt(javaMajorVersionStr);
if (java_version > PROBLEM_JRE_MAJOR_VERSION) {
isHardReferenceRequired = true;
} else if (java_version == PROBLEM_JRE_MAJOR_VERSION && Integer.parseInt(updateStr) >= PROBLEM_JRE_UPDATE) {
isHardReferenceRequired = true;
}
}
return isHardReferenceRequired;
} catch (NumberFormatException e) {
//then unable to determine java Major version or update level
e.printStackTrace();
return isHardReferenceRequired;
}
}
/**
* get all the JarFile objects for all of the jars in the classpath
*
* @return
*/
public static Set<JarFile> getAllJarsFilesInClassPath() {
Set<JarFile> jars = new LinkedHashSet<JarFile>();
for (URL url : getAllJarUrls()) {
try {
jars.add(getJarFile(url));
} catch (IOException e) {
Logger.error("unable to retrieve jar at URL: " + url);
}
}
return jars;
}
/**
* Returns set of URLS for the jars in the classpath.
* URLS will have the protocol of jar eg: jar:http://HOST/PATH/JARNAME.jar!/META-INF/MANIFEST.MF
*/
static Set<URL> getAllJarUrls() {
try {
Set<URL> urls = new LinkedHashSet<URL>();
Enumeration<URL> mfUrls = Thread.currentThread().getContextClassLoader().getResources("META-INF/MANIFEST.MF");
while (mfUrls.hasMoreElements()) {
URL jarUrl = mfUrls.nextElement();
// System.out.println(jarUrl);
if (!jarUrl.getProtocol().equals("jar")) {
continue;
}
urls.add(jarUrl);
}
return urls;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* get the jarFile object for the given url
*
* @param jarUrl
* @return
* @throws IOException
*/
public static JarFile getJarFile(URL jarUrl) throws IOException {
URLConnection urlConnnection = jarUrl.openConnection();
if (urlConnnection instanceof JarURLConnection) {
// Using a JarURLConnection will load the JAR from the cache when using Webstart 1.6
// In Webstart 1.5, the URL will point to the cached JAR on the local filesystem
JarURLConnection jcon = (JarURLConnection) urlConnnection;
return jcon.getJarFile();
} else {
throw new AssertionError("Expected JarURLConnection");
}
}
/**
* Spawn a new thread to run through each jar in the classpath and create a hardlink
* to the jars softly referenced signers infomation.
*/
public static void go() {
if (!isHardLinkerEnabled()) {
return;
}
Logger.info("Starting Resource Preloader Hardlinker");
Thread t = new Thread(new Runnable() {
public void run() {
try {
Set<JarFile> jars = getAllJarsFilesInClassPath();
for (JarFile jar : jars) {
makeHardSignersRef(jar);
}
} catch (Exception e) {
Logger.warn("Problem preloading resources", e);
} catch (Error e) {
Logger.error("Error preloading resources", e);
}
}
});
t.start();
}
}
当我们使用 JRE6 启动应用程序时,它工作正常。但问题出在 JRE7 上。当应用程序使用 JRE7 启动时,我们在日志中得到以下异常。从异常中我们知道 jars 不是硬引用的,如果 jars 被垃圾收集,用户可能会遇到问题。我们下周有一个版本,需要找到解决此问题的方法。
java.lang.NoSuchMethodException: com.sun.deploy.cache.CachedJarFile.getSigners()
at java.lang.Class.getDeclaredMethod(Unknown Source)
at com.XXXXXX.ui.main.JarSignersHardLinker.callNoArgMethod(JarSignersHardLinker.java:96)
at com.XXXXXX.ui.main.JarSignersHardLinker.makeHardSignersRef(JarSignersHardLinker.java:45)
at com.XXXXXX.ui.main.JarSignersHardLinker$1.run(JarSignersHardLinker.java:262)
at java.lang.Thread.run(Unknown Source)
java.lang.NoSuchFieldException: signersRef
at java.lang.Class.getDeclaredField(Unknown Source)
at com.XXXXXX.ui.main.JarSignersHardLinker.makeHardLink(JarSignersHardLinker.java:69)
at com.XXXXXX.ui.main.JarSignersHardLinker.makeHardSignersRef(JarSignersHardLinker.java:46)
at com.XXXXXX.ui.main.JarSignersHardLinker$1.run(JarSignersHardLinker.java:262)
at java.lang.Thread.run(Unknown Source)
此异常重复 52 次(针对所有 JAR)。
我们已确保所有 52 个 jars/文件(JNLP 本身除外)都已正确签名,并且在启动应用程序之前清除了 java 缓存。
使用的JAVA版本是windows机器上的JDK 7u40。
尝试的选项是:
- 删除 jdk.certpath.disabledAlgorithms=MD2, RSA keySize
- 检查 jar 签名者证书。签名者证书使用 SHA1withRSA 作为签名算法。
注意:
- 源代码在 JAVA 5u11 中编译并在 JAVA 7u40 中运行
- 我们观察到了另一个差异。使用 JRE6,当我们运行相同的 一段代码,它首先从 java/jre6/lib 路径加载 JAVAWS.jar、Deploy.jar 和插件 .jar,但对于 JRE7,这些 jar 不会加载。
这已在 64 位和 32 位 java 版本中尝试过,但没有成功。
非常感谢这里的任何帮助。
【问题讨论】:
-
这是您在部署 jar 之前对它们进行签名的方式吗?你试过
<jarsign/>蚂蚁任务吗?这个在 jdk1.7 更新 45 之前没有给我任何问题
标签: java classloader java-web-start jnlp jarsigner