4

これが私の問題です。DOSコマンドラインに似たソフトウェアを作成しています。Command基本クラスを拡張するクラスを含むjarファイルをフォルダーにドロップするだけで、他のコマンドを追加できるようにしたいと思います。私が試したことはまだ何も機能しません。

これが私が試したことです:

  1. Reflectionsライブラリを使用して、コマンドクラスを拡張するクラスのjarファイルの検索を追加します。
    これにより、多くのエラーが発生し、jar内のクラスの大部分が見つかりませんでした。これは、進行中のすべてのものと関係があると思いますSomeClass$1.class
  2. jar内のすべてのファイルを繰り返し処理し、それをクラスパス
    に追加する方法を見つけることができませんでした。これは、の繰り返しをZipEntryクラスパスに追加できるもの(URLなど)に変換できなかったためです。
  3. jar全体をクラスパスに追加する
    プログラムはこれらのクラスの名前を認識しないため、これも機能しませんでした。したがって、これらのクラスをコマンドに変換することはできません。

私はここでいくつかの助けが欲しいです。プログラムをより拡張可能にする方法についての提案は大歓迎です。コードを含むものには+1;)
さらに情報が必要な場合は、私に知らせてください。

編集:
新しいコード:

URLClassLoader ucl = (URLClassLoader) ClassLoader.getSystemClassLoader();
    for(File file : FileHelper.getProtectedFile("/packages/").listFiles()) {
        if(file.getName().endsWith(".jar")) {
            Set<Class<?>> set = new HashSet<Class<?>>();
            JarFile jar = null;
            try {
                jar = new JarFile(file);
                Enumeration<JarEntry> entries = jar.entries();
                while(entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();

                    if(!entry.getName().endsWith(".class"))
                        continue;

                    Class<?> clazz;
                    try {
                        clazz = ucl.loadClass(entry.getName().replace("/", ".").replace(".class", "")); // THIS IS LINE 71
                    } catch(ClassNotFoundException e) {
                        e.printStackTrace();
                        continue;
                    }

                    if(entry.getName().contains("$1"))
                        continue;

                    if(clazz.getName().startsWith("CMD_"))
                        set.add(clazz);

                    jar.close();
                }
            } catch(Exception e) {
                //Ignore file
                try {
                    jar.close();
                } catch (IOException e1) {/* Don't worry about it too much... */}
                OutputHelper.log("An error occurred whilst adding package " + OutputStyle.ITALIC + file.getName() + OutputStyle.DEFAULT + ". " + e.getMessage() + ". Deleting package...", error);
                e.printStackTrace();
                file.delete();
            }

スローされたエラー:

java.lang.ClassNotFoundException: com.example.MyCommands.CMD_eggtimer
at java.net.URLClassLoader$1.run(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at com.MyWebsite.MyApplication.Commands.CommandList.update(CommandList.java:71)

71行目は上記のコードでマークされています。

4

4 に答える 4

1

過去にこれを行う必要があったときは、ポイント2と3を組み合わせて使用​​しました。つまり、jarファイルをクラスパスに追加し、そこに含まれるファイルをリストして、クラスの名前を計算します。 、およびClass.forName(...)を使用してそれらをロードし、処理を実行する必要があるクラスであるかどうかを確認できるようにします。

もう1つのオプションは、jarファイルのマニフェストで、または他のメタデータメカニズムを介してクラスを識別することを要求することです。

于 2013-03-20T20:54:35.207 に答える
1

みんなの助けに感謝しますが、私は自分でそれを理解しました。

まず、操作するファイルの場所が必要です。これを使用して、jarのファイルとJarFileを作成します。

File file = new File("/location-to/file.jar");  
JarFile jar = new JarFile(file);  

必要に応じて、フォルダ内のものを反復処理することでこれを行うことができます。

URLClassLoader次に、そのファイルのを作成する必要があります。

URLClassLoader ucl = new URLClassLoader(new URL[] {file.toURI().toURL()});  

次に、Jarファイル内のすべてのクラスを繰り返し処理します。

Enumeration<JarEntry> entries = jar.entries();
while(entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();

if(!entry.getName().endsWith(".class"))
    continue;

Class<?> clazz;
try {
    clazz = ucl.loadClass(entry.getName().replace("/", ".").replace(".class", ""));
} catch(ClassNotFoundException e) {
    e.printStackTrace();
    continue;
}

if(entry.getName().contains("$1"))
    continue;

if(clazz.getSimpleName().startsWith("CMD_"))
    set.add(clazz); //Sort the classes how you like. Here I added ones beginning with 'CMD_' to a HashSet for later manipulation

ものを閉じることを忘れないでください:

jar.close();
ucl.close();

それでは、何が起こったのかを要約してみましょう。

  1. ファイルを取得しました
  2. 新しいを使用してクラスパスに追加しましたURLClassLoader
  3. jarのエントリを繰り返し処理し、必要なクラスを見つけました
  4. メモリリークを防ぐためにURLClassLoaderとを閉じましたJarFile

ハザ!

于 2013-03-24T16:36:58.657 に答える
0

クラスパスをスキャンして、特定のインターフェイスを実装しているすべてのクラスを見つけるのはかなり厄介なことのようですが、そうする方法があるようです-そこでの回答は、 Reflectionsライブラリの使用を示唆しています(私は実際にそれを使用したことがないことを前提としています)

より賢明な方法は、メタデータの編集を意味しますが、ServiceLoaderメカニズムを使用することです。

編集:あなたの投稿でReflectionsの参照を見逃しました、申し訳ありません...それでも、ServiceLoaderに関する提案は引き続き適用されます。

于 2013-03-20T20:44:20.700 に答える
0

java.class.pathクラスパスのjarを使用してプログラムを開始する場合は、システムプロパティを取得してオブジェクトを作成することにより、そのjarパスを取得できJarFileます。次に、そのオブジェクトのエントリを反復処理できます。各エントリにはパスがあります(jarに対して)。このパスを解析して、パッケージとクラス名をとして取得できますcom.package.Class。次に、を使用しClassLoaderてクラスをロードし、それを使って好きなことをします。(別のClassLoaderを使用することもできますが、以下のClassLoaderで動作します)

public class Main {
    static ClassLoader clazzLoader = Main.class.getClassLoader();

    public static void main(String [] args) {

        try {
            List<Class<?>> classes = new LinkedList<>();

            // do whatever transformation you need to get the jar file
            String classpath = System.getProperty("java.class.path").split(";")[1];

            // I'm just checking if it's the right jar file
            System.out.println(classpath);

            // get the file and make a JarFile out of it
            File bin = new File(classpath);
            JarFile jar = new JarFile(bin);

            // get all entries in the jar file
            Enumeration<JarEntry> entries = jar.entries();

            // iterate over jarfile entries
            while(entries.hasMoreElements()) {
                try {
                    JarEntry entry = entries.nextElement();

                    // skip other files in the jar
                    if (!entry.getName().endsWith(".class"))
                        continue;

                    // extract the class name from its URL style name
                    String className = entry.getName().replace("/", ".").replace(".class", "");

                    // load the class
                    Class<?> clazz = clazzLoader.loadClass(className);

                    // use your Command class or any other class you want to match
                    if (Comparable.class.isAssignableFrom(clazz))
                        classes.add(clazz);
                } catch (ClassNotFoundException e) {
                    //ignore
                }
            }

            for (Class<?> clazz : classes) {
                System.out.println(clazz);
                clazz.newInstance(); // or whatever
            }   

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Reflections私はこれを内部クラスで試しましたが、それも機能します。ライブラリがどのような解析を行うのかわかりません。

また、このようなものが機能するために完全なジャーは必要ありません。ファイルを含むbinフォルダーがある場合は、.classそのファイル/フォルダーを再帰的に調べ、クラスファイルを適切な形式に解析して、上記のようにロードできます。

新しいコードで編集する、いくつかのこと

  1. jar.close()反復している間は、ループ内でjarを閉じないでください。
  2. これif(clazz.getName().startsWith("CMD_"))はあなたが望むことをしていません。このgetName()メソッドは、パッケージを含むクラスのフルパス名を返します。使用するgetSimpleName()
  3. を取得している場合ClassNotFoundException、それはそのクラスがクラスパス上にないためです。これは、クラスパス上のjarファイルを使用してプログラムを実行していないことを示しています。クラスをロードするときは、zip/jarファイルを開いてクラスファイルを「ロード」するだけでは不十分です。あなたは次のようにあなたのプログラムを実行する必要があります

    java -classpath <your_jars> com.your.class.Main

    または、Eclipse(または他のIDE)を使用して、ビルドパスにjarを追加します。

クラスパスエントリを使用してJavaを実行する方法の詳細については、これを参照してください。

于 2013-03-20T21:26:46.853 に答える