Execute InvocationHandler invoke method for each method at my proxied class

2k views Asked by At

I have implement a dynamic proxy in order to do some operations before my methods started. now I have a problem when invoking two methods from the proxied class, here is the code:

Dynamic proxy class:

public class IPageProxy implements InvocationHandler {

    private Class <? extends IPage> screenClazz;

    public IPageProxy(final Class <? extends IPage> screenClazz) {
        this.screenClazz = screenClazz;
    }

    @SuppressWarnings("unchecked")
    public static <T extends IPage> T getInstance(final Class<? extends IPage> type)
            throws InstantiationException, IllegalAccessException {

        List<Class<?>> interfaces = new ArrayList<>();
        interfaces.addAll(Arrays.asList(type.getInterfaces()));

        return (T) Proxy.newProxyInstance(
                type.getClassLoader(),
                findInterfaces(type),
                new IPageProxy(type)
             );

    }


    static Class<?>[] findInterfaces(final Class<? extends IPage> type) {
        Class<?> current = type;

        do {
            final Class<?>[] interfaces = current.getInterfaces();

            if (interfaces.length != 0) {
                return interfaces;
            }
        } while ((current = current.getSuperclass()) != Object.class);

        throw new UnsupportedOperationException("The type does not implement any interface");
    }





    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws InvocationTargetException,
            IllegalAccessException, IllegalArgumentException, InstantiationException, ParserConfigurationException, XPathExpressionException, NoSuchFieldException, SecurityException {

        // before method executed this code will be done
        System.out.println("*   Dynamic proxy invoke method executed for " +  method.getName());

        // Invoke original method
        return method.invoke(screenClazz.newInstance(), args);
    }
}

Main class:

