16

We are migrating a struts application over to Spring MVC and utilizing the @Controller annotation to direct pages to various method calls.

I'm having trouble determining a good strategy for reuse though.

We basically do the same things in many of our pages:

prepareView(..., ...); //Various params -- could likely be standardized

if (!allowedToView()) {
    mav.setViewName(injectedErrorPage);
}

performBusinessLogic(..., ...);  //Various params -- not seeing how to standardize

persistEntities();
finalizeView(..., ...);  // Various params -- could likely be standardized

What strategies are used for creating a final method which will allow the developers to "forget" about these processes? I'd thought about making an abstract class, but there really isn't a way I'm seeing to "standardize" this due to differences in what each method will take.

For instance we have the following:

@RequestMapping("params="assign", method=RequestMethod.Post)
public ModelAndView assign(@SessionAttribute(value="sessionAttr") Pojo pojo,
                           @ModelAttribute("command") CommandPojo commandPojo,
                           BindingResult result) {
    //Follows pattern above
}

@RequestMapping()
public ModelAndView filterResults(@SessionAttribute(value="sessionAttr") Pojo pojo,
                                  @RequestAttribute("requestAttr") String requestAttr,
                                  @ModelAttribute("command") CommandPojo2 commandPojo2,
                                  BindingResult result) {

    //Follows pattern above
}

Having a final method would require this to be broken into two POJOs (which would then call the descriptive functions). My immediate concern there is how do we deal with different parameters coming into this final method? I don't see any way to handle this situation.

It'd be nice if we could still have this "final" method with protected functions which we could override where needed.

Scott
  • 9,458
  • 7
  • 54
  • 81
  • Note: The SessionAttribute and RequestAttribute are custom annotations we created, they aren't official by any means, we just preferred to be able to use those instead of coupling us to the Session / Request. It also allows us to have non-required SessionAttributes (which the offical spring annotation does not support). – Scott Mar 10 '11 at 21:12
  • If you told us what prepare and finalize view actually do, it might be easier to assess if there is a common Spring idiom for the task. – Affe Mar 10 '11 at 21:42
  • 2
    Have you considered using interceptors? – three-cups Mar 10 '11 at 22:38
  • Could you leverage a parameter collection? Kind of a dictionary like bag of parameters? – Nix Mar 10 '11 at 23:05
  • @three_cups_of_java This might look like our best bet. However we do lose some nice features (No way to pull the ModelAttributes out -- without adding an extra object onto the Model and pulling it in the postHandle, we also inject the views to go to next into our Controllers, that would also need be added onto the model (or at least part of it), but it would allow us to do the common work in the interceptor, and allow the controller code to simply do the business logic required. – Scott Mar 17 '11 at 13:10

4 Answers4

5

I have the same problem as you. I don't have a clean solution yet, but I believe that I made some progress so I thought I'd 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 don't 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 can't 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 interceptor's 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 haven't 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 you'll 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 doesn't 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 Spring's SimpleControllerHandlerAdapter, but I also had to extend it with my own class, because it doesn't implement the Ordered interface.
  • I had a problem defining the validator, because I didn't 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;
    }


}
Community
  • 1
  • 1
Yoni
  • 10,171
  • 9
  • 55
  • 72
  • So did you ["unroll" mvc:annotation-driven](http://stackoverflow.com/questions/3693397/howto-get-rid-of-mvcannotation-driven)? I haven't unrolled yet, but was able to add [Flash Scope](https://jira.springsource.org/browse/MOD-458) without needing to unroll the XML, but don't see an easy override for the handler (or is it as simple as creating a bean id with handlerMapping? – Scott Mar 23 '11 at 10:41
  • Yes, I unrolled it. Not because I don't like mvc:annotation-driven, but because I am trying to integrate the new features and our legacy system so they live "happily" side-by-side in the same context. It is a lot easier after unrolling – Yoni Mar 23 '11 at 12:05
  • I'd love to see any further results you've gotten. I will likely be granting you the bounty in the morning. – Scott Mar 29 '11 at 01:51
  • Hey Scott, after all the trouble I went through, I found out that the interceptors DO have access to the handler object! Have a look at the api of HandlerInterceptor preHandle and postHandle methods. I suspect that you can use the same code snippets that I showed, but in an interceptor instead of the custom handler adapter. – Yoni Mar 30 '11 at 07:08
  • That's correct. My only gripe at that point is we will lose some of our ability to test things, as we will now magically have access to variables which are now no longer needed as parameters. – Scott Mar 30 '11 at 13:28
  • Another thing I found is that in the interceptor you can't catch the exception that the handler method throws. You need an exception resolver for that. With the handler adapter approach, you combine the pre, post, and exception handling to one single method – Yoni Mar 30 '11 at 15:05
0

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?

Nilesh
  • 4,137
  • 6
  • 39
  • 53
  • The template method is my end goal. I'm looking for concrete examples of getting us there (with params that vary from one function to the next). All examples I have seen of template methods take the same params. Perhaps there is a more elegant/flexible solution than what I've seen though. – Scott Mar 11 '11 at 18:02
0

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.

javanna
  • 59,145
  • 14
  • 144
  • 125
Sherin Syriac
  • 447
  • 6
  • 13
  • Avoid 'generalizing' parameters in a controller. It hides the functional relationships the controller has with its dependent services for little gain. – Justin Mar 22 '11 at 20:41
0

If you want reusability you really should look into spring webflow, if you haven’t already. In short, webflow is an advanced spring mvc contoller that allows better separation between you view layer and your business logic. The controller accepts request, maps and validates your model, delegates requests to the correct business services, and finally decides which view to render based on the outcome of services called, of state of the model.

Everything is configured through xml, which gives you a single point where all your webflow logic and navigation is located (there is an eclipse plugin to visualize the navigation if you don’t like xml). Spring webflow also plays nice with other mvc controllers if you need to handle some requests the old fashioned way. And last but not least, spring webflow adds some scopes for your variables that are very handy. Besides request , session and application, you also get flow and conversation scope, which is kind of like session scope, but only for the current application window. That means you can have multiple windows/tabs in your browser without any of these interfering with each other.

But you should check it out for yourself, there is a small reference guide available on the spring website, as well as multiple demo’s in their svn repo. Also, the book “spring in action” touches the subject of webflow. Hope this is useful.

http://www.springsource.org/webflow

Lestat
  • 1