Handling Exceptions like ServletRequestBindingException in Spring rather than Servlet Container - spring-mvc

I am using springmvc for a REST project and whenever the client calls a rest resource with the wrong HTTP method, a servletrequestbindingexception is thrown. I cannot handle these exceptions with a #ExceptionHandler in the controller, as it happens not within the handler method but in the spring mapping layer.
Currently I declared a web.xml exception handling, this works:
<error-page>
<exception-type>org.springframework.web.bind.ServletRequestBindingException</exception-type>
<location>/servletRequestBindingException.jsp</location>
</error-page>
<error-page>
<error-code>405</error-code>
<location>/methodNotSupported.jsp</location>
</error-page>
I'd rather use spring exception handling though. For example I'd like to create a dynamic response based on teh incoming Accept header, so either writing out json or xml for a rest exception for example. The best would be to return an object from this handler that would automatically be converted to json or xml just like a normal dto returned from a handler.
Is there a way to catch these lower level mapping exceptions?

You can't use #ExceptionHandler (since as you say, this is for dealing exceptions thrown from within the handler code), but you can still use the HandlerExceptionResolver framework to do this.
By default, DispatcherServlet registers an instance of DefaultHandlerExceptionResolver:
Default implementation of the HandlerExceptionResolver interface that resolves standard Spring exceptions and translates them to corresponding HTTP status codes.
The generation of the HTTP 405 is actually handled in this class, by catching HttpRequestMethodNotSupportedException thrown by the handler-mapping code.
So if you want to handle this exception differently, you can provide your own implementation of HandlerExceptionResolver. It's probably easiest to subclass DefaultHandlerExceptionResolver and override the handleHttpRequestMethodNotSupported method, returning your ModelAndView from there.

Be careful to include the default exception resolvers if you include your own. If you are using annotated #Exception handlers you need to explicitly load these or they will no longer function.
In this case, FooBarHandlerExceptionResolver extends DefaultHandlerExceptionResolver and provides a method that the default resolver doesn't cover. This lets FooBarHandlerExceptionResolver handle class-level exceptions that I couldn't catch with annotated #Exception handler methods.
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver">
<property name="order" value="1"/>
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver" >
<property name="order" value="2"/>
</bean>
<bean class="com.company.package.foo.FooBarHandlerExceptionResolver">
<property name="order" value="3"/>
</bean>
Here is the exception resolver
public class FooBarHandlerExceptionResolver extends DefaultHandlerExceptionResolver {
#Override
protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
try {
if (ex instanceof UnsatisfiedServletRequestParameterException) {
return handleUnsatisfiedServletRequestParameter((UnsatisfiedServletRequestParameterException) ex, request, response,
handler);
}else {
super.doResolveException(request,response,handler,ex);
}
}
catch(Exception handlerException){
logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
}
return null;
}
protected ModelAndView handleUnsatisfiedServletRequestParameter(UnsatisfiedServletRequestParameterException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
logger.warn(ex.getMessage());
return new ModelAndView("blank", new ModelMap("reason", ex.getMessage()));
}
}

Related

my ConversionService encountered 500 error code

I am learning the spring mvc and when i try to use the ConversionService ,i encounterrd 500
#RequestMapping("/handle81")
public String handle81(#RequestParam("user")User user,ModelMap modelMap) {
System.out.println(user);
modelMap.put("user", user);
return "/user/success";
}
this is the handler method ,i've put the #RequestMapping("/user") at the class
and the converter
public class StringToUserConverter implements Converter<String, User> {
public User convert(String source) {
System.out.println(source);
User user=new User();
String[] item=source.split(":");
user.setUserName(item[0]);
user.setPassword(item[1]);
user.setName(item[2]);
return user;
}
}
<mvc:annotation-driven conversion-service="conversionService" />
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.baobaotao.domain.StringToUserConverter" />
</list>
</property>
</bean>
so when i browse
http://localhost:8080/spring-mvc/user/handle81.html?user=asdf:asdf:fdas
it gets 500 and prints nothing at the console(i use maven-jetty to do the test)
thx for helping~
I think your Request URL may not be not matching. You specify "/handle81" in the annotation, but are requesting "/handle81.html".
It's hard to tell, without further information, whether the problem is matching & dispatching the request to the handler; or in the conversion.
Try another handler with the parameter of type String, and see whether you can call that successfully. At least you'll then know where the problem is.
And what is the exception stack-trace? Why didn't you post it? That's your most important clue & you should always post the ex message & top few lines/ where it was thrown, when you ask a question. It should be in either the application or Tomcat/ other server logs.

Simple web application filter doesn't filter request

Starting my way with STS and created a new basic "Hello World" Spring MVC project.
I wanted to add a filter to my app so I created a filter (HelloWorldFilter.java) with the following doFilter method:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("Entering Filter");
request.setAttribute("hello", "Hello World from HelloWorldFilter!");
chain.doFilter(request, response);
System.out.println("Exiting HelloWorldFilter");
}
According to what I read it (my filter) should also be defined in the application context as a spring bean (Spring delegates it to my filter - from this manual )
So in my application context I have:
<bean id="helloWorldFilter" class="com.yl.mvc.filters.HelloWorldFilter"> </bean>
My web.xml contains the following:
<filter>
<display-name>HelloWorldFilter</display-name>
<filter-name>HelloWorldFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>HelloWorldFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
In my .jsp file I added:
<P><%=request.getAttribute("hello")%></P>
But all I see in my web-page is null (I expected Hello World from HelloWorldFilter!).
The filter doesn't even get invoked..
Am I missing something here?
Thanks in advance,
Yogi
Ok go it solved.
The filter (which is a spring bean) must have the same name in the bean definition (in the application context) as that in the filter-name element (in the web.xml).
In my case I had in my application context:
<bean id="helloWorldFilter"...
and in my web.xml:
<filter-name>HelloWorldFilter</filter-name>
So once it is with a capital H and once a small h - which caused the issue.
To resolve it I simply changed the bean id in my application context to HelloWorldFilter.

