[英]URLClassLoader and accessibility of package-private methods
我有一个类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;
}
}
}
运行时的类由其完全限定名称及其ClassLoader标识。
例如,当您测试两个Class<T>
对象是否相等时,如果它们具有相同的规范名称但是从不同的ClassLoader加载,则它们将不相等。
对于属于同一个包的两个类(并且反过来能够访问包私有方法),它们也需要从同一个ClassLoader加载,这不是这里的情况。 实际上, Test1
由系统类加载器加载,而公式由loadClass()
内部创建的URLClassLoader加载。
如果为URLClassLoader指定父加载器以使其加载Test1
,则仍然使用两个不同的加载器(您可以通过断言加载器相等来检查它)。
我不认为你可以通过相同的Test1
ClassLoader加载Formula
类(你必须使用一个众所周知的路径并把它放在CLASSPATH上),但我发现了一种相反的方法:加载另一个实例用于加载公式的ClassLoader中的Test1
。 这是伪代码的布局:
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;
}
}
这是pastebin 。 几个注意事项:我为URLClassLoader指定了一个null
父,以避免上面列出的问题,我操纵字符串来实现目的 - 但不知道这种方法在其他部署方案中有多强大。 另外,我使用的URLCLassLoader只搜索两个目录来查找类定义,而不是CLASSPATH中列出的所有条目
答案是:
在sun.reflect.Reflection
包有一个名为方法isSameClassPackage
(实际签名是private static boolean isSameClassPackage(ClassLoader arg0, String arg1, ClassLoader arg2, String arg3);
)。 此方法负责决定两个类是否属于同一个包。
第一次检查这个方法是否比较了arg0和arg2,(两个类加载器)如果它们不同,则返回false。
因此,如果您为这两个类使用不同的类加载器,则它将不匹配。
编辑:完整的调用链(根据要求)是:
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)
我在JVM规范5.4.4 (强调我的)中找到了解释:
当且仅当满足以下任一条件时,才能访问类或接口D的字段或方法R.
- [...]
- R受保护或具有默认访问权(即既不公开也不受保护也不是私有),并且由与D 相同的运行时包中的类声明。
运行时包在规范#5.3中定义:
类或接口的运行时包由包名称和类或接口的类加载器定义 。
底线:这是预期的行为。
将c:\\temp
添加到java类路径并使用与Test1.class相同的ClassLoader加载Formula.class
Class<?> formulaClass = Class.forName(className);
这将解决您的问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.