在通过Java Webstart运行时,invokeLater中的NullPointerException

从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.它看起来像JRE为不同的应用程序上下文创建了额外的事件队列,如果程序由JWS启动的话。 在JWS中有3个AppContext和3个EVT,并且如果从IDE执行程序,则只有一个上下文和EVT。


---- Update2:

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

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


---- Update3:

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

然后可以执行RMI客户端。 成功执行后,框架应该包括:

但是如果您尝试使用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文件定义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();
    }
}

---- Update4:

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

其他相关的错误:

  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8019274
  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8028290
  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8017770
  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8021370

  • 该问题发生在Webstart环境中。 在Webstart版本的Java 7u25之前,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);
    

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

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

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

    由于我的应用程序的需要,我可以将它添加到使用SwingUtilities的单个方法中,但可能需要将它添加到RMI Callable Object上的每个方法。

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


    这是JDK-8019274的解决方法,打包在实用程序类中。 对我们而言,invokeAndWait()仍然是一个问题。 此示例具有invokeLater()的现有修复程序和invokeAndWait()的新修复程序。

    笔记:

  • 你需要在你的项目中包含jnlp.jar。
  • 在调用invokeLater()之前,在main()方法的早期调用init()
  • 用这些调用替换SwingUtilities的所有调用invokeLater()和invokeAndWait()
  • (免责声明:这是来自我们的产品,该解决方案的某些方面可能不适用于您。)

    public class JreFix {
        private static String badVersionInfo = null;
        private static AppContext awtEventDispatchContext = null;
        private static AppContext mainThreadContext = null;
        private static Boolean isWebStart = null;
        private static BasicService basicService = null;
        private static IntegrationService integrationService = null;
    
        /**
         * Call this early in main().  
         */
        public static void init() {
            if (isWebstart() && isApplicableJvmType()) {
                String javaVersion = System.getProperty("java.version");
    
                if ("1.7.0_25".equals(javaVersion)) {
                    badVersionInfo = "7u25";
                }
                else if ("1.7.0_40".equals(javaVersion)) {
                    badVersionInfo = "7u40";
                }
                else if (javaVersion != null && "1.6.0_51".equals(javaVersion.substring(0,8))) {
                    badVersionInfo = "6u51";
                }
                else if ("javaws-10.25.2.16".equals(System.getProperty("javawebstart.version"))) {
                    badVersionInfo = "Web Start 10.25.2.16";
                }
            }
    
            if (badVersionInfo != null) {
                mainThreadContext = AppContext.getAppContext();
                try {
                    SwingUtilities.invokeAndWait(new Runnable() {
                        public void run() {
                            awtEventDispatchContext = AppContext.getAppContext();
                        }
                    });
                }
                catch (Exception e) {
                    displayErrorAndExit(null);
                }
    
                if (mainThreadContext == null || awtEventDispatchContext == null) {
                     displayErrorAndExit(null);
                }
            }
        }
    
        public static void invokeNowOrLater(Runnable runnable) {
            if (hasAppContextBug()) {
                invokeLaterOnAwtEventDispatchThreadContext(runnable);
            }
            else {
                SwingUtilities.invokeLater(runnable);
            }
        }
    
        public static void invokeNowOrWait(Runnable runnable) {
            if (hasAppContextBug()) {
                fixThreadAppContext(null);
            }
    
            try {
                SwingUtilities.invokeAndWait(runnable);
            } 
            catch (Exception e) {
                // handle it
            }
        }
    
        public static boolean hasAppContextBug() {
            return isJreWithAppContextBug() && AppContext.getAppContext() == null;
        }
    
        public static void invokeLaterOnAwtEventDispatchThreadContext(Runnable runnable) {
            sun.awt.SunToolkit.invokeLaterOnAppContext(awtEventDispatchContext, runnable);
        }
    
        public static void fixThreadAppContext(Component parent) {
            try {
                final Field field = AppContext.class.getDeclaredField("threadGroup2appContext");
                field.setAccessible(true);
                Map<ThreadGroup, AppContext> threadGroup2appContext = (Map<ThreadGroup, AppContext>)field.get(null);
                final ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup();
                threadGroup2appContext.put(currentThreadGroup, mainThreadContext);
            } 
            catch (Exception e) {
                displayErrorAndExit(parent);
            }
    
            if (AppContext.getAppContext() == null) {
                 displayErrorAndExit(parent);
            }
        }
    
        private static boolean isJreWithAppContextBug() {
            return badVersionInfo != null;
        }
    
        private static void displayErrorAndExit(Component parent) {
            JLabel msgLabel = new JLabel("<html>" + 
                    "Our application cannot run using <b>Web Start</b> with this version of Java.<p><p>" +
                    "Java " + badVersionInfo + " contains a bug acknowledged by Oracle (JDK-8019274).");
            JOptionPane.showMessageDialog(parent, msgLabel, "Java Version Error", JOptionPane.ERROR_MESSAGE);
            System.exit(1);
        }
    
        private static boolean isApplicableJvmType() {
            String vendor = System.getProperty("java.vendor");
            String vmName = System.getProperty("java.vm.name");
            if (vendor != null && vmName != null) {
                return vmName.contains("Java HotSpot") &&
                        (vendor.equals("Oracle Corporation") || 
                         vendor.equals("Sun Microsystems Inc."));
            }
    
            return false;
        }
    
        private static boolean isWebstart() {
            if (isWebStart == null) {
                try { 
                    basicService = (BasicService) ServiceManager.lookup("javax.jnlp.BasicService");             
                    isWebStart = true;
                } 
                catch (UnavailableServiceException e) { 
                    isWebStart = false;
                }           
    
                try {
                    integrationService = (IntegrationService) ServiceManager.lookup("javax.jnlp.IntegrationService");
                } 
                catch (UnavailableServiceException e) {
                }
            }
            return isWebStart;
        }
    }
    
    链接地址: http://www.djcxy.com/p/66125.html

    上一篇: NullPointerException in invokeLater while running through Java Webstart

    下一篇: getResource() can't load content in a jar