java – Spring MVC Controller Design

java – Spring MVC Controller Design

I have the same problem as you. I dont have a clean solution yet, but I believe that I made some progress so I thought Id share with you what I have found so far.

I explored the use of interceptors as suggested by three_cups_of_java, but I run into various problems (described below). Currently I am trying to use a custom AnnotationMethodHandlerAdapter, but I am not done yet with this effort.

Interceptors

Since the interceptors dont have access to the controller object that they intercept (correction: they do have access to it, but with limited control over the execution flow), the controller and the interceptor have to communicate through objects in the session.

Here is a somewhat simplified example of what I mean:

In our old architecture, we have our own base controller that everyone extends. It itself extends MultiActionController, and adds some custom behavior – like in your example, updating a server-side view after post request before invoking the handler method. This works because all the controllers provide an implementation of a template method (e.g. getViewKeyInSession()).

Thus, the custom code in the base controller looks roughly like this:

// inside handleRequestInternal method
if (request.getMethod().equals(POST) {
    updateViewAfterPost (session.get(getViewKeyInSession());
}
return super.handleRequestInternal();

Now, when we moved this code to the interceptor, we run into several problems:

  1. The interceptor cant invoke getViewKeyInSession(), forcing us to use the same session key for all controllers (not good), or adhere to some convention that the session key for the view is based on the url or a param of the request (so far this is not good either).
  2. Individual controllers can no longer override the behavior of updateModelAfterPost. This is usually not necessary, but unfortunately it was necessary for some controllers.
  3. If the controller provides an implementation of updateModelAfterPost and wants to signal to the interceptor that it is not interested in the interceptors help, it needs to do so by putting a marker object in the session for the interceptor to look at, and it needs to do it during the previous GET request (also not good and not flexible).

Using a Custom AnnotationMethodHandlerAdapter

Currently I am looking at specifying the DefaultAnnotationHandlerMapping directly in my xml (instead of mvc:annotation-driven) and then supplying it with a custom AnnotationMethodHandlerAdapter.

As I said earlier, I havent made enough progress to present full results, however the direction that I am aiming at is this:

I think of AnnotationMethodHandlerAdapter as a Spring-supplied MultiActionController, but for pojo controllers. For example, I already know how to plug to it my own method resolver (see this question) and other Spring goodies.

This adapter has several methods that you can override, such as
invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler),
and maybe
handle(HttpServletRequest request, HttpServletResponse response, Object handler)
as well.

In your custom code, you can inspect the handler class, and then act accordingly. To continue my previous example, if the handler class has a method updateViewAfterPost or if it implements a certain interface, then you can invoke that method, and then call super to let spring proceed with the regular invocation. Thus, the code looks roughly like this:

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    // inspect handler object, look for marker interface, methods and/or annotations
    // perform pre-processing on the handler object
    // e.g. handler.updateViewAfterPost(request, response)
    ModelAndView mav = super.handle (request, response, handler);
    // post-processing on the handler object
    return mav;
}

(Of course, this is just a toy example. In real code youll need better exception handling)

UPDATE:

I tried the above strategy with a custom AnnotationMethodHandlerAdapter, and it indeed works. I used a marker interface on my pojo controller, and introduced only one new method named updateModelAfterPost to the life-cycle, and it works as expected.

There are several small caveats that I ran into, mainly because I was combining the old ways with the new ways in the same mvc context. Below you can see the changes I made to the xml context, followed by a list of the issues that I think are worth highlighting.

<bean class=org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping>
    <property name=order value=2 />
 </bean>

<bean class=com.sample.MyAnnotationMethodHandlerAdapter>
    <property name=order value=2 />
</bean>

<bean class=com.sample.MySimpleControllerHandlerAdapter >
    <property name=order value=1 />
</bean>

<bean class=org.springframework.web.servlet.handler.SimpleUrlHandlerMapping>
    <property name=order value=1 />
    <property name=mappings>
        <props>
            ...
        </props>
    </property>
</bean>
  • As mentioned in the comment, I unrolled the <mvc:annotation-driven> short-hand. I had to explicitly define two handler mapping, and also define two handler adapters.
  • Unfortunately in my legacy code some controllers are transcational and are proxied by cglib. The AnnotationMethodHandlerAdapter doesnt cope well with that, therefore I set the order of elements such that the legacy handler mapping and handler adapter act first, and the annotation-based handler mapping and handler adapter act second.
  • I had to define explicitly Springs SimpleControllerHandlerAdapter, but I also had to extend it with my own class, because it doesnt implement the Ordered interface.
  • I had a problem defining the validator, because I didnt have the jar for jsr-303. Therefore I dropped the declaration of validators and conversion service. The above xml snippet is exactly what I use, it is not a trimmed-down version simplified for the sake of the answer.

and finally, here is the code for the relevant classes:

package com.sample;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;

public class MyAnnotationMethodHandlerAdapter extends AnnotationMethodHandlerAdapter {

    protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof MyMarkerInterface) {
            MyMarkerInterface handler2 = (MyMarkerInterface) handler;
            handler2.updateModelAfterPost(request);
        }
        return super.invokeHandlerMethod(request, response, handler);
    }

}


package com.sample;

import org.springframework.core.Ordered;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;

public class MySimpleControllerHandlerAdapter extends SimpleControllerHandlerAdapter implements Ordered {

    private int order = 0;

    public int getOrder() {
        return order;
    }

    public void setOrder(int order) {
        this.order = order;
    }


}

Could you implement a base class as you are suggesting and force a Template Method design pattern and also to borrow from what Nix said in his earlier comment to your question about leveraging a parameter collection in this base class?

Does that help?

java – Spring MVC Controller Design

If the parameters are varying from function to function I think @Nix suggestion of parameter collection is a good one. Alternatively you could use var arg of objects. But you might need to have a check to see if all parameters are present before a function is called like a Pre condition check.
Or maybe a combination of both like, you would know that some of the parameters are always needed and others optional. So use varargs for the optional likethe following for filterResults

public ModelAndView filterResults(@SessionAttribute(value=sessionAttr) Pojo pojo,
                                  @RequestAttribute(requestAttr) String requestAttr,
                                  @ModelAttribute(command) CommandPojo2 commandPojo2,
                                  Object...restOfParameters){}

This could be combined with the template pattern that is disscussed earlier.

Leave a Reply

Your email address will not be published. Required fields are marked *