简体   繁体   English

无法在所有计算机上访问JAR中的资源

[英]Can't access resource in a JAR on all computers

I'm writing an application (specifically a plugin for the Bukkit Minecraft server). 我正在编写一个应用程序(特别是Bukkit Minecraft服务器的插件)。 Doing this requires that I access a .properties file from the JAR of the application. 这样做需要我从应用程序的JAR访问.properties文件。 This is where I encounter a strange problem. 这是我遇到一个奇怪问题的地方。 When I test the program on my development PC, it runs just fine. 当我在我的开发PC上测试程序时,它运行得很好。 The .properties file gets loaded and everything is fine. .properties文件被加载,一切都很好。 However, on the other computer that I test it on, I try to start the app, and it can't loaded the properties, and the InputStream is null . 但是,在我测试的其他计算机上,我尝试启动应用程序,它无法加载属性,并且InputStreamnull Here is the method in which I load the file: 这是我加载文件的方法:

public class Points {
    private HashMap<String, MessageFormat> messages;

    public Points() {
         buildMessages();
    }

public static void buildMessages() {
        Properties messageProps = new Properties();
        InputStream in = Points.class.getResourceAsStream("resources/messages.properties");
        messages = new HashMap<String, MessageFormat>();
        Enumeration en;
        try {
            messageProps.load(in);
        } catch(IOException ex) {
            System.err.println("Couldn't read message properties file!");
            return;
        } catch(NullPointerException ex) {
            System.err.println("Couldn't read message properties file!");
            if(in == null)
                System.out.println("IOStream null");
            return;
        }
        en = messageProps.propertyNames();
        while(en.hasMoreElements()) {
            String key = (String)en.nextElement();
            String prop = messageProps.getProperty(key);
            MessageFormat form = new MessageFormat(prop.replaceAll("&", 
                "\u00a7").replaceAll("`", ""));
            messages.put(key, form);
        }
    }
}

I've omitted some irrelevant code, but that is the gist of it. 我省略了一些不相关的代码,但这就是它的要点。 The structure of the JAR is as follows: JAR的结构如下:

   com/
       pvminecraft/
           points/
               Points.java <-- The class where the file is loaded
               resources/
                   messages.properties <-- The file being loaded

On my PC the file is loaded from resources/messages.properties , but on the other file, the InputStream is null, and my catch block for the NullPointerException is run. 在我的PC上,文件从resources/messages.properties加载,但在另一个文件中, InputStream为null,并且运行了NullPointerException catch块。 What could be causing the problem, and how could I fix it? 可能导致问题的原因是什么,我该如何解决? Thanks. 谢谢。

Update: Even using the full path ( /com/pvminecraft/points/resources/messages.properties ), the same issue is still persistent. 更新:即使使用完整路径( /com/pvminecraft/points/resources/messages.properties ),同样的问题仍然存在。

Update 2: Here is the full stack-trace: 更新2:这是完整的堆栈跟踪:

java.lang.NullPointerException
    at java.util.Properties$LineReader.readLine(Properties.java:435)
    at java.util.Properties.load0(Properties.java:354)
    at java.util.Properties.load(Properties.java:342)
    at com.pvminecraft.points.Points.buildMessages(Unknown Source)
    at com.pvminecraft.points.Points.onEnable(Unknown Source)
    at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:188)
    at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:968)
    at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:280)
    at org.bukkit.craftbukkit.CraftServer.loadPlugin(CraftServer.java:186)
    at org.bukkit.craftbukkit.CraftServer.enablePlugins(CraftServer.java:169)
    at org.bukkit.craftbukkit.CraftServer.reload(CraftServer.java:436)
    at org.bukkit.Bukkit.reload(Bukkit.java:187)
    at org.bukkit.command.defaults.ReloadCommand.execute(ReloadCommand.java:22)
    at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:165)
    at org.bukkit.craftbukkit.CraftServer.dispatchCommand(CraftServer.java:378)
    at org.bukkit.craftbukkit.CraftServer.dispatchCommand(CraftServer.java:374)
    at net.minecraft.server.MinecraftServer.b(MinecraftServer.java:564)
    at net.minecraft.server.MinecraftServer.w(MinecraftServer.java:541)
    at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:425)
    at net.minecraft.server.ThreadServerApplication.run(SourceFile:457)

