Будь-який спосіб викликати приватний метод?


146

У мене є клас, який використовує XML та відображення для повернення Objects до іншого класу.

Зазвичай ці об'єкти є підполями зовнішнього об'єкта, але іноді це щось, що я хочу генерувати на льоту. Я спробував щось подібне, але безрезультатно. Я вважаю, що це тому, що Java не дозволить вам отримати доступ до privateметодів рефлексії.

Element node = outerNode.item(0);
String methodName = node.getAttribute("method");
String objectName = node.getAttribute("object");

if ("SomeObject".equals(objectName))
    object = someObject;
else
    object = this;

method = object.getClass().getMethod(methodName, (Class[]) null);

Якщо запропонований метод є private, він не працює з a NoSuchMethodException. Я міг би вирішити це, зробивши метод public, або створивши інший клас, щоб отримати його.

Якщо коротко розповісти, мені було просто цікаво, чи є спосіб отримати доступ до privateметоду через рефлексію.

Відповіді:


303

Ви можете викликати приватний метод із відображенням. Змінення останнього біта опублікованого коду:

Method method = object.getClass().getDeclaredMethod(methodName);
method.setAccessible(true);
Object r = method.invoke(object);

Є пара застережень. По-перше, getDeclaredMethodзнайдемо лише метод, оголошений у поточному Class, не успадкований від суперпертипів. Отож, перекресліть ієрархію конкретного класу, якщо це необхідно. По-друге, SecurityManagerможе запобігти використанню setAccessibleметоду. Отже, може знадобитися запуск як PrivilegedAction(за допомогою AccessControllerабо Subject).


2
коли я це робив у минулому, я також називав method.setAccessible (помилковим) після виклику методу, але я не маю уявлення, чи потрібно це чи ні.
shsteimer

16
Ні, коли ви встановлюєте доступність, це стосується лише цього екземпляра. Поки ви не дозволяєте цьому конкретному методу вийти з-під свого контролю, це безпечно.
erickson

6
Тож який сенс мати приватні методи, якщо їх можна викликати поза класом?
Пітер Айтай

2
Також переконайтеся, що ви телефонуєте getDeclaredMethod()замість просто getMethod()- це не працюватиме для приватних методів.
Ерксен

1
@PeterAjtai Вибачте за несвоєчасну відповідь, але думайте про це так: Більшість людей сьогодні замикають свої двері, хоча вони знають, що замок можна тривіально зламати або взагалі обійти. Чому? Тому що це допомагає чесним здебільшого бути чесними. Можна подумати про privateдоступ, що грає подібну роль.
erickson

34

Використовуйте getDeclaredMethod()для отримання приватного об'єкта Method, а потім використовуйте, method.setAccessible()щоб дозволити насправді викликати його.


У власному прикладі ( stackoverflow.com/a/15612040/257233 ) я отримую цифру,java.lang.StackOverflowError якщо не дзвоню setAccessible(true).
Роберт Марк Брам

25

Якщо метод приймає непомітний тип даних, то для виклику приватного методу будь-якого класу можна використовувати наступний метод:

