ทางเลือกในการ java.lang.reflect.Proxy สำหรับการสร้างพร็อกซีของคลาสนามธรรม (แทนที่จะเป็นอินเทอร์เฟซ)


90

ตามเอกสาร :

[ java.lang.reflect.] Proxyจัดเตรียมเมธอดแบบคงที่สำหรับการสร้างคลาสพร็อกซีและอินสแตนซ์แบบไดนามิกและยังเป็นซูเปอร์คลาสของคลาสพร็อกซีแบบไดนามิกทั้งหมดที่สร้างโดยเมธอดเหล่านั้น

newProxyMethodวิธี (รับผิดชอบในการสร้างผู้รับมอบฉันทะแบบไดนามิก) มีลายเซ็นต่อไปนี้:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
                             throws IllegalArgumentException

น่าเสียดายที่สิ่งนี้ป้องกันไม่ให้สร้างพร็อกซีแบบไดนามิกที่ขยายคลาสนามธรรมเฉพาะ (แทนที่จะใช้อินเทอร์เฟซเฉพาะ) สิ่งนี้สมเหตุสมผลเมื่อพิจารณาว่าjava.lang.reflect.Proxyเป็น "superclass ของไดนามิกพร็อกซีทั้งหมด" ดังนั้นจึงป้องกันไม่ให้คลาสอื่นเป็นซูเปอร์คลาส

ดังนั้นมีทางเลือกอื่นjava.lang.reflect.Proxyที่สามารถสร้างพร็อกซีแบบไดนามิกที่สืบทอดมาจากคลาสนามธรรมเฉพาะโดยเปลี่ยนเส้นทางการเรียกทั้งหมดไปยังเมธอดนามธรรมไปยังตัวจัดการการเรียกใช้หรือไม่

ตัวอย่างเช่นสมมติว่าฉันมีคลาสนามธรรมDog:

public abstract class Dog {

    public void bark() {
        System.out.println("Woof!");
    }

    public abstract void fetch();

}

มีชั้นเรียนที่อนุญาตให้ฉันทำสิ่งต่อไปนี้หรือไม่?

Dog dog = SomeOtherProxy.newProxyInstance(classLoader, Dog.class, h);

dog.fetch(); // Will be handled by the invocation handler
dog.bark();  // Will NOT be handled by the invocation handler

คำตอบ:


123

ก็สามารถทำได้โดยใช้Javassist (ดูProxyFactory) หรือCGLIB

ตัวอย่างของ Adam โดยใช้ Javassist:

ฉัน (Adam Paynter) เขียนโค้ดนี้โดยใช้ Javassist:

ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(Dog.class);
factory.setFilter(
    new MethodFilter() {
        @Override
        public boolean isHandled(Method method) {
            return Modifier.isAbstract(method.getModifiers());
        }
    }
);

MethodHandler handler = new MethodHandler() {
    @Override
    public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
        System.out.println("Handling " + thisMethod + " via the method handler");
        return null;
    }
};

Dog dog = (Dog) factory.create(new Class<?>[0], new Object[0], handler);
dog.bark();
dog.fetch();

ซึ่งสร้างผลลัพธ์นี้:

โฮ่ง!
การจัดการโมฆะนามธรรมสาธารณะ mock.Dog.fetch () ผ่านตัวจัดการวิธีการ

10
+1: สิ่งที่ฉันต้องการ! ฉันจะแก้ไขคำตอบของคุณด้วยรหัสตัวอย่างของฉัน
Adam Paynter

proxyFactory.setHandler()เลิกใช้แล้ว กรุณาใช้proxy.setHandler.
AlikElzin-kilaka

@axtavt วัตถุ "Dog" คือการใช้งานหรืออินเทอร์เฟซในโค้ดด้านบนหรือไม่?
stackoverflow

-7

สิ่งที่คุณสามารถทำได้ในกรณีนี้คือการมีตัวจัดการพร็อกซีที่จะเปลี่ยนเส้นทางการโทรไปยังเมธอดที่มีอยู่ของคลาสนามธรรมของคุณ

แน่นอนคุณจะต้องเขียนโค้ด แต่มันค่อนข้างง่าย สำหรับการสร้าง Proxy ของคุณคุณจะต้องให้InvocationHandlerไฟล์. จากนั้นคุณจะต้องตรวจสอบประเภทวิธีการในinvoke(..)วิธีการของตัวจัดการการร้องขอของคุณเท่านั้น แต่ระวัง: คุณจะต้องตรวจสอบประเภทเมธอดกับวัตถุต้นแบบที่เกี่ยวข้องกับตัวจัดการของคุณและไม่เทียบกับประเภทนามธรรมที่ประกาศไว้

