มีวิธีใดบ้างที่จะเรียกใช้วิธีการส่วนตัว


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 NoSuchMethodExceptionฉันสามารถแก้มันได้โดยการทำวิธีpublicหรือทำให้คลาสอื่นได้มาจาก

เรื่องสั้นสั้นฉันแค่สงสัยว่ามีวิธีการเข้าถึงprivateวิธีการสะท้อน

คำตอบ:


303

คุณสามารถเรียกใช้เมธอดส่วนตัวพร้อมการสะท้อน การแก้ไขบิตสุดท้ายของรหัสที่โพสต์:

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

มีข้อแม้อยู่สองสามข้อ ก่อนgetDeclaredMethodจะค้นหาวิธีการประกาศในปัจจุบันClassไม่ได้รับมาจาก supertypes ดังนั้นสำรวจลำดับชั้นของรูปธรรมหากจำเป็น ประการที่สองSecurityManagerสามารถป้องกันการใช้setAccessibleวิธีการ ดังนั้นจึงอาจจำเป็นต้องเรียกใช้เป็นPrivilegedAction(ใช้AccessControllerหรือSubject)


2
เมื่อฉันทำสิ่งนี้ในอดีตฉันก็เรียก method.setAccessible (false) หลังจากเรียกเมธอด แต่ฉันไม่รู้ว่าสิ่งนี้จำเป็นหรือไม่
shsteimer

16
ไม่เมื่อคุณตั้งค่าการเข้าถึงจะมีผลกับอินสแตนซ์นั้นเท่านั้น ตราบใดที่คุณไม่ปล่อยให้เมธอดนั้นหนีจากการควบคุมวัตถุก็จะปลอดภัย
erickson

6
ดังนั้นจุดของการมีวิธีการส่วนตัวคืออะไรถ้าพวกเขาสามารถถูกเรียกจากนอกห้องเรียน?
Peter Ajtai

2
นอกจากนี้ตรวจสอบให้แน่ใจว่าคุณโทรหาgetDeclaredMethod()แทนที่จะเป็นเพียงแค่getMethod()นี้จะไม่ทำงานสำหรับวิธีการส่วนตัว
Ercksen

1
@PeterAjtai ขออภัยในความล่าช้า แต่คิดว่าวิธีนี้: คนส่วนใหญ่ในปัจจุบันล็อคประตูของพวกเขาแม้ว่าพวกเขารู้ว่าล็อคสามารถแตกหักหรือหลีกเลี่ยงได้เล็กน้อย ทำไม? เพราะมันช่วยให้คนที่ซื่อสัตย์ส่วนใหญ่ซื่อสัตย์ คุณสามารถนึกถึงการprivateเข้าถึงที่มีบทบาทคล้ายกัน
erickson

34

ใช้getDeclaredMethod()รับวัตถุวิธีส่วนตัวแล้วใช้method.setAccessible()เพื่ออนุญาตให้เรียกจริง


ในตัวอย่างของตัวเอง ( stackoverflow.com/a/15612040/257233 ) ฉันจะได้รับถ้าฉันทำไม่ได้โทรjava.lang.StackOverflowError setAccessible(true)
Robert Mark Bram

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 ? คุณไม่สามารถใช้พารามิเตอร์ความยาวได้หรือไม่
Saad Malik

7

คุณสามารถทำได้โดยใช้ ReflectionTestUtils of Spring ( org.springframework.test.util.ReflectionTestUtils )

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

ตัวอย่าง: ถ้าคุณมีคลาสพร้อมเมธอดไพรเวต square(int x)

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

นอกจากนี้ยังมีตัวสะท้อนแสงที่จำกัด มากขึ้นในแกนกลางสปริง
Thunderforge

3

ให้ฉันให้รหัสที่สมบูรณ์สำหรับวิธีการป้องกันการดำเนินการผ่านการสะท้อน รองรับ params ทุกประเภทรวมถึง generics, autoboxed params และค่า 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);
}

วิธีการใช้ apache ClassUtils สำหรับการตรวจสอบความเข้ากันได้ของ autoboxed params


ไม่มีจุดในการตอบคำถามอายุ 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 ของ Manifold เพื่อการสะท้อน Java โดยตรงที่ปลอดภัยและพิมพ์:

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

public class Foo {
    private void callMe();
}

@Jailbreakปลดล็อกfooตัวแปรโลคัลในคอมไพเลอร์เพื่อเข้าถึงสมาชิกทั้งหมดในFooลำดับชั้นของโดยตรง

ในทำนองเดียวกันคุณสามารถใช้วิธีการขยายการแหกคุก ()สำหรับการใช้ครั้งเดียว:

foo.jailbreak().callMe();

ผ่านjailbreak()วิธีการที่คุณสามารถเข้าถึงสมาชิกใด ๆ ในFooลำดับชั้นของ

ในทั้งสองกรณีคอมไพเลอร์แก้ไขวิธีการโทรสำหรับคุณพิมพ์ได้อย่างปลอดภัยราวกับว่าวิธีการสาธารณะในขณะที่ Manifold สร้างรหัสการสะท้อนที่มีประสิทธิภาพสำหรับคุณภายใต้ประทุน

อีกทางเลือกหนึ่งถ้าไม่รู้จักชนิดสแตติกคุณสามารถใช้การพิมพ์โครงสร้างเพื่อกำหนดอินเทอร์เฟซที่ชนิดสามารถตอบสนองโดยไม่ต้องประกาศการใช้งาน กลยุทธ์นี้จะรักษาความปลอดภัยประเภทและหลีกเลี่ยงปัญหาด้านประสิทธิภาพและตัวตนที่เกี่ยวข้องกับการสะท้อนและรหัสพร็อกซี

ค้นพบเพิ่มเติมเกี่ยวกับManifold

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.