New Tricks with Dynamic Proxies in Java 8 (part 1)

from https://opencredo.com/dynamic-proxies-java/

Dynamic proxies have been a feature of Java since version 1.3. They were widely used in J2EE for remoting. Given an abstract interface, and a concrete implementation of that interface, a call to some method on the interface can be made “remote” (i.e. cross-JVM) by creating two additional classes. The first, a “marshalling” implementation of the interface, captures the details of the call in the source JVM and serializes them over the network. The second, an “unmarshalling” endpoint, receives the serialized call details and dispatches the call to an instance of the concrete class on the target JVM.

Without dynamic proxies and reflection, the developer would have had to provide these two classes for every interface that was to be used remotely. A dynamic proxy is a run-time generated class, implementing one or more interfaces, that automatically converts every method call to one of those interfaces into a call to the invoke method on a provided implementation of java.runtime.InvocationHandler:

public interface InvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

The InvocationHandler can then make decisions about how to handle the call, making use of all the information available at runtime about the method, including annotations, parameter types and the method’s return type. This makes it possible to implement generic logic defining how calls should be dispatched. Once you have written a marshalling InvocationHandler, you can use proxies calling this handler to perform marshalling for all interfaces, instead of having to write a separate implementation for each interface.

Remoting has fallen somewhat out of favour in recent years, as developers have come to understand that method call dispatch and sending a request over the network have fundamentally different semantics and failure modes, but dynamic proxies are still in the language. In this post, I’m going to discuss one of their possible uses for other purposes; in subsequent posts, I’ll talk about some new implementation techniques for dynamic proxies that are opened up by Java 8’s lambdas and default interface methods.

Magic Matchers

For some years now I’ve been using “magic” objects to make it easier to write concise, fluent tests. I define a “magic” object as an object defined purely through an interface, and instantiated via a dynamic proxy whichinterprets the interface in order to generate the desired behaviour. Of particular use in testing are “magic builders” for generating test values, and “magic matchers” for expressing assertions about the properties of test results. I’m going to concentrate on matchers here.

Suppose we have a class, Person, which is a typical bean-like object with private fields exposed via getter and setter methods.

public class Person {

    private String name;
    private int age;

    // insert getters and setters here
}

Using plain old Hamcrest, we have two ways of expressing assertions about instances of this class. One is to extract the values of its properties independently, and test them with separate assertions:

assertThat(person.getName(), containsString("Smith"));
assertThat(person.getAge(), greaterThan(30));

 

The other is to use the allOf and hasProperty matchers to express a compound expectation about the object as a whole:

assertThat(person.getName(), containsString("Smith"));
assertThat(person.getAge(), greaterThan(30));

This works well enough, but the way Hamcrest describes the compound matcher and reports mismatches is far from helpful:

Expected: (hasProperty("name", a string containing "Putey") and hasProperty("age", a value greater than <43>))
but: hasProperty("age", a value greater than <43>) property 'age' <42> was less than <43>

The hasProperty matcher also weakens the type-consistency of the code: we can write hasProperty("age", containsString("Smith")) and the type-checker won’t object.

What we really want is a fluent API that enables us to say something like this:

assertThat(person, aPerson()
    .withName("Arthur Putey")
    .withAge(greaterThan(43)));

and which reports mismatches in a nice, readable format like this:

Expected:
name: a string containing "Putey"
age: a value greater than <43>
    but:
age: <42> was less than <43>

It’s easy enough to write a custom Hamcrest matcher that has this behaviour, but tedious to have to do it lots of times. Fortunately, we can use dynamic proxies to help us out. We begin by defining a fluent interface containing the methods we want to define our compound matcher:

interface PersonMatcher extends Matcher<Person> {
    PersonMatcher withName(String expected);
    PersonMatcher withName(Matcher<? super String> matching);
    PersonMatcher withAge(int expected);
    PersonMatcher withAge(Matcher<Integer> matching);
}

We then use a static method on a class called MagicMatcher to obtain a dynamic proxy which implements this interface, capturing the conditions expressed through the method calls:

static PersonMatcher aPerson() {
    return MagicMatcher.proxying(PersonMatcher.class);
}

Each method call is “interpreted” by the proxy, which derives a property name (“age”) from the method name (“withAge”) and figures out which method on the matched object to call to get the property value (“getAge”). The property names and corresponding matchers are stored until match or describeMismatch are called on the proxy (which is why the interface must extend Matcher<Person>), at which point they are used to extract and test the object’s properties and if necessary build a mismatch report.

This approach is lightweight enough that we can introduce new custom matcher interfaces wherever we need them, re-use them across tests, and get all the benefits of writing custom Hamcrest matchers without actually having to write their implementations. All of the information needed to generate the matcher’s behaviour is present in its interface: we only need to implement, just once, the logic to interpret it and create a suitableInvocationHandler.

In next post I’m going to build up a small library of useful, composable functions for working with dynamic proxies in Java 8, and demonstrate some ways in which these functions can be used to implement a variety of proxying behaviours, including interception and the creation of “magic” objects. The source code for this library, including an implementation of the MagicMatcher class discussed in this post, can be found on github.

 

posted @ 2016-03-16 15:08  princessd8251  阅读(187)  评论(0编辑  收藏  举报