Search code examples
javadynamic-proxy

java.lang.ClassCastException on Proxy creation


My goal is to create an instance from a class that implements an interface and extends another class.

...Entity annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface Entity {

    String visibileName();

}

...implementsIEventDesignDialog

      public class EventDesignDialog implements IEventDesignDialog{

        private String show;
        private String dateAndTimeDisplayFormat;
        private String eventType;


        @Entity(visibileName = "Show")
        public String getShow() {
            return this.show;
        }

        @Entity(visibileName = "Date And Time display format")
        public String getDateAndTimeDisplayFormat() {
            return this.dateAndTimeDisplayFormat;
        }

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

IEventDesignDialog interface:

public interface IEventDesignDialog extends IPage{

    public String getShow();

    public String getDateAndTimeDisplayFormat();

    public String getEventType();


}

IPage interface:

public interface IPage {

}

Dynamic proxy implementation:

public class IPageProxy implements InvocationHandler {
    private List<Method> entityMethods;



    private Class <? extends IPage> screenClazz;

    public IPageProxy(final Class <? extends IPage> screenClazz) {
        entityMethods = new ArrayList<>();
        getEntityAnnotatedMethods(screenClazz);
        // Accept the real implementation to be proxied
        this.screenClazz = screenClazz;
    }


    /**
     * create an page instance
     * @param type
     * @param
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public static IPage getInstance(final Class<? extends IPage> type)
            throws InstantiationException, IllegalAccessException {

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

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

        /*return (IPage) Proxy.newProxyInstance(type.getClassLoader(),
               interfaces.toArray(new Class<?>[interfaces.size()])
                , new IPageProxy(type));*/
    }


    /**
     * get all methods that annotated with @Entity annotation
     * and add it for entityMethods array List
     * @param screenClazz
     */
    private void getEntityAnnotatedMethods(final Class <? extends IPage>  screenClazz) {
        // Scan each interface method for the specific annotation
        // and save each compatible method
        for (final Method m : screenClazz.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Entity.class)) {
                entityMethods.add(m);
            }
        }
    }


    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 {
        // A method on MyInterface has been called!
        // Check if we need to go call it directly or if we need to
        // execute something else before!


        if (entityMethods.contains(method)) {
            // The method exist in our to-be-proxied list
            // Execute something and the call it
            // ... some other things
            System.out.println("Something else");
        }

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

}

Main class:

public class Main {

    public static void main(String[] args) {
        try {

            ((EventDesignDialog)getInstance(EventDesignDialog.class)).getEventType();
        } 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);
    }

}

The following exception is thrown:

  Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy2 cannot be cast to abc.EventDesignDialog
    at abc.Main.main(Main.java:8)

Solution

  • You're extending Screen, which means it isn't an interface.
    Dynamic Proxies work only if a base interface is present in the hierarchy.

    interfaces.size() == 0
    

    Thus the proxy can't implement any interface, and obviously it isn't part of the Screen hierarchy.


    If Screen was an interface, your method is still too complex. This

    public static Screen getInstance(Class<? extends Screen> type)
    

    is sufficient.


    You still receive an exception because

    Class#getInterfaces
    

    returns the interfaces which are implemented by this class.

    That means if you invoke it on EventDesignDialog.class, it will return an empty array.
    That means if you invoke it on EntityDesignDialog.class, still it will return an empty array.
    When invoking it on Screen.class, it will return

    [IPage.class]
    

    You need to loop the hierarchy with

    Class#getSuperclass
    

    until you find a suitable interface.

    A possible implementation might look like

    static Class<?>[] findInterfaces(final Class<?> 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");
    }
    

    which means you need to change your code to

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

    But, being that you already know the result will be an IPage proxy, you can just

    return (IPage) Proxy.newProxyInstance(
                  type.getClassLoader(),
                  new Class[] { IPage.class },
                  new IPageProxy(type)
               );
    

    Here

    public static IPage getInstance(final Class<? extends IPage> type)
    

    you're returning an IPage, but here

    ((EventDesignDialog)getInstance(EventDesignDialog.class))
    

    you're trying to downcast it, which means you're trying to cast it to a more specific type. This isn't possible as the Proxy isn't of the type EventDesignDialog, but it just implements your IPage interface.

    Being that Dynamic Proxies are interface-based, you'll be forced to deal with interfaces.
    Trying to cast to concrete classes will always throw an exception.

    If you need an IEventDesignDialog, you need a new Proxy specifically for it.