Using a HandlerInterceptor to add model attributes with Spring Web Flow

I have a HandlerInterceptor to add some "global" model variables. It works.
Now, I try to reuse it in Spring Web Flow, for the same reason.
But HandlerInterceptors have the ModelAndView parameter set to NULL under Spring Web Flow (couldn't figure why, but it's a fact).
I have referenced my interceptor in the FlowHandlerMapping bean :
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<property name="order" value="0" />
<property name="flowRegistry" ref="flowRegistry" />
<property name="interceptors">
<list>
<ref bean="myInterceptor" />
</list>
</property>
</bean>
How can I add variables to the model ?
Is there a workaround, with the request parameter for example ?
Starting with Spring Webflow 2, the ModelAndView object is not generated anymore (see this post (and thread) at the SpringSource forum).
The FlowHandlerAdapter handle() function does not generate a ModedAndView anymore (it just returns null) even if this function is :
public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object handler)
So overriding this function is pointless, but this function creates a ServletExternalContext object, which holds all the flow variable, by calling its method :
protected ServletExternalContext createServletExternalContext(
HttpServletRequest request, HttpServletResponse response)
By overriding this function you can pretty much do what you want with this flow variables.
To do this, just create a class that extends FlowHandlerAdapter, register it instead of FlowHandlerAdapter and override the createServletExternalContext function.
Basically you use ServletExternalContext.getSessionMap() to access a SharedAttributeMap and register your properties.
As you have access to the HttpServletRequest and HttpServletResponse objects, this method can act petty much like a HandlerInterceptorAdapter.postHandle function.
See an example below.
I left out how to use generic way to reuse the same code for a HandlerInterceptor for the MVC and this object but it's easy to code, by implementing HandlerInterceptor.
MyFlowHandlerAdapter :
package my.package;
public class MyFlowHandlerAdapter extends FlowHandlerAdapter {
#Override
protected ServletExternalContext createServletExternalContext(
HttpServletRequest request,
HttpServletResponse response) {
ServletExternalContext context =
super.createServletExternalContext(request,response);
context.getSessionMap().put("myproperty", "myvalue");
return context;
}
}
You have the FlowHandlerAdapter object defined in you webflow-context.xml file like that :
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor"/>
</bean>
Just replace it with :
<bean class="my.package.MyFlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor"/>
</bean>
ModelAndView can be null when the interceptor is processing an Ajax request.
Just check if ModelAndView is null. If not, that's because the interceptor is processing a view-model, so you can add your variables at this time.

Externalize #InitBinder initialization into WebBindingInitializer

There are two major means of data binding initialization, but there is a drawback in the oldschool one, that I can't figure out. This annotation way is great :
#InitBinder("order")
public void initBinder(WebDataBinder binder) {
// Problem is that I want to set allowed and restricted fields - can be done here
binder.setAllowedFields(allowedFields.split(","));
}
but I can't be done with ConfigurableWebBindingInitializer. First off, the binder instance is created in AnnotationMethodHandlerAdapter and initializer is passed the binder instance somewhere in HandlerMethodInvoker so I can't set it up... I can't do something like this :
<bean id="codesResolver" class="org.springframework.validation.DefaultMessageCodesResolver" />
<bean id="binder" class="org.springframework.web.portlet.bind.PortletRequestDataBinder" scope="prototype">
<property name="allowedFields" value="${allowedFields}" />
<aop:scoped-proxy />
</bean>
<bean id="webBindingInitializer" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<property name="messageCodesResolver" ref="codesResolver" />
</bean>
Because binder instance is passed into it in handlerAdapter. How can I set up the binder then ?
There is no way of setting it up in xml configuration. You must implement your custom WebBindingInitializer ... The ConfigurableWebBindingInitializer is obviously missing the possibility of setting up allowed and restricted fields...
Or you can vote up SPR-8601
This is very old, however for anyone that dislike the use of annotations in production code (like me) here is a solution I found to add a init binder without use of annotations. You only need to overwrite initBinder method that extends from most of base controllers provided by Spring:
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception
{
System.out.println("Binding!!!!!");
super.initBinder(request, binder);
binder.registerCustomEditor(Double.class, new CurrencyPropertyEditor());
}
Where my CurrencyPropertyEditor class is a subclass of java.beans.PropertyEditorSupport with getAsText, getValue, setValue and setAsText methods overwrited as well.
Hope it helps!!!

Spring MVC 3.0 with Apache Tiles - Mutiple Forms in one page

I'm using spring mvc (3.0) with apache tiles in my project. I have multiple forms in a single page rendered through tiles.
The login form and search form are common to most pages. The “body” in the tile definition keeps changing.
So, as shown below, in all of my mvc controllers I have to explicitly set command object in the corresponding model.
1. model.put("userBO", userBO);
2. model.put("searchBO", searchBO);
Is there a way I can move this part of the code to a common place or a global controller, so that I don’t have to write these two lines in all the controllers that I write?
You can use an interceptor to do this in a postHandle:
public class DefaultModelInterceptor extends HandlerInterceptorAdapter {
#Override
public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler,
final ModelAndView modelAndView) throws Exception {
modelAndView.addObject("userBO", userBO);
modelAndView.addObject("searchBO", searchBO);
super.postHandle(request, response, handler, modelAndView);
}
}
This can then be wired in your spring servlet config:
<mvc:interceptors>
<bean class="my.package.DefaultModelInterceptor"/>
</mvc:interceptors>

Resources