public static Object genericInvokeMethod(Object obj, String methodName,
            Object... params) {
        int paramCount = params.length;
        Method method;
        Object requiredObj = null;
        Class<?>[] classArray = new Class<?>[paramCount];
        for (int i = 0; i < paramCount; i++) {
            classArray[i] = params[i].getClass();
        }
        try {
            method = obj.getClass().getDeclaredMethod(methodName, classArray);
            method.setAccessible(true);
            requiredObj = method.invoke(obj, params);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        return requiredObj;
    }

Параметри, які приймаються, - це obj, methodName та параметри. Наприклад

public class Test {
private String concatString(String a, String b) {
    return (a+b);
}
}

Метод concatString можна викликати як

Test t = new Test();
    String str = (String) genericInvokeMethod(t, "concatString", "Hello", "Mr.x");

7
Для чого потрібен paramCount ? Ви не можете просто використовувати params.length ?
Саад Малик

7

це можна зробити за допомогою ReflectionTestUtils Spring ( org.springframework.test.util.ReflectionTestUtils )

ReflectionTestUtils.invokeMethod(instantiatedObject,"methodName",argument);

Приклад: якщо у вас є клас із приватним методом square(int x)

Calculator calculator = new Calculator();
ReflectionTestUtils.invokeMethod(calculator,"square",10);

Існує також більш обмежена ReflectionUtils в основній Spring.
Громовержець

3

Дозвольте надати повний код для методів, захищених виконанням за допомогою відображення. Він підтримує будь-які типи парам, включаючи дженерики, автокопіровані парами та нульові значення

@SuppressWarnings("unchecked")
public static <T> T executeSuperMethod(Object instance, String methodName, Object... params) throws Exception {
    return executeMethod(instance.getClass().getSuperclass(), instance, methodName, params);
}

public static <T> T executeMethod(Object instance, String methodName, Object... params) throws Exception {
    return executeMethod(instance.getClass(), instance, methodName, params);
}

@SuppressWarnings("unchecked")
public static <T> T executeMethod(Class clazz, Object instance, String methodName, Object... params) throws Exception {

    Method[] allMethods = clazz.getDeclaredMethods();

    if (allMethods != null && allMethods.length > 0) {

        Class[] paramClasses = Arrays.stream(params).map(p -> p != null ? p.getClass() : null).toArray(Class[]::new);

        for (Method method : allMethods) {
            String currentMethodName = method.getName();
            if (!currentMethodName.equals(methodName)) {
                continue;
            }
            Type[] pTypes = method.getParameterTypes();
            if (pTypes.length == paramClasses.length) {
                boolean goodMethod = true;
                int i = 0;
                for (Type pType : pTypes) {
                    if (!ClassUtils.isAssignable(paramClasses[i++], (Class<?>) pType)) {
                        goodMethod = false;
                        break;
                    }
                }
                if (goodMethod) {
                    method.setAccessible(true);
                    return (T) method.invoke(instance, params);
                }
            }
        }

        throw new MethodNotFoundException("There are no methods found with name " + methodName + " and params " +
            Arrays.toString(paramClasses));
    }

    throw new MethodNotFoundException("There are no methods found with name " + methodName);
}

Метод використовує apache ClassUtils для перевірки сумісності автозавантажених парам


Немає сенсу відповідати на 9-річне запитання з більш ніж 90000 переглядами та прийнятою відповіддю. Натомість відповідайте на запитання без відповіді.
Anuraag Baishya

0

Ще один варіант - використання дуже потужної бібліотеки JOOR https://github.com/jOOQ/jOOR

MyObject myObject = new MyObject()
on(myObject).get("privateField");  

Це дозволяє змінювати будь-які поля, такі як кінцеві статичні константи, і викликати захищені методи yne, не вказуючи конкретний клас в ієрархії спадкування

<!-- https://mvnrepository.com/artifact/org.jooq/joor-java-8 -->
<dependency>
     <groupId>org.jooq</groupId>
     <artifactId>joor-java-8</artifactId>
     <version>0.9.7</version>
</dependency>

0

Ви можете використовувати КОЛЛЕКТОР в @Jailbreak для прямого тіпобезопасного Java відображення:

@Jailbreak Foo foo = new Foo();
foo.callMe();

public class Foo {
    private void callMe();
}

@Jailbreakрозблокує fooлокальну змінну в компіляторі для прямого доступу до всіх членів в Fooієрархії.

Аналогічно ви можете використовувати метод розширення jailbreak () для разового використання:

foo.jailbreak().callMe();

За допомогою jailbreak()методу ви можете отримати доступ до будь-якого члена в Fooієрархії Росії.

В обох випадках компілятор вирішує виклик методу для вас безпечно набирає текст, як якщо б відкритий метод, тоді як Manifold генерує для вас ефективний код відображення під кришкою.

Крім того, якщо тип статично не відомий, ви можете скористатися структурним набором тексту для визначення інтерфейсу, яким тип може задовольнити, не заявляючи про його реалізацію. Ця стратегія підтримує безпеку типу та уникає проблем із продуктивністю та ідентичністю, пов’язаними з відображенням та проксі-кодом.

Дізнайтеся більше про різноманітті .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.