19 January 2010

Java и паттерн Public Morozov

Java
Однажды понадобилось мне переопределить на работающей программе поле, помеченное как private final. Причем останавливать программу было нельзя, ибо сервер. Ну и как маленькое дополнение тип переменной был определен как inner класс. Разумеется тоже private.

К счастью, программа позволяет на ходу подключать модули, содержащие произвольный код. А значит — в нашем распоряжении вся мощь reflection!

Напоминаю, что это proof-of-concept, поэтому для конкретной задачи придется как минимум установить нужный тип для accessor, и вообще при выдирании кода из контекста некоторые блоки могут потерять смысл. Например в приведенном примере всё будет работать и без замены accessor вообще, достаточно будет снять final. Следует учитывать, что это будет гарантированно работать только для объектов. Примитивы обычно инлайнятся компилятором.

package main;

class PublicMorozov
{
// заготавливаем инстанс, для которого мы будем создавать экземпляр inner класса
private static final PublicMorozov INSTANCE = new PublicMorozov();
// и поле, содержимое которого и будем заменять
private static final java.lang.ref.WeakReference<Inner> targetField = new java.lang.ref.WeakReference<Inner>(null);

private class Inner
{}

public PublicMorozov()
{}

public static void makeReplace() throws Exception
{
// получаем через рефлект поле, которое предстоит заменить
java.lang.reflect.Field targetAsField = Class.forName("main.PublicMorozov").getDeclaredField("targetField");
// снимаем с него private
targetAsField.setAccessible(true);

// получаем адрес поля модификаторов в целевом поле
java.lang.reflect.Field modifiers = Class.forName("java.lang.reflect.Field").getDeclaredField("modifiers");
// снимаем с него private
modifiers.setAccessible(true);

// снимаем с целевого поля private и final, а вместо них ставим public
modifiers.setInt(targetAsField, targetAsField.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
modifiers.setInt(targetAsField, targetAsField.getModifiers() & ~java.lang.reflect.Modifier.PRIVATE);
modifiers.setInt(targetAsField, targetAsField.getModifiers() | java.lang.reflect.Modifier.PUBLIC);

// но всё не так просто... если попытаться применить изменения то нас может отправить куда подальше с IllegalAccessException
// поэтому мы заменяем accessor на свой, которому будет пофиг на финал
// мы используем именно overrideFieldAccessor поскольку поле изначально было private, в противном случае следует использовать fieldAccessor
java.lang.reflect.Field accessorField = Class.forName("java.lang.reflect.Field").getDeclaredField("overrideFieldAccessor");
// как обычно снимаем с него private
accessorField.setAccessible(true);
// поскольку мы заменяем статический Object нам нужен именно этот тип, их много под разные типы полей и данных
java.lang.reflect.Constructor accessorConstructor = Class.forName("sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl").getDeclaredConstructor(java.lang.reflect.Field.class, boolean.class);
// конструктор тоже сокрыт... но разве нас этим испугаешь?
accessorConstructor.setAccessible(true);
// вот теперь всё нормально - новый accessor на поле final и смотреть не будет
accessorField.set(targetAsField, accessorConstructor.newInstance(targetAsField, false));

// и на десерт - доступ к inner классу
java.lang.reflect.Constructor innerConstructor = Class.forName("main.PublicMorozov$Inner").getDeclaredConstructor(Class.forName("main.PublicMorozov"));
innerConstructor.setAccessible(true);

// заменяем таки содержимое нужного поля
targetAsField.set(null, new java.lang.ref.WeakReference<Inner>((Inner) innerConstructor.newInstance(INSTANCE)));
}
}


* This source code was highlighted with Source Code Highlighter.
Tags:javajava.lang.reflectpublic morozov
Hubs: Java
+37
26.4k 42
Comments 25
Ads