Jakiś sposób na wywołanie metody prywatnej?


146

Mam klasę, która używa XML i odbicia, aby zwrócić Objects do innej klasy.

Zwykle te obiekty są polami podrzędnymi obiektu zewnętrznego, ale czasami jest to coś, co chcę wygenerować w locie. Próbowałem czegoś takiego, ale bezskutecznie. Uważam, że dzieje się tak, ponieważ Java nie pozwala na dostęp do privatemetod refleksji.

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);

Jeśli podana metoda to private, zakończy się niepowodzeniem z plikiem NoSuchMethodException. Mógłbym to rozwiązać, tworząc metodę publiclub inną klasę, z której będzie pochodziła.

Krótko mówiąc, zastanawiałem się, czy istnieje sposób na dostęp do privatemetody poprzez refleksję.

Odpowiedzi:


303

Możesz wywołać metodę prywatną z odbiciem. Modyfikacja ostatniego bitu opublikowanego kodu:

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

Jest kilka zastrzeżeń. Po pierwsze, getDeclaredMethodznajdzie tylko metodę zadeklarowaną w bieżącej Class, a nie dziedziczoną z nadtypów. W razie potrzeby przejrzyj więc konkretną hierarchię klas. Po drugie, SecurityManagermoże uniemożliwić stosowanie tej setAccessiblemetody. Może więc wymagać uruchomienia jako PrivilegedAction(używając AccessControllerlub Subject).


2
kiedy robiłem to w przeszłości, po wywołaniu metody wywołałem również metodę method.setAccessible (false), ale nie mam pojęcia, czy jest to konieczne, czy nie.
shsteimer

16
Nie, kiedy ustawisz dostępność, ma to zastosowanie tylko do tej instancji. Dopóki nie pozwolisz, aby ten konkretny obiekt Method wymknął się spod Twojej kontroli, jest to bezpieczne.
erickson

6
Więc jaki jest sens posiadania metod prywatnych, skoro można je wywołać spoza klasy?
Peter Ajtai,

2
Upewnij się również, że dzwonisz getDeclaredMethod()zamiast tylko getMethod()- to nie zadziała w przypadku metod prywatnych.
Ercksen

1
@PeterAjtai Przepraszamy za spóźnioną odpowiedź, ale pomyśl o tym w ten sposób: większość ludzi obecnie zamyka drzwi na klucz, mimo że wiedzą, że zamek można w trywialny sposób wyłamać lub w ogóle obejść. Czemu? Ponieważ pomaga to zachować uczciwość przeważnie uczciwych ludzi. Możesz pomyśleć o privatedostępie odgrywającym podobną rolę.
erickson,


25

Jeśli metoda akceptuje inny niż pierwotny typ danych, do wywołania metody prywatnej dowolnej klasy można użyć następującej metody:

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;
    }

Akceptowanymi parametrami są obj, nazwa metody i parametry. Na przykład

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

Metoda concatString może zostać wywołana jako

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

7
Dlaczego potrzebny jest paramCount ? Nie możesz po prostu użyć params.length ?
Saad Malik


3

Pozwól, że dostarczę kompletny kod dla metod chronionych przed wykonaniem za pomocą odbicia. Obsługuje dowolne typy parametrów, w tym typy ogólne, parametry z automatyczną skrzynką i wartości null

@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);
}

Metoda używa klasy Apache ClassUtils do sprawdzania zgodności parametrów z automatycznym blokowaniem


Nie ma absolutnie sensu odpowiadać na pytanie od 9 lat, które ma ponad 90000 wyświetleń i zaakceptowaną odpowiedź. Zamiast tego odpowiedz na pytania bez odpowiedzi.
Anuraag Baishya

0

Jeszcze jeden wariant wykorzystuje bardzo potężną bibliotekę JOOR https://github.com/jOOQ/jOOR

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

Pozwala modyfikować dowolne pola, takie jak końcowe stałe statyczne i wywoływać metody chronione bez określania konkretnej klasy w hierarchii dziedziczenia

<!-- 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

Możesz użyć @Jailbreak Manifolda do bezpośredniego, bezpiecznego dla typu odzwierciedlenia Java:

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

public class Foo {
    private void callMe();
}

@Jailbreakodblokowuje foozmienną lokalną w kompilatorze w celu bezpośredniego dostępu do wszystkich członków w Foohierarchii.

Podobnie możesz użyć metody rozszerzenia jailbreak () do jednorazowego użycia:

foo.jailbreak().callMe();

Za pomocą tej jailbreak()metody można uzyskać dostęp do dowolnego członka w Foohierarchii.

W obu przypadkach kompilator rozwiązuje wywołanie metody dla Ciebie, bezpiecznie wpisując, jak w przypadku metody publicznej, podczas gdy Manifold generuje wydajny kod odbicia pod maską.

Alternatywnie, jeśli typ nie jest znany statycznie, możesz użyć typowania strukturalnego, aby zdefiniować interfejs, który może spełniać typ, bez konieczności deklarowania jego implementacji. Ta strategia zapewnia bezpieczeństwo typów i pozwala uniknąć problemów z wydajnością i tożsamością związanych z odbiciem i kodem proxy.

Dowiedz się więcej o kolektorze .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.