[b]现象[/b]
两个module A和B分别采用了infosec的不同版本,虽然classloader已经保证了A和B所依赖的资源均可以正确的load, 并且成功地newInstance() 或者new构造,但是仍然出现ClassCastException、ClassNotFoundException、NoSuchMethodException等异常
[b]问题分析[/b]
Security vendor有infosecurity,bouncycastle等好几种,在JCE框架里面都被抽象成Provider,每个供应商实现抽象类Provider,JVM会管理这些Provider.同一个JVM中,不同的module依赖了同一个Provider(eg.Infosecurity)的不同版本,如果Provider之间是不兼容的,尽管通过ClassLoader机制让不通的module分别load到了各自对应的Provider,但是因为JCE管理Provider的方式,它们的name完全相同,B的会把A的infosec Provider给覆盖。这会导致A使用了B的provider,仍然会产生可恶的ClassCastException、NoSuchMethodException等。
[b]问题探索[/b]
一步步的看Provider是被管理使用的。首先从MessageDigest开始,一般我们这么使用
[code]
MessageDigest md = MessageDigest.getInstance("SHA-1","INFOSEC");
//or
MessageDigest md = MessageDigest.getInstance("MD5");[/code]
看看里面怎么做的,很明显,找到多个取第一个。
然后就到Security类了
getSpiClass方法暂时忽略掉,这里有2个分支,为了方便直接看第二个分支。
可以看到,最终的是在Providers所管理的ProviderList里面保存所有的Provider,而且保存的方式是以paramString3参数,也就是"INFOSEC"去获取Provider。而相同vendor的不同版本Provider的Name是完全一样的,区别是version不同,这里印证了猜想:[color=red]不同的Provider因为相同的name而被认为是同一个Provider而产生的同名覆盖[/color]
如何解决。Security,Providers,ProviderList是java core包的类,不可轻易改动,否则后果很难估计。我在这里采用的方法比较简陋,从Providers类做改动,直接覆盖Providers类的管理逻辑:针对当前Thread的classloader的类型做判断,如果是SystemClassLoader,则使用自己的Provider管理逻辑,否则还是使用之前的管理方式。
完成后对Providers重新打包成providers.jar,加入启动参数-Xbootclasspath/p:/data/overwrite/providers.jar以覆盖rt.jar中的Providers类。大功告成。
两个module A和B分别采用了infosec的不同版本,虽然classloader已经保证了A和B所依赖的资源均可以正确的load, 并且成功地newInstance() 或者new构造,但是仍然出现ClassCastException、ClassNotFoundException、NoSuchMethodException等异常
[b]问题分析[/b]
Security vendor有infosecurity,bouncycastle等好几种,在JCE框架里面都被抽象成Provider,每个供应商实现抽象类Provider,JVM会管理这些Provider.同一个JVM中,不同的module依赖了同一个Provider(eg.Infosecurity)的不同版本,如果Provider之间是不兼容的,尽管通过ClassLoader机制让不通的module分别load到了各自对应的Provider,但是因为JCE管理Provider的方式,它们的name完全相同,B的会把A的infosec Provider给覆盖。这会导致A使用了B的provider,仍然会产生可恶的ClassCastException、NoSuchMethodException等。
[b]问题探索[/b]
一步步的看Provider是被管理使用的。首先从MessageDigest开始,一般我们这么使用
[code]
MessageDigest md = MessageDigest.getInstance("SHA-1","INFOSEC");
//or
MessageDigest md = MessageDigest.getInstance("MD5");[/code]
看看里面怎么做的,很明显,找到多个取第一个。
public static MessageDigest getInstance(String paramString)
throws NoSuchAlgorithmException
{
try
{
Object[] arrayOfObject = Security.getImpl(paramString, "MessageDigest", (String)null);
if (arrayOfObject[0] instanceof MessageDigest)
{
localObject = (MessageDigest)arrayOfObject[0];
((MessageDigest)localObject).provider = ((Provider)arrayOfObject[1]);
return localObject;
}
Object localObject = new Delegate((MessageDigestSpi)arrayOfObject[0], paramString);
((MessageDigest)localObject).provider = ((Provider)arrayOfObject[1]);
return localObject;
}
catch (NoSuchProviderException localNoSuchProviderException)
{
throw new NoSuchAlgorithmException(paramString + " not found");
}
}
然后就到Security类了
static Object[] getImpl(String paramString1, String paramString2, String paramString3)
throws NoSuchAlgorithmException, NoSuchProviderException
{
if (paramString3 == null)
return GetInstance.getInstance(paramString2, getSpiClass(paramString2), paramString1).toArray();
return GetInstance.getInstance(paramString2, getSpiClass(paramString2), paramString1, paramString3).toArray();
}
getSpiClass方法暂时忽略掉,这里有2个分支,为了方便直接看第二个分支。
public static Instance getInstance(String paramString1, Class paramClass, String paramString2, String paramString3)
throws NoSuchAlgorithmException, NoSuchProviderException
{
return getInstance(getService(paramString1, paramString2, paramString3), paramClass);
}
public static Provider.Service getService(String paramString1, String paramString2, String paramString3)
throws NoSuchAlgorithmException, NoSuchProviderException
{
if ((paramString3 == null) || (paramString3.length() == 0))
throw new IllegalArgumentException("missing provider");
Provider localProvider = Providers.getProviderList().getProvider(paramString3);
if (localProvider == null)
throw new NoSuchProviderException("no such provider: " + paramString3);
Provider.Service localService = localProvider.getService(paramString1, paramString2);
if (localService == null)
throw new NoSuchAlgorithmException("no such algorithm: " + paramString2 + " for provider " + paramString3);
return localService;
}
可以看到,最终的是在Providers所管理的ProviderList里面保存所有的Provider,而且保存的方式是以paramString3参数,也就是"INFOSEC"去获取Provider。而相同vendor的不同版本Provider的Name是完全一样的,区别是version不同,这里印证了猜想:[color=red]不同的Provider因为相同的name而被认为是同一个Provider而产生的同名覆盖[/color]
如何解决。Security,Providers,ProviderList是java core包的类,不可轻易改动,否则后果很难估计。我在这里采用的方法比较简陋,从Providers类做改动,直接覆盖Providers类的管理逻辑:针对当前Thread的classloader的类型做判断,如果是SystemClassLoader,则使用自己的Provider管理逻辑,否则还是使用之前的管理方式。
完成后对Providers重新打包成providers.jar,加入启动参数-Xbootclasspath/p:/data/overwrite/providers.jar以覆盖rt.jar中的Providers类。大功告成。