Skip to content

Instantly share code, notes, and snippets.

@danbev
Created August 9, 2012 16:09
Show Gist options
  • Save danbev/3305522 to your computer and use it in GitHub Desktop.
Save danbev/3305522 to your computer and use it in GitHub Desktop.
Route definitions explained

Below is how routes are defined in aerogear-controller:

Routes routes = new AbstractRoutingModule() {
    @Override
    public void configuration() {
        route()
                .from("/home")
                .on(GET)
                .to(SampleController.class).index();
    }
}.build();

Now, you might be wondering why the index() method is not actually called when the above code is executed. You might stick a System.out.println statement in index() to verify this.

So how does this work then?
When you invoke the to() method, what gets executed is the following method of RouteDescriptor:

   @Override
    public <T> T to(Class<T> clazz) {
        this.targetClass = clazz;
        try {
            Object o = Enhancer.create(clazz, new MyMethodInterceptor(this));
            return (T) o;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

What is happening here is that we are creating a proxy using cglib for the passed in target class, which in this example is the SampleController.class. When a method is called on the proxied instance, for example index(), MyMethodInterceptor's intercept() method will be invoked:

private static class MyMethodInterceptor implements MethodInterceptor {
    private final RouteDescriptor routeDescriptor;

    public MyMethodInterceptor(RouteDescriptor routeDescriptor) {
        this.routeDescriptor = routeDescriptor;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        this.routeDescriptor.targetMethod = method;
        this.routeDescriptor.args = args;
        return null;
    }
}

Let's just recap for one sec, the following line of code:

.to(SampleController.class).index();

creates a new proxied instance of the class SampleController and when we call index() on that proxied instance, this will set the RouteDescriptors targetMethod instance variable (args will be null since index() does not accept arguments). Notice that the proxied method does not delegate to the target instance so the real index() method implementation will not be invoked at this stage.

So how are the target methods, like index(), invoked then?
Well, the proxy stored the targetMethod and its arguments (if any) which will later be used to create a Route. For example, RouteBuilderImpl looks like this:

public Route build() {
    return new DefaultRoute(routeDescriptor.getPath(), routeDescriptor.getMethods(), routeDescriptor.getTargetClass(), routeDescriptor.getTargetMethod());
}

We can see here that targetMethod is used to create a DefaultRoute. Don't confuse this with getMethods() which refers to the HTTP methods. The actual call later to the index() method implementation is done from DefaultRouter's dispatch method:

Route route = routes.routeFor(extractMethod(request), requestPath);
Object[] params;
if (route.isParameterized()) {
    params = extractPathParameters(requestPath, route);
} else {
    params = extractParameters(request, route);
}
Object result = route.getTargetMethod().invoke(getController(route), params);

Notice that this is done with normal JDK reflection.

Why not use the "Dynamic Proxy" feature in the JDK instead of introducing a new dependency to the project?
I believe the reason for this is that dynamic proxies only work with interfaces, and the class specified in the to() method could be either an interface or a class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment