Автоматическое делегирование всех методов класса java

Скажем, у меня есть класс со многими общедоступными методами:

public class MyClass {

    public void method1() {}
    public void method2() {}
    (...)
    public void methodN() {}

}

Теперь я хотел бы создать класс-оболочку, который делегировал бы все методы wrapped instance (delegate):

public class WrapperClass extends MyClass  {
    private final MyClass delegate;

    public WrapperClass(MyClass delegate) {
        this.delagate = delegate;
    }

    public void method1() { delegate.method1(); }
    public void method2() { delegate.method2(); }
    (...)
    public void methodN() { delegate.methodN(); }

}

Теперь, если у MyClass много методов, мне нужно будет переопределить каждый из них, который является более или менее тем же кодом, который просто "делегирует". Мне было интересно, можно ли сделать магию для автоматического вызова метода в Java (поэтому класс Wrapper должен был бы сказать "Эй, если вы вызываете метод на мне, просто перейдите к делегированию объекта и вызовите этот метод на нем).

BTW: я не могу использовать наследование, потому что делегат не находится под моим контролем. Я просто получаю его экземпляр из другого места (в другом случае, если MyClass был окончательным).

ПРИМЕЧАНИЕ. Я не хочу генерировать IDE. Я знаю, что могу сделать это с помощью IntelliJ/Eclipse, но мне любопытно, можно ли это сделать в коде.

Любые предложения о том, как добиться чего-то подобного? (ПРИМЕЧАНИЕ: я, вероятно, смогу сделать это на некоторых языках сценариев, таких как php, где я мог бы использовать магические функции php для перехвата вызова).

Ответ 1

Возможно, динамический Proxy java может вам помочь. Он работает, только если вы используете интерфейсы. В этом случае я вызову интерфейс MyInterface и настрою реализацию по умолчанию:

public class MyClass implements MyInterface {

    @Override
    public void method1() {
        System.out.println("foo1");
    }

    @Override
    public void method2() {
        System.out.println("foo2");
    }

    @Override
    public void methodN() {
        System.out.println("fooN");
    }

    public static void main(String[] args) {
        MyClass wrapped = new MyClass();
        wrapped.method1();
        wrapped.method2();
        MyInterface wrapper = WrapperClass.wrap(wrapped);
        wrapper.method1();
        wrapper.method2();
    }

}

Реализация класса-оболочки будет выглядеть так:

public class WrapperClass extends MyClass implements MyInterface, InvocationHandler {

    private final MyClass delegate;

    public WrapperClass(MyClass delegate) {
        this.delegate = delegate;
    }

    public static MyInterface wrap(MyClass wrapped) {
        return (MyInterface) Proxy.newProxyInstance(MyClass.class.getClassLoader(), new Class[] { MyInterface.class }, new WrapperClass(wrapped));
    }

    //you may skip this definition, it is only for demonstration
    public void method1() {
        System.out.println("bar");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Method m = findMethod(this.getClass(), method);
        if (m != null) {
            return m.invoke(this, args);
        }
        m = findMethod(delegate.getClass(), method);
        if (m != null) {
            return m.invoke(delegate, args);
        }
        return null;
    }

    private Method findMethod(Class<?> clazz, Method method) throws Throwable {
        try {
            return clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

}

Обратите внимание, что этот класс:

  • extends MyClass, чтобы наследовать реализацию по умолчанию (любой другой будет делать)
  • реализует Invocationhandler, чтобы позволить прокси-серверу делать отражение.
  • возможно реализовать MyInterface (чтобы удовлетворить шаблон декоратора)

Это решение позволяет переопределять специальные методы, но делегировать все остальные. Это будет работать даже с подклассами класса Wrapper.

Обратите внимание, что метод findMethod еще не фиксирует особые случаи.

Ответ 2

Этот вопрос уже 6 месяцев, и замечательный ответ @CoronA удовлетворил и был принят @walkeros, но я думал, что добавлю что-то здесь, поскольку я думаю, что это может быть сделано дополнительным шагом.

Как обсуждалось с @CoronA в комментариях к его ответу, вместо того, чтобы создавать и поддерживать длинный список методов MyClass в WrapperClass (т.е. public void methodN() { delegate.methodN(); }), решение динамического прокси переносит это на интерфейс, Проблема в том, что вам по-прежнему приходится создавать и поддерживать длинный список подписей для методов MyClass в интерфейсе, что, возможно, немного проще, но не полностью решает проблему. Это особенно важно, если у вас нет доступа к MyClass, чтобы узнать все методы.

Согласно Три подхода к декорированию вашего кода,

Для более длинных классов программист должен выбрать меньшее из двух зол: реализовать множество методов обертки и сохранить тип декорированного объекта или поддерживать простую реализацию декоратора и сохранение жертвы оформленный тип объекта.

Возможно, это ожидаемое ограничение шаблона Decorator.

@Марк-Брамник, однако, дает увлекательное решение с использованием CGLIB в Взаимодействие по методам класса Java (без интерфейсов). Я смог объединить это с решением @CoronaA, чтобы создать оболочку, которая может переопределить отдельные методы, но затем передать все остальное на обернутый объект, не требуя интерфейса.

Вот MyClass.

public class MyClass {

    public void method1() { System.out.println("This is method 1 - " + this); } 
    public void method2() { System.out.println("This is method 2 - " + this); } 
    public void method3() { System.out.println("This is method 3 - " + this); } 
    public void methodN() { System.out.println("This is method N - " + this); }

}

Здесь WrapperClass, который только переопределяет method2(). Как вы увидите ниже, не переопределенные методы, по сути, не передаются делегату, что может быть проблемой.

public class WrapperClass extends MyClass {

    private MyClass delagate;

    public WrapperClass(MyClass delegate) { this.delagate = delegate; }

    @Override
    public void method2() {
        System.out.println("This is overridden method 2 - " + delagate);
    }

}

Здесь MyInterceptor, который расширяет MyClass. Он использует прокси-решение с использованием CGLIB, как описано @Mark-Bramnik. Он также использует метод @CononA для определения, отправлять или не отправлять метод в оболочку (если она переопределена) или обернутый объект (если это не так).

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MyInterceptor extends MyClass implements MethodInterceptor {

    private Object realObj;

    public MyInterceptor(Object obj) { this.realObj = obj; }

    @Override
    public void method2() {
        System.out.println("This is overridden method 2 - " + realObj);
    }

    @Override
    public Object intercept(Object arg0, Method method, Object[] objects,
            MethodProxy methodProxy) throws Throwable {
        Method m = findMethod(this.getClass(), method);
        if (m != null) { return m.invoke(this, objects); }
        Object res = method.invoke(realObj, objects);
        return res;
    }

    private Method findMethod(Class<?> clazz, Method method) throws Throwable {
        try {
            return clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

}

Вот Main и результаты, полученные вами, если вы запустите его.

import net.sf.cglib.proxy.Enhancer;

public class Main {

    private static MyClass unwrapped;
    private static WrapperClass wrapped;
    private static MyClass proxified;

    public static void main(String[] args) {
        unwrapped = new MyClass();
        System.out.println(">>> Methods from the unwrapped object:");
        unwrapped.method1();
        unwrapped.method2();
        unwrapped.method3();
        wrapped = new WrapperClass(unwrapped);
        System.out.println(">>> Methods from the wrapped object:");
        wrapped.method1();
        wrapped.method2();
        wrapped.method3();
        proxified = createProxy(unwrapped);
        System.out.println(">>> Methods from the proxy object:");
        proxified.method1();
        proxified.method2();
        proxified.method3();
    }

    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T obj) {
        Enhancer e = new Enhancer();
        e.setSuperclass(obj.getClass());
        e.setCallback(new MyInterceptor(obj));
        T proxifiedObj = (T) e.create();
        return proxifiedObj;
    }

}

>>> Methods from the unwrapped object:
This is method 1 - [email protected]
This is method 2 - [email protected]
This is method 3 - [email protected]

>>> Methods from the wrapped object:
This is method 1 - [email protected]
This is overridden method 2 - [email protected]
This is method 3 - [email protected]

>>> Methods from the proxy object:
This is method 1 - [email protected]
This is overridden method 2 - [email protected]
This is method 3 - [email protected]

Как вы можете видеть, при запуске методов на wrapped вы получаете оболочку для методов, которые не переопределены (т.е. method1() и method3()). Однако при запуске методов на proxified все методы запускаются на обернутом объекте без большой необходимости делегировать их все в WrapperClass или помещать все подписи метода в интерфейс. Благодаря @CoronA и @Mark-Bramnik за то, что кажется довольно крутым решением этой проблемы.

Ответ 4

Вам не обязательно это делать - ваш класс Wrapper является подклассом исходного класса, поэтому он наследует все его общедоступные методы - и если вы их не реализуете, будет вызван оригинальный метод.

У вас не должно быть extends Myclass вместе с частным MyClass объектом, что действительно действительно избыточно, и я не могу думать о шаблоне проектирования, где это правильно. Ваш WrapperClass a MyClass, и поэтому вы можете просто использовать свои собственные поля и методы вместо вызова delegate.

РЕДАКТИРОВАТЬ: В случае MyClass, являющегося final, вы обходите декларацию willfull, чтобы не допускать подклассов путем "подделки" наследования; Я не могу придумать кого-либо, желающего сделать это, кроме вас, кто контролирует WrapperClass; но, поскольку вы контролируете WrapperClass, не обертывать все, что вам не нужно, это больше, чем вариант - это правильно, потому что ваш объект не a MyClass и должен вести себя как один в тех случаях, которые вы мысленно рассмотрели.

РЕДАКТИРОВАТЬ, вы только что изменили свой вопрос, чтобы означать что-то совершенно другое, удалив суперкласс класса MyClass на ваш WrapperClass; это немного плохо, потому что это аннулирует все ответы, данные до сих пор. Вы должны были открыть другой вопрос.

Ответ 6

Определите метод в WrapperClass i.e. delegate(), который возвращает экземпляр MyClass

ИЛИ

Вы можете использовать отражение, чтобы сделать это, но вызывающий должен передать имя метода в качестве аргумента открытому методу. И будут осложнения в отношении аргументов метода/перегруженных методов и т.д.

BTW: я не могу использовать наследование, потому что делегат не находится под моим контролем. Я просто получаю его экземпляр из другого места (в другом случае, если MyClass был окончательным)

В коде, который вы разместили, есть public class WrapperClass extends MyClass

На самом деле ваша текущая реализация WrapperClass на самом деле является декоратором поверх MyClass

Ответ 7

Кредиты отправляются в CoronA для указания классов Proxy и InvocationHandler. Я разработал более многоразовый класс утилиты, основанный на его решении, используя generics:

public class DelegationUtils {

    public static <I> I wrap(Class<I> iface, I wrapped) {
        return wrapInternally(iface, wrapped, new SimpleDecorator(wrapped));
    }

    private static <I> I wrapInternally (Class<I> iface, I wrapped, InvocationHandler handler) {
        return (I) Proxy.newProxyInstance(wrapped.getClass().getClassLoader(), new Class[] { iface }, handler);
    }

    private static class SimpleDecorator<T> implements InvocationHandler {

        private final T delegate;

        private SimpleDecorator(T delegate) {
            this.delegate = delegate;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Method m = findMethod(delegate.getClass(), method);
            if (m == null) {
                throw new NullPointerException("Found no method " + method + " in delegate: " + delegate);
            }
            return m.invoke(delegate, args);
        }
    }    

    private static Method findMethod(Class<?> clazz, Method method) throws Throwable {
        try {
            return clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            return null;
        }
    }
}

Проверьте это:

public class Test {

    public  interface Test {
        public void sayHello ();
    }

    public static class TestImpl implements Test {
        @Override
        public void sayHello() {
            System.out.println("HELLO!");
        }
    }

    public static void main(String[] args) {
        Test proxy = DelegationUtils.wrap(Test.class, new TestImpl());
        proxy.sayHello();
    }
}

Я хотел создать автоматический класс делегирования, который выполняет методы делегата в EDT. С помощью этого класса вы просто создаете новый метод утилиты, который будет использовать EDTDecorator, в котором реализация будет обертывать m.invoke в SwingUtilities.invokeLater.

Однако, если я подумаю об этом, я могу пересмотреть вопрос о создании прокси-сервера, отличного от Reflection, для каждого интерфейса, который у меня есть - он может быть более чистым и быстрым и более понятным. Но, возможно.