I want to create an exception handler which will intercept all controllers in my project. Is that possible to do? Looks like I have to put a handler method in each controller. Thanks for your help. I have a spring controller that sends Json response. So if an exception happens I want to send an error response which can be controlled from one place.
(I found a way to implement it in Spring 3.1, this is described in the second part of this answer)
See chapter 16.11 Handling exceptions of Spring Reference
There are some more ways than using @ExceptionHandler
(see gouki's answer)
If you do not have a specific logic for the exception, but only specifc view then you could use the SimpleMappingExceptionResolver, which is at least an implementation of the HandlerExceptionResolver
where you can specify an Exception name pattern and the view (jsp) which is shown when the exception is thrown. For example:
<bean
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"
p:defaultErrorView="uncaughtException">
<property name="exceptionMappings">
<props>
<prop key=".DataAccessException">dataAccessFailure</prop>
<prop key=".TypeMismatchException">resourceNotFound</prop>
<prop key=".AccessDeniedException">accessDenied</prop>
</props>
</property>
</bean>
In Spring 3.2+ one can annotate a class with @ControllerAdvice
, all @ExceptionHandler
methods in this class work in a global way.
In Spring 3.1 there is no @ControllerAdvice
. But with a little hack one could have a similar feature.
The key is the understanding of the way @ExceptionHandler
works. In Spring 3.1 there is a class ExceptionHandlerExceptionResolver
. This class implements (with help of its superclasses) the interface HandlerExceptionResolver
and is responsible invoking the @ExceptionHandler
methods.
The HandlerExceptionResolver
interface has only one Method:
ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex);`.
When the request was handled by a Spring 3.x Controller Method, then this method (represented by org.springframework.web.method.HandlerMethod
) is the handler
parameter.
The ExceptionHandlerExceptionResolver
uses the handler
(HandlerMethod
) to obtain the Controller class and scan it for methods annotated with @ExceptionHandler
. If one of this methods matches the exception (ex
) then this methods get invoked in order to handle the exception. (else null
get returned in order to signal that this exception resolver feels no responsible).
The first idea would be to implement an own HandlerExceptionResolver
that behaves like ExceptionHandlerExceptionResolver
, but instead of search for @ExceptionHandler
in the controller class, it should search for them in one special bean. The drawback would be, that one has to (copy (or subclass ExceptionHandlerExceptionResolver
) and must) configure all nice message converters, argument resolvers and return value handlers by hand (the configuration of the real one and only ExceptionHandlerExceptionResolver
is done by spring automatically). So I came up with another idea:
Implement a simple HandlerExceptionResolver
that "forwards" the exception to THE (already configured) ExceptionHandlerExceptionResolver
, BUT with an modified handler
which points to the bean that contains the global Exception handlers (I call them global, because they do the work for all controllers).
And this is the implementation: GlobalMethodHandlerExeptionResolver
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
public class GlobalMethodHandlerExeptionResolver
implements HandlerExceptionResolver, Ordered {
@Override
public int getOrder() {
return -1; //
}
private ExceptionHandlerExceptionResolver realExceptionResolver;
private List<GlobalMethodExceptionResolverContainer> containers;
@Autowired
public GlobalMethodHandlerExeptionResolver(
ExceptionHandlerExceptionResolver realExceptionResolver,
List<GlobalMethodExceptionResolverContainer> containers) {
this.realExceptionResolver = realExceptionResolver;
this.containers = containers;
}
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
for (GlobalMethodExceptionResolverContainer container : this.containers) {
ModelAndView result = this.realExceptionResolver.resolveException(
request,
response,
handlerMethodPointingGlobalExceptionContainerBean(container),
ex);
if (result != null)
return result;
}
// we feel not responsible
return null;
}
protected HandlerMethod handlerMethodPointingGlobalExceptionContainerBean(
GlobalMethodExceptionResolverContainer container) {
try {
return new HandlerMethod(container,
GlobalMethodExceptionResolverContainer.class.
getMethod("fakeHanderMethod"));
} catch (NoSuchMethodException | SecurityException e) {
throw new RuntimeException(e);
}
}
}
The global Handler has to implement this interface (in order to get found and to implement the fakeHanderMethod
used for the handler
public interface GlobalMethodExceptionResolverContainer {
void fakeHanderMethod();
}
And example for an global Handler:
@Component
public class JsonGlobalExceptionResolver
implements GlobalMethodExceptionResolverContainer {
@Override
public void fakeHanderMethod() {
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ValidationErrorDto handleMethodArgumentNotValidException(
MethodArgumentNotValidException validationException,
Locale locale) {
...
/* map validationException.getBindingResult().getFieldErrors()
* to ValidationErrorDto (custom class) */
return validationErrorDto;
}
}
BTW: You do not need to register the GlobalMethodHandlerExeptionResolver
because spring automatically register all beans that implements HandlerExceptionResolver
for exception resolvers. So a simple <bean class="GlobalMethodHandlerExeptionResolver"/>
is enough.