Adakah cara untuk Memohon metode pribadi?

146

Saya memiliki kelas yang menggunakan XML dan refleksi untuk mengembalikan Objects ke kelas lain.

Biasanya objek-objek ini adalah sub bidang dari objek eksternal, tetapi kadang-kadang itu adalah sesuatu yang ingin saya hasilkan dengan cepat. Saya sudah mencoba sesuatu seperti ini tetapi tidak berhasil. Saya percaya itu karena Java tidak akan mengizinkan Anda mengakses privatemetode untuk refleksi.

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

Jika metode yang disediakan adalah private, gagal dengan a NoSuchMethodException. Saya bisa menyelesaikannya dengan membuat metode public, atau membuat kelas lain untuk menurunkannya.

Singkat cerita, saya hanya ingin tahu apakah ada cara untuk mengakses privatemetode melalui refleksi.

Sheldon Ross
sumber

Jawaban:

303

Anda dapat memanggil metode pribadi dengan refleksi. Memodifikasi bit terakhir dari kode yang diposting:

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

Ada beberapa peringatan. Pertama, getDeclaredMethodhanya akan menemukan metode yang dideklarasikan saat ini Class, tidak diwarisi dari supertypes. Jadi, lintasi hierarki kelas yang konkret jika perlu. Kedua, SecurityManagerdapat mencegah penggunaan setAccessiblemetode ini. Jadi, mungkin perlu dijalankan sebagai PrivilegedAction(menggunakan AccessControlleratau Subject).

erickson
sumber
2
ketika saya sudah melakukan ini di masa lalu, saya juga telah memanggil method.setAccessible (false) setelah memanggil metode, tetapi saya tidak tahu apakah ini perlu atau tidak.
shsteimer
16
Tidak, ketika Anda mengatur aksesibilitas, itu hanya berlaku untuk instance itu. Selama Anda tidak membiarkan objek Metode tertentu lolos dari kendali Anda, itu aman.
erickson
6
Jadi, apa gunanya memiliki metode pribadi jika mereka dapat dipanggil dari luar kelas?
Peter Ajtai
2
Pastikan juga Anda menelepon getDeclaredMethod()bukan hanya getMethod()- ini tidak akan berfungsi untuk metode pribadi.
Ercksen
1
@PeterAjtai Maaf atas tanggapan yang terlambat, tapi pikirkan seperti ini: Kebanyakan orang saat ini mengunci pintu mereka, meskipun mereka tahu kunci itu bisa rusak atau dielakkan secara sepele sama sekali. Mengapa? Karena itu membantu menjaga orang kebanyakan jujur ​​jujur. Anda dapat memikirkan privateakses memainkan peran yang sama.
erickson
34

Gunakan getDeclaredMethod()untuk mendapatkan objek Metode pribadi dan kemudian gunakan method.setAccessible()untuk mengizinkannya memanggilnya.

Mihai Toader
sumber
Dalam contoh saya sendiri ( stackoverflow.com/a/15612040/257233 ) saya mendapatkan java.lang.StackOverflowErrorjika saya tidak menelepon setAccessible(true).
Robert Mark Bram
25

Jika metode menerima tipe data non-primitif maka metode berikut dapat digunakan untuk memanggil metode pribadi dari kelas apa pun:

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

Parameter yang diterima adalah obj, methodName, dan parameter. Sebagai contoh

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

Metode concatString dapat dipanggil sebagai

Test t = new Test();
    String str = (String) genericInvokeMethod(t, "concatString", "Hello", "Mr.x");
gaur
sumber
7
Mengapa paramCount dibutuhkan? Tidak bisakah Anda menggunakan params.length ?
Saad Malik
8

Anda dapat melakukan ini menggunakan ReflectionTestUtils of Spring ( org.springframework.test.util.ReflectionTestUtils )

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

Contoh: jika Anda memiliki kelas dengan metode pribadi square(int x)

Calculator calculator = new Calculator();
ReflectionTestUtils.invokeMethod(calculator,"square",10);
KaderLAB
sumber
Ada juga ReflectionUtils yang lebih terbatas di Spring inti.
Thunderforge
3

Biarkan saya memberikan kode lengkap untuk eksekusi metode yang dilindungi melalui refleksi. Ini mendukung semua jenis params termasuk generik, parob autoboxed dan nilai 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);
}

Metode menggunakan apache ClassUtils untuk memeriksa kompatibilitas dari paro autobox

Pavel Chertalev
sumber
Sama sekali tidak ada gunanya menjawab pertanyaan berusia 9 tahun dengan lebih dari 90000 pandangan dan jawaban yang diterima. Jawab pertanyaan yang tidak terjawab sebagai gantinya.
Anuraag Baishya
0

Satu lagi varian menggunakan library JOOR yang sangat kuat https://github.com/jOOQ/jOOR

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

Hal ini memungkinkan untuk memodifikasi bidang apa pun seperti konstanta statis final dan memanggil metode yang dilindungi yne tanpa menentukan kelas beton dalam hierarki warisan

<!-- 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>
Pavel Chertalev
sumber
0

Anda dapat menggunakan @Jailbreak Manifold untuk refleksi Java langsung dan aman-tipe:

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

public class Foo {
    private void callMe();
}

@Jailbreakmembuka foovariabel lokal di kompiler untuk akses langsung ke semua anggota dalam Foohierarki.

Demikian pula Anda dapat menggunakan metode ekstensi jailbreak () untuk sekali pakai:

foo.jailbreak().callMe();

Melalui jailbreak()metode ini Anda dapat mengakses anggota mana pun dalam Foohierarki.

Dalam kedua kasus kompiler menyelesaikan panggilan metode untuk Anda ketik-aman, seolah-olah metode publik, sementara Manifold menghasilkan kode refleksi yang efisien untuk Anda di bawah tenda.

Atau, jika jenisnya tidak diketahui secara statis, Anda dapat menggunakan Structural Typing untuk mendefinisikan antarmuka yang dapat dipenuhi oleh suatu jenis tanpa harus mendeklarasikan implementasinya. Strategi ini menjaga keamanan jenis dan menghindari masalah kinerja dan identitas yang terkait dengan refleksi dan kode proxy.

Temukan lebih lanjut tentang Manifold .

Scott
sumber