Spring MVC exception handling with HandlerExceptionResolver - spring-mvc

I am currently trying to use HandlerExceptionResolver for exception handling in a Spring MVC project.
I want to handle normal exceptions via resolveException as well as 404's via
handleNoSuchRequestHandlingMethod.
Depending on the request type JSON or text/html the exception response should be returned appropriately.
resolveException works now.
But handleNoSuchRequestHandlingMethod is giving me a headache. It's never called!
According to the docu the method should be called on 404 errors
http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.html
What am I doing wrong...
This is what I have so far.
public class JsonExceptionResolver implements HandlerExceptionResolver {
protected final Log logger = LogFactory.getLog(getClass());
public ModelAndView resolveException(HttpServletRequest request,
if (exception instanceof NoSuchRequestHandlingMethodException) {
return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) exception, request, response, handler);
}
...
}
public ModelAndView handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler){
logger.info("Handle my exception!!!");
ModelAndView mav = new ModelAndView();
boolean isJSON = request.getHeader("Accept").equals("application/json");
if(isJSON){
...
}else{
..
}
return mav;
}
}
EDIT with DefaultHandlerExceptionResolver:
public class MyExceptionResolver extends DefaultHandlerExceptionResolver {
protected final Log logger = LogFactory.getLog(getClass());
#Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) {
logger.warn("An Exception has occured in the application", exception);
logger.info("exception thrown " + exception.getMessage() );
if (exception instanceof NoSuchRequestHandlingMethodException) {
return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) exception, request, response, handler);
}
...
return mav;
}
public ModelAndView handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler){
logger.info("Handle my exception!!!");
ModelAndView mav = new ModelAndView();
boolean isJSON = request.getHeader("Accept").equals("application/json");
if(isJSON){
...
}else{
...
}
return mav;
}
}
The above code still has no effect.
Any other ideas?

