Saturday, November 24, 2007

Обращение к private-элементам в Java (Java Reflection)

Недавно у нас с Ивом состоялся разговор о private-элементах Java (приватные классы, поля и т.д.)
Он был уверен, что если класс объявлен как приватный - его экземпляр нельзя создать где-либо еще, кроме как из класса, в котором он был объявлен. То же самое с приватными полями - если поле описано с модификатором доступа private, то к нему нельзя получить доступ если нет соответствующих getter/setter-методов.

На самом деле, механизм Java Reflection позволяет сделать с кодом все, что угодно и получить доступ к любой информации о коде и к состоянию объектов.

PublicClass.java

01
02 public class PublicClass {
03
04
/**
05
* Значение этой переменной нельзя непосредственно установить из Java-кода
06 * оператором присваивания, но, как будет видно дальше, это значение можно
07 * изменить при помощи механизма рефлексии.
08 */
09 private int a = 0;
10
11 public int getA() {
12 return a;
13 }
14
15 /**
16 * Это приватный класс. Дальше в примерах мы создадим экземпляр этого класса
17 * и вызовем на нем метод <code>toString()</code>.
18 *
19 * Этот класс может существовать только в контексте экземпляра
20 * {@link PublicClass}; мы увидим, как можно установить контекст для нового
21 * экземпляра {@link PrivateInnerClass}.
22 *
23 * @author dmitrygusev
24 *
25 */
26 @SuppressWarnings("unused")
27 private class PrivateInnerClass {
28 @Override
29 public String toString() {
30 return "Hello from PrivateInnerClass instance!";
31 }
32 }
33
34 }


TestAccessPrivateMembers.java

01 import static org.junit.Assert.assertEquals;
02
03 import java.lang.reflect.Constructor;
04 import java.lang.reflect.Field;
05 import java.lang.reflect.Member;
06
07 import org.junit.Test;
08
09 public class TestAccessPrivateMembers {
10
11 /**
12 * Получим список объявленных конструкторов. Методы
13 * <code>java.lang.Class.getDeclaredXXX()</code>, например,
14 * {@link Class#getDeclaredConstructors()}, возвращают список всех
15 * объявленных элементов ({@link Member}), а не только
16 * <code>public</code>.
17 *
18 * Если нужно получить только <code>public</code> элементы, то можно
19 * воспользоваться методами <code>java.lang.Class.getYYY()</code>
20 * (например, {@link Class#getConstructors()}).
21 *
22 * @see java.lang.reflect.Member
23 * @see java.lang.Class
24 * @see java.lang.reflect.Field
25 * @see java.lang.reflect.Method
26 * @see java.lang.reflect.Constructor
27 */
28 @Test public void accessPrivateMembers() throws Exception {
29 /*
30 * В данном случае у нас лишь один inner-класс - PrivateInnerClass
31 */
32 Class<?> clazz = PublicClass.class.getDeclaredClasses()[0];
33
34 /*
35 * Получим его конктруктор по умолчанию
36 */
37 Constructor<?> constr = clazz.getDeclaredConstructors()[0];
38
39 /*
40 * Установим признак доступности этого конструктора, чтобы его можно
41 * было вызвать
42 */
43 constr.setAccessible(true);
44
45 /*
46 * Создадим класс-контекст для внутреннего класса
47 */
48 PublicClass pc = new PublicClass();
49
50 /*
51 * Создадим экземпляр внутреннего класса, передав класс-контекст в
52 * качестве параметра конструктору
53 */
54 Object o = constr.newInstance(pc);
55
56 /*
57 * Вызовем метод toString() на внутреннем классе
58 */
59 assertEquals("Hello from PrivateInnerClass instance!", o.toString());
60
61 /*
62 * Прочитаем значение private-переменной через соответствующий
63 * getter-метод
64 */
65 assertEquals(0, pc.getA());
66
67 /*
68 * Изменим значение этой private-переменной. Для этого нужно получить
69 * соответствующий объект java.lang.reflect.Field, у которого есть метод
70 * Field.set(object, value).
71 */
72 Field fieldA = pc.getClass().getDeclaredField("a");
73
74 fieldA.setAccessible(true);
75
76 fieldA.set(pc, 2);
77
78 /*
79 * Прочитаем значение private-поля - оно должно быть равно 2
80 */
81 assertEquals(2, pc.getA());
82 }
83
84 }

4 comments:

  1. Не знал про setAccessible. Признаю.

    Но тем не менее настаиваю на своем, в том плане, что без вырубки грубой физической силой механизма контроля доступа к элементам классов (http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/AccessibleObject.html), получить доступ к приватным элементам из вне - невозможно. Даже с рефлексией.

    Тут просто обошли систему стороной. Как если бы я решил написать свой собственный java с только мне известными правилами :-)

    ReplyDelete
  2. Из javadoc'а:

    Setting the accessible flag in a reflected object permits sophisticated applications with sufficient privilege, such as Java Object Serialization or other persistence mechanisms, to manipulate objects in a manner that would normally be prohibited.

    То есть тут специально обошли систему стороной - оставили лазейку, чтобы можно было поддерживать разные низкоуровневые подсистемы, которые работают не с моделью предметной области, а с кодом, как, например, механизм сериализации или enhancement классов в JDO

    ReplyDelete
  3. Ты об этой лазейке знал *сознательно* когда спорил или обнаружил уже после?

    И опять же, как истинный ООП последователь, не согласен с подобным подходом. Думать о видимости надо сразу, а не потом только потому что так удобнее. Хотя... Всякое наверное бывает.

    ReplyDelete
  4. > Ты об этой лазейке знал *сознательно* когда спорил или обнаружил уже после?

    Конечно знал :)

    ReplyDelete