NullPointerException in invokeLater,同时通过 Java Webstart 运行

2022-09-03 01:51:12

从 JRE 1.7.0_21 升级到 1.7.0_25-b15 后,我的应用程序在从 Java WebStart 运行时开始在 SwingUtilities.invokeLater(...) 中抛出 NullPointerException。令人惊讶的是,当它作为独立应用程序(在JWS之外)执行时,它工作得很好。

下面是堆栈的顶部:

Exception in thread "AWT-EventQueue-2" java.lang.NullPointerException
at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1011)
at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1007)
at sun.awt.SunToolkit.getSystemEventQueueImpl(SunToolkit.java:1002)
at java.awt.Toolkit.getEventQueue(Toolkit.java:1730)
at java.awt.EventQueue.invokeLater(EventQueue.java:1217)
at javax.swing.SwingUtilities.invokeLater(SwingUtilities.java:1290)
at AppletView$8.setBaseUnits(AppletView.java:536)
    (...)

为了让您全面了解:方法setBaseUnits(..)被远程服务器作为RMI的回调调用。完整的堆栈跟踪很长。

安全模型中是否有某些东西在RMI或JWS中发生了变化,可能会破坏事情?如果是这样,我预计会有一些安全异常,但它可能是在JRE中未正确检测到并导致NPE的原因。

任何建议都值得赞赏。


---- 更新1:

JRE 1.7.0_25 更新也存在类似的问题,可能是关于某些安全更改和 AppContext 对象:https://forums.oracle.com/message/11080621 https://forums.oracle.com/thread/2552799。我尝试了建议的修复:https://forums.oracle.com/message/11082162#11082162,但没有任何成功。

我可以在我的应用程序中看到3个AWT-EventQueue线程,其数字从0到2。如果程序由 JWS 启动,则 JRE 似乎会为不同的应用程序上下文创建其他事件队列。JWS 中有 3 个 AppContext 和 3 个 EVT,如果从 IDE 执行程序,则只有一个上下文和 EVT。


---- 更新2:

下面有一个解决方法,如gurumman所建议的那样(非常感谢)。不幸的是,必须替换所有对 from RMI 线程的调用,并且程序开始依赖于 Sun JRE 内部 API。SwingUtilities.invokeLater(..)

我仍然在寻找更通用的方法,而不是特定于Sun JRE的。我认为这是一个JRE错误。也许可以以某种方式修补它:AppContext在RMI线程中不应该为空。


----更新3:

我做了一个简单的测试用例来说明问题。它由4个文件组成。要运行此测试用例,需要对目标 jar (TestCase.jar) 进行签名。首先在 launch.jnlp 中指定正确的代码库,然后通过 Java Web Start 运行服务器(例如,使用 javaws launch.jnlp)。屏幕上应显示以下帧:

The server application frame after start

然后可以执行 RMI 客户端。成功执行后,帧应包含:

The server application frame after successful RMI call

但是,如果您尝试使用 JWS 执行服务器,您将在客户端程序中收到以下异常(异常从 RMI 服务器传播到 RMI 客户端):

Exception in thread "main" java.lang.NullPointerException
    at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1011)
    at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1007)
    at sun.awt.SunToolkit.getSystemEventQueueImpl(SunToolkit.java:1002)
    at java.awt.Toolkit.getEventQueue(Toolkit.java:1730)
    at java.awt.EventQueue.invokeLater(EventQueue.java:1217)
    at javax.swing.SwingUtilities.invokeLater(SwingUtilities.java:1290)
    at testcase.RmiServiceImpl.callBack(RmiServiceImpl.java:70)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322)
    at sun.rmi.transport.Transport$1.run(Transport.java:177)
    at sun.rmi.transport.Transport$1.run(Transport.java:174)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.rmi.transport.Transport.serviceCall(Transport.java:173)
    at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:553)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:808)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:667)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)
    at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:273)
    at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:251)
    at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:160)
    at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:194)
    at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:148)
    at com.sun.proxy.$Proxy0.callBack(Unknown Source)
    at testcase.RmiClient.main(RmiClient.java:22)