According to Juergen Hoeller from Spring, it isn't possible with the HandlerExceptionResolver because it only works for sub-mapping e.g.
you have a controller mapped to /account/** and accesss a method from acount where no mapping exists like /acount/notExists than it should work.
I will open a JIRA improvement ticket for this functionality
EDIT:
JIRA ticket about this issue
https://jira.springsource.org/browse/SPR-8837?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=72648#comment-72648

handleNoSuchRequestHandlingMethod isn't part of the HandlerExceptionResolver interface, so just declaring a method of that name will do nothing. It's a protected method specific to DefaultHandlerExceptionResolver, and is called from its resolveException method (which is part of the interface):
if (ex instanceof NoSuchRequestHandlingMethodException) {
return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response, handler);
}
To reproduce the same functionality, you can either subclass DefaultHandlerExceptionResolver and override the methods you need to, or you need to add a case in your resolveException method that handles NoSuchRequestHandlingMethodException.

Related

How can I get RedirectAttributes in HandlerInterceptor.postHandle() method

Background:
A web project uses SpringMvc framework is required to front-back
seperation reconstruct. Frontend is going to use React, and some of the backend's Controllers which used to return jsp view need to return jsonObject. The data passed to jsp through ModelMap now needed to be transferred to json and write back through Response. And to avoid to modify every Controller, I came up with an idea of using Interceptor to get this job down. It works good for those Controllers pass data with ModelMap, but it dosen't for those pass data with RedirectAttributes.
Problem:
Some of the Controllers return redirect and use RedirectAttributes to pass attributes. Since postHandler() has only 4 params: request,response,handler and modelAndView, I can hardly get attributes of RedirectAttributes from these.
Controller code:
#RequestMapping(value="save")
public String save(CarInfoEntity entity, HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes, Model model){
redirectAttributes.addFlashAttribute("message", "success!");
return "redirect:/demo/carInfo/list";
}
Interceptor Code:
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if(modelAndView != null) {
Map<String, Object> map = modelAndView.getModel();
String json = JSON.toJSONString(map, SerializerFeature.DisableCircularReferenceDetect);
logger.debug(json);
if (null != json) {
try {
response.setHeader("Content-type", "application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
PrintWriter pw = response.getWriter();
pw.write(json);
pw.flush();
pw.close();
} catch (IOException e) {
}
}
modelAndView.clear();
}
}
Question 1: Can I get RedirectAttributes in Interceptors?
Question 2: How, if I can?
I tried to use AOP, perfect solved the problem. I found that AOP is more powerful than Intercepter. I can define cut points more flexibly and get all arguments with getArgs() method of ProceedingJoinPoint.

How to catch FeignClient exception

I try to catch exception wich I received from another microservice connected by FeignClient. I've made custom ErrorDecoder, and
public class CustomErrorDecoder implements ErrorDecoder {
private final Logger log = LoggerFactory.getLogger(getClass());
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
log.info("----------------- "+methodKey+" , "+response.status()+" , "+ response.reason());
return new RestApiException(99,99,"");
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
Where RestApiException extends Exception.
#ControllerAdvice
public class GlobalControllerAdvice {
private final Logger log = LoggerFactory.getLogger(getClass());
#ExceptionHandler(RestApiException.class)
public ResponseEntity<RestApiException> handleException(RestApiException exception, HttpServletRequest req) {
log.error("Sending error to client ( "+req.getUserPrincipal().getName()+" ) \"{}\"", exception.getErrMsg());
return new ResponseEntity<RestApiException>(exception, exception.getStatus());
}
#ExceptionHandler(Throwable.class)
public ResponseEntity<RestApiException> handleException(Throwable throwable, HttpServletRequest req) {
RestApiException exception=new RestApiException(HttpStatus.INTERNAL_SERVER_ERROR, 100, 100,
throwable.getMessage());
return handleException(exception, req);
}
As a result, when I get <--- HTTP/1.1 400 Bad Request (5380ms)
I've got default error messages
HttpStatus.INTERNAL_SERVER_ERROR, 100, 100,
throwable.getMessage());
but not expexted custom exception, which I try to set in CustomErrorDecoder.
What I'm doing wrong, why I can't call RetAppiException and return error answer to rest client.
Thanks.
You can't catch the exception of the FeignClient with a #ControllerAdvice. The exception handler will not catch the exceptions generated by the feign client, error decoder..
A simple solution would be catch your feign calls, and then throw the exception you want.
try{
feignClient.method();
} catch(Exception ex){
//throw exceptions you want
throw new YourException();
}
Then you'll be able to handle it:
#ControllerAdvice
public class GlobalControllerAdvice {
private final Logger log = LoggerFactory.getLogger(getClass());
#ExceptionHandler(YourException.class)
public ResponseEntity<RestApiException> handleException(RestApiException exception, HttpServletRequest req) {
//impl
}
}
You can catch feign client exceptions by catching HystrixRuntimeException and casting the getCause() to FeignClientException.
Example:
#ExceptionHandler
public ResponseEntity<Problem> handleFeignException(HystrixRuntimeException ex, NativeWebRequest request) {
final FeignException.FeignClientException feignClientException = (FeignException.FeignClientException) ex.getCause();
return handleException(feignClientException, request);
}

#ExceptionHandler with parameters not working

I am trying to capture all exceptions of some class in my Controller class. It works fine when
I define it like this:
#ExceptionHandler(NoSearchResultException.class)
public String handleNoSearchResultException() {
return "someView";
}
But not if I add any parameters:
#ExceptionHandler(NoSearchResultException.class)
public String handleNoSearchResultException(Exception e) {
return "someView";
}
What could possibly be happening? Also, I've read #ExceptionHandler does not support Model arguments, so how would I pass a parameter (like the error message for instance) to the view in order to offer a dynamic error page?
To pass a parameter to the view I would create a custom Exception class in which you can store any required model parameters (such as error messages). Then in #ExceptionHandler method you can extract those model parameters and make them available in the view. For example:
class RequestException extends RuntimeException {
...
public void setErrorMessages(List<String> errorMsgs) {
this.errorMessages = errorMsgs
}
...
}
#ExceptionHandler(RequestException.class)
public ModelAndView handleNoSearchResultException(RequestException ex) {
ModelAndView mav = new ModelAndView("someView");
mav.addObject("errors", ex.getErrorMessages()); //fetch error messages
return mav;
}
As for parameters, try specifying NoSearchResultException as method parameter instead of it's Exception superclass.
EDIT:
Had a bug in 2nd example return value.
I Solved the problem by passing the custom arguments in request itself.
code is as below :
Controller
#RequestMapping(method = RequestMethod.GET, value = "/exception2")
public String getException1(ModelMap model, #CRequestParam("p") String p, HttpServletRequest request) {
System.out.println("Exception 2 " + p);
request.setAttribute("p", p);
throw new CustomGenericException("1", "2");
}
Exception Handler
#ExceptionHandler(CustomGenericException.class)
public ModelAndView handleCustomException(CustomGenericException ex, HttpServletRequest request) {
ModelAndView model2 = new ModelAndView("error/generic_error");
model2.addObject("exception", ex);
System.out.println(request.getAttribute("p"));
System.out.println("CustomGenericException ");
return model2;
}
here is Sackoverflow question and its answer and
Complete source code is available at git

Accessing current controller executing in DelegatingHandler

I was wondering if it's possible to access the controller being executed (or about to be executed) in the SendAsync method of the DelegatingHandler? I can't seem to figure out how to get access to it, and I figure it's because it executes outside of the controller execution...
Is it possible to refer to it?
No, because message handlers operate on raw HttpRequestMessage or raw HttpResponseMessage (in case of continuations). So really, there is no concept of "current controller executing" with DelegatingHandlers since message handlers will be called before dispatching the request to the controller or (again, in the case of continuations) after the controller returns the reponse.
However, it really depends what you are trying to do.
If you want to know to which controller the request will end up getting routed, you can manually call the mechanism that would internally select the controllers.
public class MyHandler : DelegatingHandler
{
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var config = GlobalConfiguration.Configuration;
var controllerSelector = new DefaultHttpControllerSelector(config);
// descriptor here will contain information about the controller to which the request will be routed. If it's null (i.e. controller not found), it will throw an exception
var descriptor = controllerSelector.SelectController(request);
// continue
return base.SendAsync(request, cancellationToken);
}
}
Extending the #GalacticBoy solution, it would be better to use
public class MyHandler : DelegatingHandler
{
private static IHttpControllerSelector _controllerSelector = null;
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
if (_controllerSelector == null)
{
var config = request.GetConfiguration();
_controllerSelector = config.Services.GetService(typeof(IHttpControllerSelector)) as IHttpControllerSelector;
}
try
{
// descriptor here will contain information about the controller to which the request will be routed. If it's null (i.e. controller not found), it will throw an exception
var descriptor = _controllerSelector.SelectController(request);
}
catch
{
// controller not found
}
// continue
return base.SendAsync(request, cancellationToken);
}
}
Depending on what your doing with the information maybe your fine with getting the information after the request is executed. For example logging the executed controller/action.
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
namespace Example
{
public class SampleHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
HttpResponseMessage response = task.Result;
string actionName = request.GetActionDescriptor().ActionName;
string controllerName = request.GetActionDescriptor().ControllerDescriptor.ControllerName;
// log action/controller or do something else
return response;
}, cancellationToken);
}
}
}

Using Spring 3 #ExceptionHandler with commons FileUpload and SizeLimitExceededException/MaxUploadSizeExceededException

I am having trouble with catching and gracefully handling commons fileupload's FileUploadBase.SizeLimitExceededException or spring's MaxUploadSizeExceededException when uploading large files.
From what I can tell these exceptions are thrown during data binding, before the controller is actually reached, therefore resulting in a 500 and no calling of the exception handler method. Has anyone come across this before, and what is the best way for handling these exceptions properly?
thanks to thetoolman for this simple solution. I extended it a bit. I wanted to leave the file handling untouched and transport the Exception to the Controller.
package myCompany;
public class DropOversizeFilesMultipartResolver extends CommonsMultipartResolver {
/**
* Parse the given servlet request, resolving its multipart elements.
*
* Thanks Alexander Semenov # http://forum.springsource.org/showthread.php?62586
*
* #param request
* the request to parse
* #return the parsing result
*/
#Override
protected MultipartParsingResult parseRequest(final HttpServletRequest request) {
String encoding = determineEncoding(request);
FileUpload fileUpload = prepareFileUpload(encoding);
List fileItems;
try {
fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
} catch (FileUploadBase.SizeLimitExceededException ex) {
request.setAttribute(EXCEPTION_KEY, ex);
fileItems = Collections.EMPTY_LIST;
} catch (FileUploadException ex) {
throw new MultipartException("Could not parse multipart servlet request", ex);
}
return parseFileItems(fileItems, encoding);
}
}
and in the controller
#InitBinder("fileForm")
protected void initBinderDesignForm(WebDataBinder binder) {
binder.setValidator(new FileFormValidator());
}
#RequestMapping(value = "/my/mapping", method = RequestMethod.POST)
public ModelAndView acceptFile(HttpServletRequest request, Model model, FormData formData,
BindingResult result) {
Object exception = request.getAttribute(DropOversizeFilesMultipartResolver.EXCEPTION_KEY);
if (exception != null && FileUploadBase.SizeLimitExceededException.class.equals(exception.getClass())) {
result.rejectValue("file", "<your.message.key>");
LOGGER.error(exception);
}
the spring config remains the same. It would be really nice to have the exception transported to the validator, but I haven't figured out how to do this yet.
I know this is old, but I was looking for a solution to this as well and could not find anything. We are providing RESTful services using Spring and we are doing file upload and were not sure how to handle this. I came up with the following and hopefully it will be useful to someone:
All our exceptions are handled with annotations, so we have our error handler resolver set-up like this:
#Configuration
public class MyConfig{
#Bean
public AnnotationMethodHandlerExceptionResolver exceptionResolver(){
final AnnotationMethodHandlerExceptionResolver resolver = new AnnotationMethodHandlerExceptionResolver();
resolver.setMessageConverters(messageConverters());
resolver;
}
}
Then a common class that can handle the exception
public class MultipartExceptionHandler
{
#ExceptionHandler(MaxUploadSizeExceededException.class)
#ResponseStatus(value = HttpStatus.PRECONDITION_FAILED)
#ResponseBody
protected CustomError handleMaxUploadSizeExceededException(final HttpServletRequest request,
final HttpServletResponse response, final Throwable e)
throws IOException
{
logger.error(e);
CustomError c = new CustomErrorMaxFileSize("Max file size exceeded", MAX_FILE_SIZE);
return c;
}
#ExceptionHandler(MultipartException.class)
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
protected CustomError handleGenericMultipartException(final HttpServletRequest request,
final HttpServletResponse response, final Throwable e)
throws IOException
{
logger.error(e);
CustomError c = new CustomErrorGeneric("There was a problem with the upload");
return c;
}
}
Then we subclass the commons multipart resolver and implement the HandlerExceptionResolver interface
#Component(value="multipartResolver") // Spring expects this name
public class MyMultipartResolver extends CommonsMultipartResolver implements HandlerExceptionResolver
{
// This is the Spring bean that handles exceptions
// We defined this in the Java configuration file
#Resource(name = "exceptionResolver")
private AnnotationMethodHandlerExceptionResolver exceptionResolver;
// The multipart exception handler with the #ExceptionHandler annotation
private final MultipartExceptionHandler multipartExceptionHandler = new MultipartExceptionHandler();
// Spring will call this when there is an exception thrown from this
// multipart resolver
#Override
public ModelAndView resolveException(
final HttpServletRequest request,
final HttpServletResponse response,
final Object handlerParam,
final Exception ex)
{
// Notice that we pass this.multipartExceptionHandler
// and not the method parameter 'handlerParam' into the
// exceptionResolver. We do this because the DispatcherServlet
// doDispatch() method calls checkMultipart() before determining
// the handler for the request. If doing the multipart check fails
// with a MultipartException, Spring will never have a reference
// to the handler and so 'handlerParam' will be null at this point.
return exceptionResolver.resolveException(request, response, this.multipartExceptionHandler, ex);
}
}
This seems to be a quite common problem. I've had similar problems and similar questions have been asked, see for example this question. I have yet to see a nice solution to the problem. You could use a vanilla servlet filter to handle these exceptions, but that will duplicate your error handling since you already have an ExceptionHandler.

Resources