使用webstart 1.6启动1.4.2_12应用程序时出现MissingResourceException

时间:2012-09-21 11:31:42

标签: java classloader java-web-start resourcebundle

在我最终弄清楚为什么JWS 1.6.0_29无法启动1.4.2_12应用程序(参见this question)之后,我在启动1.4.2_12应用程序时遇到了另一个例外。与JWS 1.6.0_29。

加载ResourceBundle时出现MissingResourceException。然而,message.properties文件确实存在与正在加载它的类在同一个包中。

  

当使用JWS 1.4或1.5启动应用程序时,不会引发异常   仅在启动应用程序时引发异常。与JWS 1.6。

完全stackstrace是:

java.lang.ExceptionInInitializerError
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at com.sun.javaws.Launcher.executeApplication(Unknown Source)
    at com.sun.javaws.Launcher.executeMainClass(Unknown Source)
    at com.sun.javaws.Launcher.doLaunchApp(Unknown Source)
    at com.sun.javaws.Launcher.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Caused by: java.util.MissingResourceException: Can't find bundle for base name com.test.hello.messages, locale fr_FR
    at java.util.ResourceBundle.throwMissingResourceException(Unknown Source)
    at java.util.ResourceBundle.getBundleImpl(Unknown Source)
    at java.util.ResourceBundle.getBundle(Unknown Source)
    at com.test.hello.Main.<clinit>(Main.java:10)
    ... 9 more

要重现的测试用例

JNLP描述符是:

<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+" codebase="http://localhost:80/meslegacy/apps" href="testJwsXXTo142.jnlp">
    <information>
        <title>JWS TEST 1.6 -> 1.4.2</title>
        <vendor>Hello World Vendor</vendor>
        <description>Hello World</description>
    </information>

    <security>
        <all-permissions />
    </security>

    <resources>
        <j2se version="1.4.2_12" href="http://java.sun.com/products/autodl/j2se" />
        <jar href="jar/helloworld.jar" main="true" />
    </resources>

    <application-desc main-class="com.test.hello.Main" />
</jnlp>

com.test.hello.Main类是:

package com.test.hello;

import java.util.ResourceBundle;

import javax.swing.JFrame;

public class Main {

    private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(Main.class.getPackage().getName()+".messages");

    public static void main(String[] args) {
        JFrame frame = new JFrame("Hello world !");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(800,600);
        frame.setVisible(true);
    }

}

补充测试

  • 为ResourceBundle.getBundle()指定ClassLoader和Locale 方法无法解决问题。
  • Main.class.getClassLaoder()和 Thread.currentThread()。getContextClassLaoder()已经过测试并产生了相同的异常。
  • “手动”加载资源确实有效(见下文)。

测试代码以手动加载资源:

ClassLoader cl = Main.class.getClassLoader();
String resourcePath = baseName.replaceAll("\\.", "/");
System.out.println(resourcePath);
URL resourceUrl = cl.getResource(resourcePath+".properties");
System.out.println("Resource manually loaded :"+resourceUrl);

将产生:

com/test/hello/messages.properties
Resource manually loaded :jar:http://localhost:80/meslegacy/apps/jar/helloworld.jar!/com%2ftest%2fhello%2fmessages.properties
  • 但是,虽然可以找到资源,但获取的资源内容却不是。

示例:

ClassLoader cl = Main.class.getClassLoader();
String resourcePath = baseName.replaceAll("\\.", "/") + ".properties";
URL resourceUrl = cl.getResource(resourcePath);
// here, resourceUrl is not null. Then build bundle by hand
ResourceBundle prb = new PropertyResourceBundle(resourceUrl.openStream());

哪个产生:

java.io.FileNotFoundException: JAR entry com%2ftest%2fhello%2fmessages.properties not found in C:\Documents and Settings\firstname.lastname\Application Data\Sun\Java\Deployment\cache\6.0\18\3bfe5d92-3dfda9ef
    at com.sun.jnlp.JNLPCachedJarURLConnection.connect(Unknown Source)
    at com.sun.jnlp.JNLPCachedJarURLConnection.getInputStream(Unknown Source)
    at java.net.URL.openStream(Unknown Source)
    at com.test.hello.Main.main(Main.java:77)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at com.sun.javaws.Launcher.executeApplication(Unknown Source)
    at com.sun.javaws.Launcher.executeMainClass(Unknown Source)
    at com.sun.javaws.Launcher.doLaunchApp(Unknown Source)
    at com.sun.javaws.Launcher.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)

似乎更像是一种缓存问题......

我们任何人都有提示,我们将不胜感激,

感谢阅读。

1 个答案:

答案 0 :(得分:1)

以下是此问题的解释和解决方法。

1 - 解释

问题来自系统ClassCloader(JWS6系统ClassLoader)返回的URL。

使用JWS 1.6,系统ClassLoader返回的URL包含转义序列,如下所示:

jar:http://localhost:80/meslegacy/apps/jar/helloworld.jar!/com%2ftest%2fhello%2fmessages.properties

在类路径中定位资源是可能的,但是当涉及到实际访问该资源的内容时,会引发FileNotFoundException:这是导致ResourceBundle中的FileNotFoundException的原因。

请注意,当URL中没有出现转义序列时,例如当资源位于claspath的根目录时,访问资源内容没有问题。只有在URL路径部分中获得%xx内容时才会出现问题。

