Alpha: Lightweight Dynamic Proxy API

I have added an alpha version of a dynamic proxy API to fluent reflection.

It allows you to define Java dynamic proxies using a light-weight fluent syntax. It uses the same reusable hamcrest matchers that are used throughout the rest of the fluent reflection API.

Overview

For example, to proxy a simple interface like this

interface TwoQueryMethod {
   int methodA();
 
   int methodB();
}

You define a new class which extends com.lexicalscope.fluentreflection.dynamicproxy.Implementing. You can define as many methods as you like inside this class. Each method should start by asserting what it is proxying by declaring a hamcrest matcher to match against the method currently being proxied. The first matching method found will be executed.

In this simple example each method is matched by exact name, but the matchers can be as complex as you need them to be:

final TwoQueryMethod dynamicProxy = dynamicProxy(new Implementing<TwoQueryMethod>() {
   public void bodyA() {
      whenProxying(callableHasName("methodA"));
      returnValue(42);
   }
 
   public void bodyB() {
      whenProxying(callableHasName("methodB"));
      returnValue(24);
   }
});

Method Arguments

The arguments of the original method are available from the context provided by the Implementing base class

interface MethodWithArgument {
   int method(int argument);
   String method(String argument);
}
 
final MethodWithArgument dynamicProxy = dynamicProxy(new Implementing<MethodWithArgument>() {
   public void body() {
      returnValue(args()[0]);
   }
});

An alternative approach can be used if you know in advance what the arguments of the method will be. In this example a matcher which matches against the arguments of each body is implied. The body will only be called if the proxied method call has arguments that can be used to satisfy the requirements of the body:

final MethodWithArgument dynamicProxy = dynamicProxy(new Implementing<MethodWithArgument>() {
   public int body(final int agument)
   {
      return 42;
   }
 
   public String body(final String agument)
   {
      return "42";
   }
});

Introducing Fluent Reflection

I am in the processes of releasing the first alpha build of fluent-reflection, you can get the snapshot version from the fluent-reflection snapshot repository.

Motivation

Reflection code is difficult to write in Java, and even harder to read once it has been written. Although the Java Reflection API allows complete access to the Java runtime type system, the complexity of using the Java Reflection API reduces its usefulness. I don’t want to have to write any more code like this:

final List<Method> getMethods = new ArrayList<Method>();
 
Class<? super Bean> klass = Bean.class;
while (klass != null) {
   final Method[] declaredMethods = klass.getDeclaredMethods();
   for (final Method method : declaredMethods) {
      if (method.getName().startsWith("get") && 
          method.getParameterTypes().length == 0 && 
          !method.getReturnType().equals(void.class)) {
         getMethods.add(method);
      }
   }
   klass = klass.getSuperclass();
   [...]
}

Solution

There are three APIs which have heavily influenced the design of the fluent-reflection API.

  1. LambdaJ
  2. Hamcrest
  3. Jmock 2

All of them are examples of a style of API design known as fluent interfaces. They use a combination of static methods, method chaining, generic methods, static factory methods and object scoping to produce a type safe and readable programming style.

In particular, the combination of method chaining and generic methods can help the user discover the usage of each part of the API and also takes advantage of Java’s static type system to prevent programming errors.

Using fluent-reflection the above code can be re-written in declarative style:

final List<ReflectedMethod> getMethods = 
   object(new Bean()).methods(
      callableHasNameStartingWith("get").
      and(callableHasNoArguments()).
      and(not(callableHasVoidReturn())));

Extensibility

The query methods in the API are given queries described by Hamcrest Matchers. So users can easily construct custom and reusable queries.

For example, this Matcher can select methods with a particular annotation:

class MatcherAnnotatedWith extends ReflectionMatcher<ReflectedAnnotated> {
    private final Class<? extends Annotation> annotation;
 
    public MatcherCallableAnnotatedWith(final Class<? extends Annotation> annotation) {
        this.annotation = annotation;
    }
 
    @Override protected boolean matchesSafely(final ReflectedAnnotated item) {
        return item.annotation(reflectedTypeReflectingOn(annotation)) != null;
    }
 
    @Override public void describeTo(final Description description) {
        description.appendText("callable annotated with ").appendValue(annotation);
    }
}

Mashability

Because the API uses the de facto standard Hamcrest matchers, and the Java collections framework it can be combined with other libraries in powerful ways.

For example, calling all of the @PostConstruct methods on an Object using LambdaJ and fluent-reflection:

forEach(
   object(subject).methods(annotatedWith(PostConstruct.class)),
   ReflectedMethod.class).call();