ถ้าฉันยกตัวอย่างคลาสสุนัขของคุณวิธีการเรียกของตัวจัดการการเรียกของคุณอาจมีลักษณะเช่นนี้ (โดยมีคลาสย่อยที่เกี่ยวข้องกับสุนัขที่มีอยู่เรียกว่า .. well ... dog)

public void invoke(Object proxy, Method method, Object[] args) {
    if(!Modifier.isAbstract(method.getModifiers())) {
        method.invoke(dog, args); // with the correct exception handling
    } else {
        // what can we do with abstract methods ?
    }
}

อย่างไรก็ตามมีบางอย่างที่ทำให้ฉันสงสัย: ฉันได้พูดคุยเกี่ยวกับdogวัตถุ แต่เนื่องจากคลาส Dog เป็นนามธรรมคุณจึงไม่สามารถสร้างอินสแตนซ์ได้ดังนั้นคุณจึงมีคลาสย่อยอยู่แล้ว นอกจากนี้เมื่อมีการตรวจสอบซอร์สโค้ด Proxy อย่างเข้มงวดคุณอาจพบ (ที่ Proxy.java:362) ว่าไม่สามารถสร้าง Proxy สำหรับอ็อบเจ็กต์คลาสที่ไม่ได้แสดงถึงอินเทอร์เฟซ)

ดังนั้นนอกเหนือจากความเป็นจริงแล้วสิ่งที่คุณต้องการจะทำก็เป็นไปได้อย่างสมบูรณ์แบบ


1
โปรดอดทนกับฉันในขณะที่ฉันพยายามเข้าใจคำตอบของคุณ ... ในกรณีเฉพาะของฉันฉันต้องการให้คลาสพร็อกซี (สร้างขึ้นที่รันไทม์) เป็นคลาสย่อยของDog(ตัวอย่างเช่นฉันไม่ได้เขียนPoodleคลาสที่ใช้งานอย่างชัดเจนfetch()) ดังนั้นจึงไม่มีdogตัวแปรที่จะเรียกใช้เมธอด ... ขออภัยหากฉันสับสนฉันจะต้องคิดเรื่องนี้ให้มากขึ้น
Adam Paynter

1
@Adam - คุณไม่สามารถสร้างคลาสย่อยแบบไดนามิกที่รันไทม์ได้หากไม่มีการจัดการ bytecode (CGLib ฉันคิดว่าทำแบบนี้) คำตอบสั้น ๆ คือพร็อกซีแบบไดนามิกสนับสนุนอินเทอร์เฟซ แต่ไม่ใช่คลาสนามธรรมเนื่องจากทั้งสองมีแนวคิดที่แตกต่างกันมาก แทบจะเป็นไปไม่ได้เลยที่จะคิดถึงวิธีการสร้างคลาสนามธรรมของพร็อกซีแบบไดนามิกด้วยวิธีที่ดี
Andrzej Doyle

1
@ Andrzej: ฉันเข้าใจว่าสิ่งที่ฉันขอนั้นต้องการการจัดการ bytecode (อันที่จริงฉันได้เขียนวิธีแก้ปัญหาโดยใช้ ASM แล้ว) ฉันเข้าใจด้วยว่าพร็อกซีไดนามิกของ Java รองรับเฉพาะอินเทอร์เฟซเท่านั้น บางทีคำถามของฉันอาจไม่ชัดเจนทั้งหมด - ฉันกำลังถามว่ามีคลาสอื่น (นั่นคืออย่างอื่นที่ไม่ใช่java.lang.reflect.Proxy) ที่ทำในสิ่งที่ฉันต้องการหรือไม่
Adam Paynter

2
เพื่อให้สิ่งที่ยาวสั้น ... ไม่ (อย่างน้อยในคลาส Java มาตรฐาน) การใช้การจัดการ bytecode ท้องฟ้ามีขีด จำกัด !
Riduidel

9
ฉันลดคะแนนลงเพราะมันไม่ใช่คำตอบสำหรับคำถามจริงๆ OP ระบุว่าเขาต้องการพร็อกซีคลาสไม่ใช่อินเทอร์เฟซและทราบว่า java.lang.reflect.Proxy ทำไม่ได้ คุณเพียงแค่ทำซ้ำข้อเท็จจริงนั้นและไม่มีวิธีแก้ปัญหาอื่นใด
jcsahnwaldt Reinstate Monica
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.