mimicry in action II – dynamically implement an interface using Dynamic Proxy

this entry is the continuation of part 1, where we saw how to leverage Dynamic Proxy in order to let an object appear as if it would implement an arbitrary interface as long as it offers the same methods.

extensions

in this second part we’ll extend the solution in order to map required methods of an interface to an object’s methods, even if those methods partially bear another name (or offer sybtypes of the required arguments, even if they are placed in a different order).

a fluent interface

one fundamental goal is a simple and fluent api in order to support ease of use, allowing a simple and declarative style of usage – well, we’ll see …

for clarity, we will modify our example – imagine that class Foo has no longer a matching method echo(), but a method called reverb():

public class Foo {
    public String say( String msg ){
        return "foo said " + msg;
    }
    ...
}

now we want to (have to) map the required method echo() of interface IBar to reverb(). we’ll start with thinking about a possible api extension and have some thoughts about it’s implementation afterwards. a possible way to express the mapping could look like this:

barClient.speakTo( foo.as( IBar.class, map( "echo" ).to( "say" ) ) );

mapping method names

as you can see, we only extend the signature in order to push one (or more) mappings to the proxy, using a statically imported method map(). therefore we will use varargs in the following solution, so it’s completely free to the client to apply as many mappings as needed:

public class InterfaceMimic implements InvocationHandler{

    public static <I> I mimic( Object bean, Class<I> asInterface, MethodMapping...mappings ){
        return
            (I) Proxy.newProxyInstance(
                    bean.getClass().getClassLoader(),
                    new Class[]{asInterface},
                    new InterfaceMimic( bean, mappings ) );
    }

    public static MethodMapping map( String interfaceMethod ){
        return new MethodMapping( interfaceMethod );
    }

    private MethodMapping[] mappings = new MethodMapping[0];
    private Object proxee = null;

    public InterfaceMimic( Object proxee, MethodMapping...mappings ){
        this.proxee = proxee;
        this.mappings = mappings;
    }
    ...
}

we’re not finished yet. you surely saw the new type MethodMapping, which now extends the signature of method mimic() (formerly as() ) as a vararg.

MethodMapping

this type will hold the whole information about the mapping. you also saw the static factory method map(), which will create a new instance of MethodMapping initializing it with the name of the method required by the interface. what’s left is the mapping link to the method which have to be called on the object:

public class MethodMapping {

    private String interfaceMethod = null;
    private String beanMethod = null;

    public MethodMapping( String interfaceMethod ){
        this.interfaceMethod = interfaceMethod;
    }

    public MethodMapping to( String beanMethod, Class...argTypes ){
        this.beanMethod = beanMethod;
        return this;
    }

    protected boolean matches( Method method ){...}

    protected Object callMappedMethodOn( Object bean, Method method, Object[] args ) {...}
    ...
}

now we only have to extend the proxy in order to hold the mappings and ask if there’s one when intercepting a method:

public class InterfaceMimic implements InvocationHandler{
    ...
    public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable {
        MethodMapping mapping = findMappingFor( method );
        return
            mapping != null ?
                mapping.callMappedMethodOn( proxee, method, args ) :
                callProxy( method, args );
    }

    private Object callProxy( Method method, Object[] args) {
        try {
            return
                proxee.getClass().getDeclaredMethod(
                    method.getName(),
                    method.getParameterTypes() ).invoke( proxee, args );
        }
        catch (Exception e) {
            throw new RuntimeException( e );
        }
    }

    private MethodMapping findMappingFor( Method method ){
        for( MethodMapping mapping : mappings ){
            if( mapping.matches( method ) )
                return mapping;
            }
        }
        return null;
    }
}

as you can see, we first try to find a matching MethodMapping. if one exists, we call the mapped method on the object. if not, we try to call a method on the object with the same name as the required one. that’s all.

of course you could now extend type MethodMapping in order to additionally gathering information about the argument types of the mapped method and / or their different order, like so:

// reorderArgs will show on the first position,
// to which argument position the first interface
// argument will map, and so on ...
foo.as( IBar.class,
        map( "add" ).to( "calc", BigDecimal.class, int.class, String.class ).reorderArgs( 3, 2, 1 ) ) );

i’ll leave this exercise to you. it should be no big challenge at all. if you want to see one possible solution, take a look at the open source project Bricks4J. i comitted the complete source along with a unit test in the current cvs branch, which will show you the correct usage.

conclusion

as you now have seen, Dynamic Proxy is a valuable tool when it comes to mimic interfaces. you can easily make an object appear as if it would implement an interface, even if this object methods doesn’t match exactly by name or argument types.
of course you’ll face a trade off – sometimes it might by easier and more comprehensible simply to implement an adaptor for an interface than using a bulk of mappings. so it’s up to you – as always – to choose the right ‘weapon’ …

Posted in java. 2 Comments »

2 Responses to “mimicry in action II – dynamically implement an interface using Dynamic Proxy”

  1. Markus Says:

    I just wrote some lines why this mimicry interface is interesting but not tempting, and during writing I realized, yeah .. why not.
    You extend an uneditable implementation and make it through an interface more usefull for your own code.

    But perhaps its better to extend that uneditable class and give the new class an interface?

    Markus

  2. Mario Gleichmann Says:

    Hey Markus,

    thanks for your feedback.

    I’m strongly dissapointed, judging the mimicry proxy not tempting ;o)

    Honestly now: in the past, i was more than once annoyed by writing adapters only to implement a requested interface by a service, only to delegate 90 percent of the methods to the adaptee unchanged.

    In this point of view it’s a trade off between an increased code base by introducing a new adapter class, which ain’t do any meaningful but calling the adaptees methods on the one side and an increased runtime performance by introducing an additional abstraction on the other side.

    I tried to provide a interface which is as fluent and simply as possible for clients. And of course there are some situations, where using the Proxy introduces more complexity (especially when you also have to map arguments) than using an additional adapter class!

    So as always – it’s up to you to decide whether to go to the one or other side of the trade off …

    Greetings

    Mario


Leave a reply to Mario Gleichmann Cancel reply