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.
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:
updateModelAfterPost
. This is usually not necessary, but unfortunately it was necessary for some controllers.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 asinvokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
,
and maybehandle(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>
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. SimpleControllerHandlerAdapter
, but I also had to extend it with my own class, because it doesn't implement the Ordered
interface.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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With