All of the org.bukkit and org.craftbukkit stuff is the server. 所有的org.bukkitorg.craftbukkit都是服务器。 The .properties file is loaded in the buildMessages method, called by the onEnable method of Points . .properties文件加载到buildMessages方法中,由PointsonEnable方法调用。

Update 3: On a fresh install of Arch Linux, the message properties file is loaded correctly and all is well. 更新3:在全新安装的Arch Linux上,正确加载了消息属性文件,一切正常。 The remote server is Ubuntu Linux, and my dev PC is Arch. 远程服务器是Ubuntu Linux,我的开发PC是Arch。

Update 4: Alright, this is sort of a resolution. 更新4:好的,这是一种解决方案。 It seems to be a localized problem. 这似乎是一个局部问题。 I say that because I've managed to get access to two more computers, and the program runs correctly on both. 我这样说是因为我设法访问了另外两台计算机,并且程序在两者上运行正常。 While it's annoying, this doesn't seem to be anything wrong with my code or build scripts. 虽然这很烦人,但我的代码或构建脚本似乎没有任何问题。 I'm still wanting to know what's wrong, but it isn't pressing any more. 我仍然想知道什么是错的,但它不再紧迫了。 I'll continue looking into this. 我会继续研究这个。 Thanks everyone. 感谢大家。

Seems like minor subtleties between the different Java class loaders and their search paths. 看起来像是不同的Java类加载器及其搜索路径之间的细微差别。 Before going into these details; 在进入这些细节之前; why don't you try the full path within this jar file? 为什么不尝试这个jar文件中的完整路径? (ie something like this: (即这样的事情:

Points.class.getResourceAsStream("com/pvminecraft/points/resources/messages.properties");

)

Point.class.getClassLoader().getResourceAsStream("com/pvminecraft/points/resources/messages.properties");

Try it without the first '/' and it should work anywhere running a JVM. 尝试没有第一个'/',它应该在任何运行JVM的地方工作。

If that didn't work, please try to put the file in the ROOT of the JAR file and try it again. 如果这不起作用,请尝试将文件放在JAR文件的ROOT中并再次尝试。

If still doesn't work, try using this method: 如果仍然无效,请尝试使用此方法:

public static byte[] getFile(File zip, String fileName) throws FileNotFoundException, ZipException, IOException {
        String filename = fileName;

        if (!zip.exists()) {
            throw new FileNotFoundException(zip.getName());
        }
        while (filename.charAt(0) == '/' || filename.charAt(0) == '\\') {
            filename = filename.substring(1);
        }

        if (filename.contains("\\")) {
            filename = filename.replace("\\", "/");
        }

        ZipFile zipFile = new ZipFile(zip);
        Enumeration entries = zipFile.entries();

        ByteArrayOutputStream output;
        byte[] result = null;

        while (entries.hasMoreElements()) {
            ZipEntry entry = (ZipEntry) entries.nextElement();

            if (entry.getName().equalsIgnoreCase(filename)) {
                FileUtils.copyInputStream(zipFile.getInputStream(entry), output = new ByteArrayOutputStream());
                result = output.toByteArray();
                zipFile.close();
                output.close();
                return result;
            }
        }

        zipFile.close();
        throw new FileNotFoundException(filename);
    }

You will need this 你需要这个

public static void copyInputStream(InputStream in, OutputStream out) throws IOException {
    byte[] buffer = new byte[1024];
    int len;
    while (((len = in.read(buffer)) >= 0)) {
        out.write(buffer, 0, len);
    }
    out.flush();
}

