Các lựa chọn thay thế cho java.lang.reflect.Proxy để tạo proxy của các lớp trừu tượng (thay vì giao diện)


89

Theo tài liệu :

[ java.lang.reflect.] Proxycung cấp các phương thức tĩnh để tạo các lớp và thể hiện proxy động, và nó cũng là lớp cha của tất cả các lớp proxy động được tạo bởi các phương thức đó.

Các newProxyMethodphương pháp (có trách nhiệm để tạo ra các proxy động) có chữ ký sau đây:

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

Thật không may, điều này ngăn người ta tạo proxy động mở rộng một lớp trừu tượng cụ thể (thay vì triển khai các giao diện cụ thể). Điều này có ý nghĩa, việc xem xét java.lang.reflect.Proxylà "lớp cha của tất cả proxy động", do đó ngăn chặn một lớp khác là lớp cha.

Do đó, có bất kỳ giải pháp thay thế java.lang.reflect.Proxynào có thể tạo proxy động kế thừa từ một lớp trừu tượng cụ thể, chuyển hướng tất cả các lệnh gọi đến các phương thức trừu tượng đến trình xử lý lệnh gọi không?

Ví dụ: giả sử tôi có một lớp trừu tượng Dog:

public abstract class Dog {

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

    public abstract void fetch();

}

Có lớp học nào cho phép tôi làm những điều sau không?

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

Câu trả lời:


123

Nó có thể được thực hiện bằng Javassist (xem ProxyFactory) hoặc CGLIB .

Ví dụ của Adam bằng cách sử dụng Javassist:

Tôi (Adam Paynter) đã viết mã này bằng 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();

Cái nào tạo ra đầu ra này:

Gâu!
Xử lý công khai void mock.Dog.fetch () thông qua trình xử lý phương thức

10
+1: Chính xác những gì tôi cần! Tôi sẽ chỉnh sửa câu trả lời của bạn với mã mẫu của tôi.
Adam Paynter

proxyFactory.setHandler()không được dùng nữa. Hãy sử dụng proxy.setHandler.
AlikElzin-kilaka

@axtavt là đối tượng "Dog" là một triển khai hay một giao diện trong đoạn mã trên?
stackoverflow

-7

Những gì bạn có thể làm trong trường hợp này là có một trình xử lý proxy sẽ chuyển hướng các cuộc gọi đến các phương thức hiện có của lớp trừu tượng của bạn.

Tất nhiên bạn sẽ phải viết mã nó, tuy nhiên nó khá đơn giản. Để tạo Proxy của bạn, bạn sẽ phải cung cấp cho anh ta một InvocationHandler. Sau đó, bạn sẽ chỉ phải kiểm tra loại phương thức trong invoke(..)phương thức của trình xử lý lời gọi của bạn. Nhưng hãy cẩn thận: bạn sẽ phải kiểm tra kiểu phương thức đối với đối tượng cơ bản được liên kết với trình xử lý của bạn, chứ không phải đối với kiểu đã khai báo của lớp trừu tượng của bạn.

Nếu tôi lấy làm ví dụ về lớp chó của bạn, thì phương thức gọi của trình xử lý lệnh gọi của bạn có thể trông giống như thế này (với một lớp con chó được liên kết hiện có được gọi là .. 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 ?
    }
}

Tuy nhiên, có điều gì đó khiến tôi băn khoăn: Tôi đã nói về một dogđối tượng. Tuy nhiên, vì lớp Dog là trừu tượng, bạn không thể tạo các cá thể, vì vậy bạn có các lớp con hiện có. Hơn nữa, khi việc kiểm tra nghiêm ngặt mã nguồn Proxy cho thấy, bạn có thể phát hiện ra (tại Proxy.java:362) rằng không thể tạo Proxy cho đối tượng Lớp không đại diện cho giao diện).

Vì vậy, ngoài thực tế , bạn muốn làm gì cũng hoàn toàn có thể làm được.


1
Vui lòng chịu đựng tôi trong khi tôi cố gắng hiểu câu trả lời của bạn ... Trong trường hợp cụ thể của tôi, tôi muốn lớp proxy (được tạo trong thời gian chạy) là lớp con của Dog(ví dụ: tôi không viết rõ ràng một Poodlelớp triển khai fetch()). Do đó, không có dogbiến nào để gọi các phương thức dựa trên ... Xin lỗi nếu tôi khó hiểu, tôi sẽ phải suy nghĩ kỹ hơn về vấn đề này.
Adam Paynter

1
@Adam - bạn không thể tự động tạo các lớp con trong thời gian chạy mà không có một số thao tác bytecode (Tôi nghĩ CGLib thực hiện điều gì đó như thế này). Câu trả lời ngắn gọn là proxy động hỗ trợ các giao diện, nhưng không hỗ trợ các lớp trừu tượng, vì hai khái niệm này rất khác nhau. Gần như không thể nghĩ ra cách để ủy quyền động cho các lớp trừu tượng một cách lành mạnh.
Andrzej Doyle

1
@Andrzej: Tôi hiểu rằng những gì tôi đang yêu cầu yêu cầu thao tác bytecode (trên thực tế, tôi đã viết một giải pháp cho vấn đề của mình bằng ASM). Tôi cũng hiểu rằng các proxy động của Java chỉ hỗ trợ các giao diện. Có lẽ câu hỏi của tôi không hoàn toàn rõ ràng - tôi đang hỏi liệu có bất kỳ lớp nào khác (nghĩa là, một cái gì đó khác java.lang.reflect.Proxy) có sẵn những gì tôi cần không.
Adam Paynter

2
Vâng, để làm cho những thứ dài ngắn ... không (ít nhất là trong các lớp Java tiêu chuẩn). Sử dụng thao tác bytecode, bầu trời là giới hạn!
Riduidel

9
Tôi đã phản đối vì nó không thực sự là câu trả lời cho câu hỏi. OP nói rằng anh ấy muốn ủy quyền một lớp chứ không phải một giao diện và biết rằng điều đó là không thể với java.lang.reflect.Proxy. Bạn chỉ cần lặp lại thực tế đó và không đưa ra giải pháp nào khác.
jcsahnwaldt Khôi phục Monica
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.