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:
- 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).
- Individual controllers can no longer override the behavior of
updateModelAfterPost
. This is usually not necessary, but unfortunately it was necessary for some controllers.
- 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;
}
}