Get the path of the running jar 获取正在运行的jar的路径

 String currentJar = "";
                                        // Get current jar path. Since user may rename this file, we need to do this way
              try {
                   currentJar = (Points.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
                   if (currentJar.startsWith("/")) currentJar = currentJar.substring(1);
                   } catch (URISyntaxException ex) {
                   }

The first '/' I don't really remember why it appears, but it do, so you must remove it: 第一个'/'我真的不记得它出现的原因,但确实如此,所以你必须删除它:

Finally call the method: getFile(currentJar, "PATH_TO_PROPERTIES_FILE"); 最后调用方法: getFile(currentJar, "PATH_TO_PROPERTIES_FILE");

You will have an array of bytes to work with. 您将拥有一个可以使用的字节数组。 Just put it as a ByteArrayInputStream and your problems should be solved. 只需将其作为ByteArrayInputStream,您的问题就应该解决了。


That code is part of a util class I've created, that's why the unecessary read to a byte array, but ofc, you can change it to use directly that InputStream to the Properties.load() method. 该代码是我创建的util类的一部分,这就是为什么不必要的读取字节数组,但是,你可以改变它直接使用InputStream到Properties.load()方法。

Link for the ZIP util class ZIP util类的链接

http://all-inhonmodman.svn.sourceforge.net/viewvc/all-inhonmodman/ModManager/src/modmanager/utility/ZIP.java?revision=292&content-type=text%2Fplain http://all-inhonmodman.svn.sourceforge.net/viewvc/all-inhonmodman/ModManager/src/modmanager/utility/ZIP.java?revision=292&content-type=text%2Fplain

Link for the FileUtils util class FileUtils util类的链接

http://all-inhonmodman.svn.sourceforge.net/viewvc/all-inhonmodman/ModManager/src/modmanager/utility/FileUtils.java?revision=294&content-type=text%2Fplain http://all-inhonmodman.svn.sourceforge.net/viewvc/all-inhonmodman/ModManager/src/modmanager/utility/FileUtils.java?revision=294&content-type=text%2Fplain

You may also want to make sure if your build script (Ant, Maven) or your IDE didn't remove/relocate that messages.properties (coz it is not .class) from the resulting JAR file. 您可能还需要确保构建脚本(Ant,Maven)或IDE没有从生成的JAR文件中删除/重定位那些messages.properties(因为它不是.class)。 You can check your JAR contents by using tools like 7zip or WinZip. 您可以使用7zip或WinZip等工具检查JAR内容。

Since you're loading a resource that's in the same package of your class ( Point ), you don't need to use the absolute path for loading it. 由于您正在加载属于同一个类( Point )的资源,因此您无需使用绝对路径来加载它。

Does the server use any kind of cache for loading the plugins? 服务器是否使用任何类型的缓存来加载插件? This could be caused by an older version of the plugin jar being present in the class path. 这可能是由于类路径中存在旧版本的插件jar。 To verify that the server is really loading the correct version of the jar file, you could try deploying a version of your plugin which logs something to the console and see if something happens (if the message does get logged). 要验证服务器是否真的加载了正确版本的jar文件,您可以尝试部署一个版本的插件,该插件会将某些内容记录到控制台并查看是否发生了某些事情(如果消息确实被记录)。

Also, I don't know how the class loader hierarchy is organized in the server, but you could try loading the resource from the current thread class loader (usually the root parent class loader, which will look for the resource in every other child class loader). 另外,我不知道如何在服务器中组织类加载器层次结构,但是您可以尝试从当前线程类加载器加载资源(通常是根父类加载器,它将在每个其他子类中查找资源)装载机)。 You'd have to use the absolute path for that. 你必须使用绝对路径。

ClassLoader rootCL = Thread.currentThread().getContextClassLoader();
InputStream resource = rootCL.getResourceAsStream(
        "/com/pvminecraft/points/resources/messages.properties");

Check this question to learn more about the different class loaders. 检查此问题以了解有关不同类加载器的更多信息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM