tomcat 类加载器错误?

Dan*_*ana 3 java tomcat bouncycastle

更新:我已经了解了更多有关正在发生的事情并在底部添加了新信息。

我有 2 个应用程序在 tomcat 下运行。首先加载 App1,然后加载 App2。如果 App1 在启动过程中遇到任何类型的错误并且无法成功加载,我会在 App2 启动过程中收到此错误:

Caused by: java.security.NoSuchAlgorithmException: No such algorithm: RSA/NONE/OAEPWithSHA1AndMGF1Padding
    at javax.crypto.Cipher.getInstance(DashoA13*..)
    at javax.crypto.Cipher.getInstance(DashoA13*..)
    at com.jp.protection.security.BouncyCastleSecurityProvider.getCipher(BouncyCastleSecurityProvider.java:139)
    at com.jp.protection.security.BouncyCastleSecurityProvider.decode(BouncyCastleSecurityProvider.java:110)
    ... 70 more
Caused by: java.lang.NullPointerException
    at org.bouncycastle.jcajce.provider.util.DigestFactory.getDigest(DigestFactory.java:86)
    at org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.initFromSpec(CipherSpi.java:83)
    at org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.engineSetPadding(CipherSpi.java:214)
    at javax.crypto.Cipher$r.a(DashoA13*..)
    ... 74 more
Run Code Online (Sandbox Code Playgroud)

请注意,最终原因是 NullPointerException。我下载了 DigestFactory 的源代码,它看起来像这样(只是摘录了相关部分):

package org.bouncycastle.jcajce.provider.util;

public class DigestFactory
{
    private static Set sha1 = new HashSet();

    static
    {           
        sha1.add("SHA1");
        sha1.add("SHA-1");
    }

    public static Digest getDigest(
        String digestName) 
    {
        digestName = Strings.toUpperCase(digestName);

        if (sha1.contains(digestName))  ** line 86 where npe occurs**
        {
            return new SHA1Digest();
        }
    [...]
Run Code Online (Sandbox Code Playgroud)

在第 86 行获取 NPE 的唯一方法是 sha1 为空。(如果digestName为null,NPE将出现在对Strings.toUpperCase的调用中。)事实上,如果我在这里放置一个断点,在错误场景下调试器将sha1(以及所有其他类似的静态初始化字段)显示为null。这些字段是私有的,并且没有允许修改这些字段的方法。

这怎么可能?我想也许我的 DigestFactory 源代码与我正在运行的 jar 不完全匹配,因此调试器误导了我,但它应该是正确的版本,并且其他所有内容似乎都一致。

在调试器下,我尝试在 App2 初始化的早期阶段、异常发生之前调用 DigestFactory.getDigest("SHA-1") (使用调试器的求值表达式),并且它成功返回。这表明要么 DigestFactory 的静态字段已成功初始化,然后以某种方式设置为 null,要么另一个类加载器具有该类的不同版本(即使是这种情况,也无法解释它们如何为 null)。

此异常发生在第三方代码的 2 层深处(jproductivity Protection 包使用 bouncycastle),因此我对情况的控制是有限的。然而,我想首先了解这是如何可能的,以及希望如何防止或解决它。另一个谜团是为什么第一个应用程序的错误会对第二个应用程序产生影响——在 tomcat 下,这些应用程序应该有单独的类加载器。但是,如果第一个应用程序中没有错误,那么第二个应用程序中就不会出现此问题。

更新:自从我发布这篇文章以来,我学到了更多东西。当 webapp 被 tomcat 停止时(在本例中是由于启动错误),tomcat 会清空其类中的静态字段以避免内存泄漏。所以这解释了我的不可变静态字段如何设置为空。但是,这不应该影响 App2,因为它应该使用单独的类加载器。但我查看了调试器,实际上两个 Web 应用程序中的 DigestFactory 类都使用相同的类加载器。这与我能找到的所有 tomcat 文档相矛盾。对于其他类(我自己的类),有不同的类加载器。我想知道它是否与 DigestFactory 是静态和不可变的有什么关系,所以理论上它来自哪里并不重要。

因此,作为实验,我从两个 web 应用程序中删除了包含 DigestFactory 的 jar,并将其添加到 tomcat/lib 中(以便它是共享的,而不是任一 web 应用程序的一部分)。这解决了问题——它的字段没有被清空,大概是因为它不是有问题的 web 应用程序的一部分。然而,这种方法是不可取的,并且没有必要。这是 Tomcat 的错误吗?

Ein*_*rve 5

我相信正在发生的事情是,当您启动 App1 时,bouncycastle 会在应用程序类加载器下注册。如果我没记错的话,提供者会通过一些静态初始化器或方法注册到 JVM 类加载器中。

当您的 App1 崩溃(或重新部署)时,它的类加载器以及它加载的任何类(包括 bouncycastle)都会被删除。结果是 JVM 认为它仍然存在,因为它仍然注册,但实际上它并不存在。

解决方案是通过添加类似以下的行,将 BouncyCastleProvider 添加到 jre/lib/security/java.security 中的安全提供程序列表(java 7 位置,我认为它在旧版本中的 jre/lib/ext 中):

security.provider.[下一个可用号码]=org.bouncycastle.jce.provider.BouncyCastleProvider

您可能还需要在那里添加 jar 文件。