16

Formulaパッケージにあるクラスがありjavaapplication4、URLClassLoaderを使用してロードします。ただし、同じパッケージにある別のクラスから呼び出すとTest1、デフォルトのアクセス修飾子を持つメソッドにアクセスできません(パブリックメソッドにアクセスできます)。

次の例外が発生します。

java.lang.IllegalAccessException:クラスjavaapplication4.Test1は、修飾子""を使用してクラスjavaapplication4.Formulaのメンバーにアクセスできません。

同じパッケージから実行時にロードされたクラスのパッケージプライベートメソッドにアクセスするにはどうすればよいですか?

別のクラスローダーを使用することに問題があると思いますが、理由はわかりません(URLClassLoaderの親を設定しました)。

問題を再現するSSCCE(Windowsパス)-問題はloadClassメソッドにあると思います:

public class Test1 {

    private static final Path TEMP_PATH = Paths.get("C:/temp/");

    public static void main(String[] args) throws Exception {
        String thisPackage = Test1.class.getPackage().getName();
        String className = thisPackage + ".Formula"; //javaapplication4.Formula
        String body = "package " + thisPackage + ";   "
                    + "public class Formula {         "
                    + "    double calculateFails() {  "
                    + "        return 123;            "
                    + "    }                          "
                    + "    public double calculate() {"
                    + "        return 123;            "
                    + "    }                          "
                    + "}                              ";

        compile(className, body, TEMP_PATH);
        Class<?> formulaClass = loadClass(className, TEMP_PATH);

        Method calculate = formulaClass.getDeclaredMethod("calculate");
        double value = (double) calculate.invoke(formulaClass.newInstance());
        //next line prints 123
        System.out.println("value = " + value);

        Method calculateFails = formulaClass.getDeclaredMethod("calculateFails");
        //next line throws exception:
        double valueFails = (double) calculateFails.invoke(formulaClass.newInstance());
        System.out.println("valueFails = " + valueFails);
    }

    private static Class<?> loadClass(String className, Path path) throws Exception {
        URLClassLoader loader = new URLClassLoader(new URL[]{path.toUri().toURL()}, Test1.class.getClassLoader());
        return loader.loadClass(className);
    }

    private static void compile(String className, String body, Path path) throws Exception {
        List<JavaSourceFromString> sourceCode = Arrays.asList(new JavaSourceFromString(className, body));

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(path.toFile()));
        boolean ok = compiler.getTask(null, fileManager, null, null, null, sourceCode).call();

        System.out.println("compilation ok = " + ok);
    }

    public static class JavaSourceFromString extends SimpleJavaFileObject {
        final String code;

        JavaSourceFromString(String name, String code) {
            super(URI.create("string:///" + name.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension),
                    JavaFileObject.Kind.SOURCE);
            this.code = code;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return code;
        }
    }
}
4

4 に答える 4

9

実行時のクラスは、完全修飾名とClassLoaderの両方で識別されます。

たとえば、2つのClass<T>オブジェクトが等しいかどうかをテストするときに、それらが同じ正規名を持っているが、異なるClassLoaderからロードされた場合、それらは等しくなりません。

2つのクラスが同じパッケージに属する(そしてパッケージプライベートメソッドにアクセスできるようにする)には、同じClassLoaderからもロードする必要がありますが、ここではそうではありません。実際Test1、Formulaは内部で作成されたURLClassLoaderによってロードされますが、FormulaはシステムクラスローダーによってロードされloadClass()ます。

URLClassLoaderをロードするために親ローダーを指定した場合Test1でも、2つの異なるローダーが使用されます(ローダーの同等性をアサートすることで確認できます)。

Formula同じClassLoaderでクラスをロードすることはできないと思いますがTest1(既知のパスを使用してCLASSPATHに配置する必要があります)、逆の方法を見つけましたTest1。式のロードに使用されるClassLoader。これは、擬似コードのレイアウトです。

class Test1 {

  public static void main(String... args) {
    loadClass(formula);
  }

  static void loadClass(location) {
    ClassLoader loader = new ClassLoader();
    Class formula = loader.load(location);
    Class test1 = loader.load(Test1);
    // ...
    Method compute = test1.getMethod("compute");
    compute.invoke(test1, formula);
  }

  static void compute(formula) {
    print formula;
  }
}

これがペーストビンです。いくつかの注意事項:null上記の問題を回避するためにURLClassLoaderの親を指定し、目的を達成するために文字列を操作しましたが、このアプローチが他のデプロイメントシナリオでどれほど堅牢であるかはわかりません。また、私が使用したURLCLassLoaderは、クラスパスにリストされているすべてのエントリではなく、クラス定義を見つけるために2つのディレクトリを検索するだけでした。

于 2013-01-11T17:00:35.200 に答える
8

答えは次のとおりです。

sun.reflect.Reflectionパッケージには、と呼ばれるメソッドがあります(isSameClassPackage実際の署名は private static boolean isSameClassPackage(ClassLoader arg0, String arg1, ClassLoader arg2, String arg3);)。このメソッドは、2つのクラスが同じパッケージに属しているかどうかを判断する役割を果たします。

このメソッドが行う最初のチェックでは、arg0とarg2(2つのクラスローダー)が異なる場合は、falseを返します。

したがって、2つのクラスに異なるクラスローダーを使用すると、一致しなくなります。

編集:完全なコールチェーン(要求に応じて)は次のとおりです:

Method.invoke()
Method.checkAccess() -- fallback to the real check
Method.slowCheckMemberAccess()   -- first thing to do to call
Reflection.ensureMemberAccess()  -- check some nulls, then
Reflection.verifyMemberAccess()  -- if public, it,'s OK, otherwise check further
Reflection.isSameClassPackage(Class, Class) -- get the class loaders of 2 classes
Reflection.isSameClassPackage(ClassLoader, String, ClassLoader, String) 
于 2013-01-11T17:38:30.300 に答える
3

私はJVM仕様5.4.4(私の強調)で説明を見つけました:

フィールドまたはメソッドRは、次の条件のいずれかが当てはまる場合にのみ、クラスまたはインターフェイスDにアクセスできます。

  • [...]
  • Rは保護されているか、デフォルトのアクセス権を持っており(つまり、パブリックでもプロテクトでもプライベートでもありません)、Dと同じランタイムパッケージのクラスによって宣言されます。

そして、ランタイムパッケージは仕様#5.3で定義されています:

クラスまたはインターフェイスのランタイムパッケージは、パッケージ名とクラスまたはインターフェイスの定義クラスローダーによって決定されます。

結論:これは予想される動作です。

于 2013-01-11T17:47:09.000 に答える
1

Javaクラスパスに追加c:\tempし、Test1.classと同じClassLoaderを使用してFormula.classをロードします。

Class<?> formulaClass = Class.forName(className);

これはあなたの問題を解決します。

于 2013-01-12T01:57:30.817 に答える