所以这里是测试用例文件:

1) JNLP file definition launch.jnlp:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<jnlp codebase="file:/home/user/NetBeansProjects/TestCase/dist/" href="launch.jnlp" spec="1.0+">
    <information>
        <title>TestCase</title>
        <vendor>digital_infinity</vendor>
        <homepage href=""/>
        <description>TestCase</description>
        <description kind="short">TestCase</description>
    </information>
<security>
  <all-permissions/>
</security>
    <update check="always"/>
    <resources>
        <j2se version="1.7+"/>
        <jar href="TestCase.jar" main="true"/>
    </resources>
    <application-desc main-class="testcase.RmiServiceImpl">
    </application-desc>
</jnlp>

2) RMI 接口定义 (RmiService.java):

package testcase;    
public interface RmiService extends java.rmi.Remote  {
    void callBack() throws java.rmi.RemoteException;
}

3) RMI服务代码及服务主类:

package testcase;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

/**
 */
public class RmiServiceImpl extends java.rmi.server.UnicastRemoteObject 
implements RmiService {

    final static int PORT = 1099;

    static JFrame frame;
    static JTextField textField;

    public RmiServiceImpl() throws RemoteException {
        super(PORT);
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws Exception {
        Registry reg;
        RmiServiceImpl service = new RmiServiceImpl();
        try {
            reg = LocateRegistry.getRegistry(PORT);
            reg.rebind("test", service);
        } catch (RemoteException ex) {
            reg = LocateRegistry.createRegistry(PORT);
            reg.rebind("test", service);
        }
        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                frame = new JFrame("Test App");
                textField = new JTextField("Before call to callBack");
                frame.getContentPane().add(textField);
                frame.pack();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
            }
        });
    }

    /** RMI callback */
    public void callBack() {
        Runnable rn = new Runnable() {
            public void run() {
                textField.setText("CallBack succesfully called.");
                frame.pack();
            }
        };
        SwingUtilities.invokeLater(rn);
    }
}

4)简单的客户端代码:

package testcase;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RmiClient {
    public static void main(String[] args) throws Exception {
        //now we trying to communicate with object through RMI
        Registry reg = LocateRegistry.getRegistry(RmiServiceImpl.PORT);
        //after got the registry, lookup the object and finally do call
        RmiService serv = (RmiService) reg.lookup("test");
        serv.callBack();
    }
}

----更新4:

JRE 错误 I 提交: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8019272

其他相关错误:


答案 1

我发现了我认为是这个错误的更好解决方案。

我只是在调用 SwingUtilities 或任何与 Swing 相关的组件方法之前添加了以下代码。它为 RMI 线程创建一个新的 AppContext(运行以下代码时,RMI 线程必须是当前线程)。

if(AppContext.getAppContext() == null){
    SunToolkit.createNewAppContext();
}

由于我的应用程序的需要,我能够将其添加到使用 SwingUtilities 的单个方法上,但您可能需要将其添加到 RMI 可调用对象上的每个方法中。

代码只需要运行一次,因此请检查应用程序的行为。


答案 2

在 Web 启动环境中出现问题。在 Java 7u25 的 Webstart 版本之前,AppContext 是在系统线程组上设置的。然而,它是在主线程组上设置的。

如果线程组的线程的父线程或祖父线程组不是主线程组,则它没有 sun.awt.AppContext。

您应该基于安全管理器的线程组(如果存在)创建线程。

Runnable task = ....
ThreadGroup threadGroup = System.getSecurityManager() != null
                                    ? System.getSecurityManager().getThreadGroup()
                                    : Thread.currentThread().getThreadGroup();
Thread t = new Thread(threadGroup, task, "my thread", 0);

推荐