2 - Workarround

问题一旦集中(我需要花费数天的时间来解决这个问题!),现在是时候为此找到一个解决方案了。 虽然我可以在特定的本地化代码部分修复我的问题,但很快就证明通过编写特定的ClassLoader以“替换”JNLPClassLoader可以解决全局问题。 我没有实际“替换”,因为对我来说似乎不可能,但我宁愿做以下事情:

  1. 禁用SecurityManager以使用我的自定义 类加载器
  2. 编写我自己的从URLClassLoader派生的类加载器,它在返回时修复URL
  3. 使用从中提取的claspath设置其类路径 JNLPClassLoader
  4. 将此自定义类加载器设置为上下文类加载器
  5. 将此自定义类加载器设置为AWT-Event-Thread上下文 类加载器
  6. 使用此自定义类加载器加载我的应用程序入口点。
  7. 这给出了以下ClassLoader

    public class JwsUrlFixerClassLoader extends URLClassLoader {
    
        private final static Logger LOG = Logger.getLogger(JwsUrlFixerClassLoader.class);
    
        private static String SIMPLE_CLASS_NAME = null;
    
        private static boolean LOG_ENABLED = "true".equals(System.getProperty("classloader.debug"));
    
        static {
            SIMPLE_CLASS_NAME = JwsUrlFixerClassLoader.class.getName();
            int idx = SIMPLE_CLASS_NAME.lastIndexOf('.');
            if (idx >= 0 && idx < SIMPLE_CLASS_NAME.length()-1) {
                SIMPLE_CLASS_NAME = SIMPLE_CLASS_NAME.substring(idx + 1);
            }
        }
    
        public JwsUrlFixerClassLoader(URL[] urls, ClassLoader parent) {
            super(urls, parent);
        }
    
        public URL getResource(String name) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("getResource(): getResource(" + name + ")");
            }
            if (LOG_ENABLED) {
                login("getResource(" + name + ")");
            }
            URL out = super.getResource(name);
            if (out != null) {
                out = URLFixerTool.fixUrl(out);
            }
            if (LOG_ENABLED) {
                logout("getResource returning " + out);
            }
            return out;
        }
    
        public URL findResource(String name) {
            if (LOG_ENABLED) {
                login("findResource(" + name + ")");
            }
            URL out = super.findResource(name);
            if (out != null) {
                out = URLFixerTool.fixUrl(out);
            }
            if (LOG_ENABLED) {
                logout("findResource returning " + out);
            }
            return out;
        }
    
        public InputStream getResourceAsStream(String name) {
            if (LOG_ENABLED) {
                login("getResourceAsStream(" + name + ")");
            }
            InputStream out = super.getResourceAsStream(name);
            if (LOG_ENABLED) {
                logout("getResourceAsStream returning " + out);
            }
            return out;
        }
    
        protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
            if (LOG_ENABLED) {
                login("loadClass(" + name + ")");
            }
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                try {
                    c = findClass(name);
                } catch (ClassNotFoundException cnfe) {
                    if (getParent() == null) {
                        // c = findBootstrapClass0(name);
                        Method m = null;
                        try {
                            m = URLClassLoader.class.getMethod("findBootstrapClass0", new Class[] {});
                            m.setAccessible(true);
                            c = (Class) m.invoke(this, new Object[] { name });
                        } catch (Exception e) {
                            throw new ClassNotFoundException();
                        }
                    } else {
                        c = getParent().loadClass(name);
                    }
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            if (LOG_ENABLED) {
                logout("loadClass returning " + c);
            }
            return c;
        }
    
        private static void login(String message) {
            System.out.println("---> [" + Thread.currentThread().getName() + "] " + SIMPLE_CLASS_NAME + ": " + message);
        }
    
        private static void logout(String message) {
            System.out.println("<--- [" + Thread.currentThread().getName() + "] " + SIMPLE_CLASS_NAME + ": " + message);
        }
    
    }
    

    现在在我设置为JNLP描述符中的主类的AppBoostrap类中,我执行以下操作:

        System.setSecurityManager(null);
        ClassLoader parentCL = AppBootstrap.class.getClassLoader();
        URL[] classpath = new URL[] {};
        if (parentCL instanceof URLClassLoader) {
            URLClassLoader ucl = (URLClassLoader) parentCL;
            classpath = ucl.getURLs();
        }
        final JwsUrlFixerClassLoader vlrCL = new JwsUrlFixerClassLoader(classpath, parentCL);
        Thread.currentThread().setContextClassLoader(vlrCL);
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
    
                public void run() {
                    Thread.currentThread().setContextClassLoader(vlrCL);
                }
            });
        } catch (Exception e) {
            LOG.error("main(): Failed to set context classloader !", e);
        }
    

    在上一段摘录中,我得到了加载我的AppBootstrap类的ClassLoader,并将其用作我的JwsUrlFixerClassLoader的父类加载器。

    我必须修复URLClassLodaer.loadClass()的默认父委派策略的问题,并将其替换为“先尝试我的类路径然后再使用父级”。

    在完成所有工作之后,一切顺利,其他一些我们到目前为止无法解释的错误已经消失了。

    那太神奇了!经过很多痛苦之后......

    希望有一天能帮到某人......