I have created a custom HandlerInterceptorAdapter to override the postHandle method:
public class AcmeInterceptor extends HandlerInterceptorAdapter {
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
super.postHandle(request, response, handler, modelAndView);
AcmeController controller = (AcmeController) handler;
controller.finalize(modelAndView);
}
}
In the AcmeModel, I define a field annotated with NumberFormat:
public class AcmeModel {
private BigDecimal cost = BigDecimal.valueOf(67890.6789);
@NumberFormat(style = Style.CURRENCY)
public BigDecimal getCost() {
return cost;
}
}
In acme.jsp I use <spring:bind> to output the formatted value:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<spring:bind path="acmeModel.cost">
Cost: <c:out value="${status.value}" />
</spring:bind>
Now, first I try the controller like this:
@Controller
public class AcmeController {
@RequestMapping("/")
public ModelAndView index() {
ModelAndView modelAndView = new ModelAndView("WEB-INF/views/acme.jsp");
modelAndView.addObject(new AcmeModel());
return modelAndView;
}
public void finalize(ModelAndView modelAndView) {
}
}
And this is the output I get:
Cost: $67,890.68
Here is the puzzling part. If I move the call to addObject into the body of finalize:
@Controller
public class AcmeController {
@RequestMapping("/")
public ModelAndView index() {
ModelAndView modelAndView = new ModelAndView("WEB-INF/views/acme.jsp");
//modelAndView.addObject(new AcmeModel());
return modelAndView;
}
public void finalize(ModelAndView modelAndView) {
modelAndView.addObject(new AcmeModel());
}
}
Then the output becomes:
Cost: 67890.6789
What is the difference between adding an object to ModelAndView in the handler method as opposed to a normal controller method that affects <spring:bind>?
Edit: Here is the bean definition for the servlet.
<beans ...>
<mvc:annotation-driven />
<context:component-scan base-package="com.example" />
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean id="acmeInterceptor" class="com.example.numberformat.AcmeInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
</beans>
Spring handles the formatting via the BindingResult object. When you add values to the model in a controller then spring validates them, creates a BindingResult and adds it to the Model. This mechanism only works for the controllers, not for the interceptors, may it be intentional or not I am not sure but it definitely works like that. If you want binding to be done for values set in an interceptor then you need to do the binding yourslef. Modify your interceptor like this :
as you can see this piece of code adds the extra BindingResult object to the model. It needs to get hold of the ConversionService. For now it is done by getting it from the controller. The Controller can access it using the @InitBinder annotation like this
An another option is to implement the preHandle method in the interceptor instead of the postHandle. PreHandle can’t access the model, but it can access the request, so in the preHandle you can add AcmeModel to the reqiest as an attribute then in your controller you can get the value, add it to the model, then it will be Bound by Spring just like the rest of the model attributes.