IllegalAccessException при использовании отражения
Я пытался научиться размышлению, и я наткнулся на это исключение IllegalAccessException. См. Следующий код:
public class ReflectionTest
{
public static void main(String[] args)
{
Set<String> myStr = new HashSet<String>();
myStr.add("obj1");
Iterator itr = myStr.iterator();
Method mtd = itr.getClass().getMethod("hasNext");
System.out.println(m.invoke(it));
}
}
Когда я попытался запустить эту программу, я получил следующее:
Exception in thread "main" IllegalAccessException
Я не понимаю, что происходит. Есть идеи? Спасибо заранее.
Ответы
Ответ 1
Вам необходимо отключить проверку доступа к языку Java, чтобы отразить вызов частного метода в другом классе с помощью setAccessible (true):
Method mtd= itr.getClass().getMethod("hasNext");
if(!mtd.isAccessible()) {
mtd.setAccessible(true);
}
Кроме того, когда включен SecurityManager, нам нужны дополнительные разрешения для вызова setAccessible (true). В противном случае получаем:
C:\ReflectionTest>java -Djava.security.manager CallFoo
Exception in thread "main" java.security.AccessControlException: access denied (java.lang.reflect.ReflectPermission suppressAccessChecks)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
at java.security.AccessController.checkPermission(AccessController.java:427)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.reflect.AccessibleObject.setAccessible(AccessibleObject.java:107)
at CallFoo.main(CallFoo.java:8)
Мы только хотим предоставить этому suppressAccessChecks права на источник доверенного кода, определенно не для всех классов в стеке вызовов. Поэтому мы изменили бы CallFoo.java:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
public class CallFoo {
public static void main(String args[]) throws Exception {
doCallFoo();
}
public static void doCallFoo() throws IllegalAccessException, ClassNotFoundException, NoSuchMethodException,
InvocationTargetException, InstantiationException, PrivilegedActionException {
Class fooClass = Class.forName("Foo");
final Foo foo = (Foo) fooClass.newInstance();
final Method helloMethod = fooClass.getDeclaredMethod("hello");
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
if(!helloMethod.isAccessible()) {
helloMethod.setAccessible(true);
}
helloMethod.invoke(foo);
return null;
}
});
}
}
Ответ 2
Трудный фрагмент кода таков:
itr.getClass().getMethod
Вероятно, вы хотели hasNext
в классе Iterator
. То, что вы написали, это класс HashMap.KeyIterator
, который, согласно спецификациям доступа к языку Java (или, по крайней мере, грубая интерпретация JDK 1.0, используемого при отражении), недоступен для вашего кода.
Используйте вместо этого:
Iterator.class.getMethod
(И если это было не для учебных целей, держитесь подальше от отражения.)
Ответ 3
Вы не можете получить к нему доступ, потому что Iterator является частным внутренним классом. Более подробное описание можно найти здесь.
Ответ 4
Очевидно, что ваш текущий исполняемый метод не имеет доступа к методу с именем hasNext
, например: private
или protected
. Вы можете попытаться включить доступ к нему с помощью method.setAccessible(true);
Возможно также, что у вас есть ограничения, определенные в вашем security manager
(который, если вы используете, например, linux
, может были включены по умолчанию из дистрибутива java-пакета).
[EDIT] Как выясняется, Том Хотин определил правильную причину. Вы действительно работаете на HashMap.KeyIterator
. Хотя решение было бы использовать Iterator.class
вместо itr.getClass()
, вы все равно могли бы разрешить доступ к нему с помощью setAccessible(true)
.
Ответ 5
Я подозреваю, что вы должны использовать getDeclaredMethod (среди других проблем). Я не удосужился вспомнить подробности API Reflection (они для компилятора!), Но в вашем случае сравните свой код с кодом, созданным dp4j:
$ javac -Averbose=true -All -cp dp4j-1.2-SNAPSHOT-jar-with-dependencies.jar ReflectionTest.java
ReflectionTest.java:6: Note:
import java.util.*;
public class ReflectionTest {
public ReflectionTest() {
super();
}
@com.dp4j.Reflect()
public static void main(String[] args) throws java.lang.ClassNotFoundException, java.lang.NoSuchFieldException, java.lang.IllegalAccessException, java.lang.NoSuchMethodException, java.lang.reflect.InvocationTargetException, java.lang.InstantiationException, java.lang.IllegalArgumentException {
final java.lang.reflect.Constructor hashSetConstructor = Class.forName("java.util.HashSet").getDeclaredConstructor();
hashSetConstructor.setAccessible(true);
Set<String> myStr = (.java.util.Set<.java.lang.String>)hashSetConstructor.newInstance();
final java.lang.reflect.Method addWithEMethod = Class.forName("java.util.Set").getDeclaredMethod("add", .java.lang.Object.class);
addWithEMethod.setAccessible(true);
addWithEMethod.invoke(myStr, new .java.lang.Object[1][]{"obj1"});
final java.lang.reflect.Method iteratorMethod = Class.forName("java.util.Set").getDeclaredMethod("iterator");
iteratorMethod.setAccessible(true);
Iterator itr = (.java.util.Iterator)iteratorMethod.invoke(myStr);
final java.lang.reflect.Method hasNextMethod = Class.forName("java.util.Iterator").getDeclaredMethod("hasNext");
hasNextMethod.setAccessible(true);
final java.lang.reflect.Method printlnWithbooleanMethod = Class.forName("java.io.PrintStream").getDeclaredMethod("println", .java.lang.Boolean.TYPE);
printlnWithbooleanMethod.setAccessible(true);
printlnWithbooleanMethod.invoke(System.out, new .java.lang.Object[1][]{hasNextMethod.invoke(itr)});
}
}
public static void main(String[] args)
^
...
$ java ReflectionTest
true
Единственное изменение, которое вам нужно сделать, это аннотировать ваш основной метод с помощью @com.dp4j.Reflect:
$ vim ReflectionTest.java
import java.util.*;
public class ReflectionTest
{
@com.dp4j.Reflect
public static void main(String[] args)
{
Set<String> myStr = new HashSet<String>();
myStr.add("obj1");
Iterator itr = myStr.iterator();
// Method mtd = itr.getClass().getMethod("hasNext");
System.out.println(itr.hasNext());
}
}
NB: это работает только с dp4j-1.2-SNAPSHOT (я только что добавил для него поддержку). Если вы не используете Maven, загрузите банку из здесь. Вы найдете тестовый пример с вашей проблемой здесь.