public static void main(String[] args) {
        try {
            //IEventDesignDialog a = new EventDesignDialog();
            IEventDesignDialog a  = (IEventDesignDialog)getInstance(EventDesignDialog.class);
            a.getEventType().getShow();

        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }


    @SuppressWarnings("unchecked")
    public static <T extends IPage> T getInstance(final Class<? extends IPage> type) throws InstantiationException, IllegalAccessException {
        return (T) IPageProxy.getInstance(type);
    }

Proxied class:

public class EventDesignDialog implements IEventDesignDialog{


        private String show;


        private String dateAndTimeDisplayFormat;
        private String eventType;


        @Entity(visibileName = "Show")
        public IEventDesignDialog getShow() {
            System.out.println("get show method invokde successfully");
            return this;
        }

        @Entity(visibileName = "Date And Time display format")
        public IEventDesignDialog getDateAndTimeDisplayFormat() {
            System.out.println("get date and time display format method invokde successfully");
            return this;
        }

        @Entity(visibileName = "Event Type")
        public IEventDesignDialog getEventType() {
            System.out.println("get event type method invokde successfully");
            return this;
        }



}

Actual output:

***   Dynamic proxy invoke method executed for getEventType
get event type method invokde successfully
get show method invokde successfully**

as shown invoke method executed only at the first method invocation after initializing the proxy, second method invoked directly, without proxy functionality is done.

my goal is to execute invoke method each time a method appears at my collection is invoked, the expected result should be as shown below.

Expected output:

***   Dynamic proxy invoke method executed for getEventType
get event type method invokde successfully
*   Dynamic proxy invoke method executed for getShow
get show method invokde successfully**

please let me know if more explanations needed.

2

There are 2 answers

0
Ali Taha On BEST ANSWER

I have solve this issue by creating an Interface with default method that return proxy instance, then returned it after executing the invoked method functionality:

updated code:

public interface IPage {
    default <T extends IPage> T getProxyInstance() {
        try {
            return (T) IPageProxy.getInstance(this.getClass());
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
}

My Page Interface:

@Page(path = "MyPath")
public interface IEventDesignDialog extends IPage{
    @Entity(visibileName = "Show")
    public IEventDesignDialog getShow();

    @Entity(visibileName = "Date And Time display format")
    public IEventDesignDialog getDateAndTimeDisplayFormat();

    @Entity(visibileName = "Event Type")
    public IEventDesignDialog getEventType();    
}

My Page class:

@Page(path = "MyPath")
public class EventDesignDialog implements IEventDesignDialog{
        @Entity(visibileName = "Show")
        public IEventDesignDialog getShow() {
            System.out.println("get show method invokde successfully");
            return getProxyInstance();
        }

        @Entity(visibileName = "Date And Time display format")
        public IEventDesignDialog getDateAndTimeDisplayFormat() {
            System.out.println("get date and time display format method invokde successfully");
            return getProxyInstance();
        }

        @Entity(visibileName = "Event Type")
        public IEventDesignDialog getEventType() {
            System.out.println("get event type method invokde successfully");
            return getProxyInstance();
        }
}

main class:

public class Main {

    public static void main(String[] args) {
        try {
            IEventDesignDialog a  = ((IEventDesignDialog)getInstance(EventDesignDialog.class)).getEventType().getShow();

            ((IShowDesignDialog)getInstance(ShowDesignDialog.class)).getShowName().getShowType();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }


    @SuppressWarnings("unchecked")
    public static <T extends IPage> T getInstance(final Class<? extends IPage> type) throws InstantiationException, IllegalAccessException {
        return (T) IPageProxy.getInstance(type);
    }

}

IProxy Page stay the same without changes.

0
Borislav Markov On

What is happening is that first you proxy only the first invocation , but then you invoke getShow() on a non proxified class, and this is why you get the result like you mentioned. If you want to achieve the goal you mentioned you need to make another proxy based on created instance rather than on just on the class.

Update: I will provide example code, you can paste in any java file and execute it. Where you see the TODO, you can place your own logic depending how you want to supply proxy. See NOTE for important moments. I placed all classes in one file for simplicity of demonstration.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.List;

class Scratch {
    public static void main(String[] args) {
        try {
            IEventDesignDialog a  = proxy(EventDesignDialog.class);
            a.getEventType().getShow();
            a.getDateAndTimeDisplayFormat().getShow();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }


    @SuppressWarnings("unchecked")
    private static <T extends IPage> T proxy(final Class<? extends IPage> type) throws InstantiationException, IllegalAccessException {
        return (T) IPageProxy.proxy(type);
    }
}
interface IPage{}
interface IEventDesignDialog extends IPage{
    IEventDesignDialog getShow();
    IEventDesignDialog getEventType();
    IEventDesignDialog getDateAndTimeDisplayFormat();
}
class EventDesignDialog implements IEventDesignDialog{

    public IEventDesignDialog getShow() {
        System.out.println("get show method invoked successfully");

        //NOTE: this will be treated as same proxy but not this
        return this;
    }

    public IEventDesignDialog getDateAndTimeDisplayFormat() {
        System.out.println("get date and time display format method invoked successfully");

        // NOTE: we supply some iinstance which will be proxied
        return new MyIEventDesignDialog();
    }

    public IEventDesignDialog getEventType() {
        System.out.println("get event type method invoked successfully");

        //NOTE: this will be treated as same proxy but not this
        return this;
    }

}
class IPageProxy implements InvocationHandler {

    private IPage instance;
    private List<Class<?>> interfaces;


    public IPageProxy(IPage instance, List<Class<?>> interfaces) {
        this.instance = instance;
        this.interfaces = interfaces;
    }

    @SuppressWarnings("unchecked")
    public static <T extends IPage> T proxy(final Class<? extends IPage> type)
            throws InstantiationException, IllegalAccessException {

        List<Class<?>> interfaces = Arrays.asList(type.getInterfaces());


        //TODO: get interfaces properly recursively
        return (T) Proxy.newProxyInstance(
                type.getClassLoader(),
                type.getInterfaces(),
                new IPageProxy(type.newInstance(), interfaces)
        );

    }

    @SuppressWarnings("unchecked")
    public static <T extends IPage> T proxy(T object) {

        //TODO: get interfaces properly recursively
        List<Class<?>> interfaces = Arrays.asList(object.getClass().getInterfaces());

        return (T) Proxy.newProxyInstance(
                object.getClass().getClassLoader(),
                object.getClass().getInterfaces(),
                new IPageProxy(object, interfaces)
        );

    }




    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Exception {

        // before method executed this code will be done
        System.out.println("*   Dynamic proxy invoke method executed for " +  method.getName());

        // Invoke original method
        Object invoke = method.invoke(instance, args);
        if (invoke == null) {
            return null;
        }

        //If some of the method returns the original object
        //we swap the returned object by our proxy
        if (invoke == instance) {
            return proxy;
        }

        //TODO: check if you want to swap in place
        //other interfaces
        if (interfaces.contains(method.getReturnType())) {
            return IPageProxy.proxy((IPage)invoke);
        }
        return invoke;
    }
}

class MyIEventDesignDialog implements IEventDesignDialog {
    @Override
    public IEventDesignDialog getShow() {
        return null;
    }

    @Override
    public IEventDesignDialog getEventType() {
        return null;
    }

    @Override
    public IEventDesignDialog getDateAndTimeDisplayFormat() {
        return null;
    }
}

Output:

*   Dynamic proxy invoke method executed for getEventType
get event type method invoked successfully
*   Dynamic proxy invoke method executed for getShow
get show method invoked successfully
*   Dynamic proxy invoke method executed for getDateAndTimeDisplayFormat
get date and time display format method invoked successfully
*   Dynamic proxy invoke method executed for getShow

You can get ideas from how Mockito is working. Please check this page: https://static.javadoc.io/org.mockito/mockito-core/2.27.0/org/mockito/Mockito.html#spy-T-

I know it is for testing, but you still can get ideas from it. So you can apply spy() on a class and on an object to spy on it.