Merge pull request #359 from bclozel/SPR-10481

* bclozel-SPR-10481:
  Add NoHandlerFoundException to DispatcherServlet
master
Rossen Stoyanchev 11 years ago
commit a64441e129
  1. 30
      spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
  2. 67
      spring-webmvc/src/main/java/org/springframework/web/servlet/NoHandlerFoundException.java
  3. 26
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java
  4. 26
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java
  5. 14
      spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java
  6. 13
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java
  7. 14
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java
  8. 6
      src/reference/docbook/mvc.xml

@ -45,6 +45,7 @@ import org.springframework.context.i18n.LocaleContext;
import org.springframework.core.OrderComparator;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.ui.context.ThemeSource;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@ -277,6 +278,9 @@ public class DispatcherServlet extends FrameworkServlet {
/** Perform cleanup of request attributes after include request? */
private boolean cleanupAfterInclude = true;
/** Throw a NoHandlerFoundException if no Handler was found to process this request? **/
private boolean throwExceptionIfNoHandlerFound = false;
/** MultipartResolver used by this servlet */
private MultipartResolver multipartResolver;
@ -408,6 +412,24 @@ public class DispatcherServlet extends FrameworkServlet {
this.detectAllViewResolvers = detectAllViewResolvers;
}
/**
* Set whether to throw a NoHandlerFoundException when no Handler was found for this request.
* This exception can then be caught with a HandlerExceptionResolver or an
* {@code @ExceptionHandler} controller method.
*
* <p>Note that if
* {@link org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler}
* is used, then requests will always be forwarded to the default servlet and
* a NoHandlerFoundException would never be thrown in that case.
*
* <p>Default is "false", meaning the DispatcherServlet sends a NOT_FOUND error
* through the Servlet response.
* @since 4.0
*/
public void setThrowExceptionIfNoHandlerFound(boolean throwExceptionIfNoHandlerFound) {
this.throwExceptionIfNoHandlerFound = throwExceptionIfNoHandlerFound;
}
/**
* Set whether to perform cleanup of request attributes after an include request, that is,
* whether to reset the original state of all request attributes after the DispatcherServlet
@ -1097,7 +1119,13 @@ public class DispatcherServlet extends FrameworkServlet {
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + requestUri +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
response.sendError(HttpServletResponse.SC_NOT_FOUND);
if(throwExceptionIfNoHandlerFound) {
ServletServerHttpRequest req = new ServletServerHttpRequest(request);
throw new NoHandlerFoundException(req.getMethod().name(),
req.getServletRequest().getRequestURI(),req.getHeaders());
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
/**

@ -0,0 +1,67 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import javax.servlet.ServletException;
import org.springframework.http.HttpHeaders;
/**
* Exception to be thrown if DispatcherServlet is unable to determine
* a corresponding handler for an incoming HTTP request.
* The DispatcherServlet throws this exception only if its
* throwExceptionIfNoHandlerFound property is set to "true".
*
* @author Brian Clozel
* @since 4.0
* @see org.springframework.web.servlet.DispatcherServlet
*/
@SuppressWarnings("serial")
public class NoHandlerFoundException extends ServletException {
private final String httpMethod;
private final String requestURL;
private final HttpHeaders headers;
/**
* Constructor for NoHandlerFoundException.
* @param httpMethod the HTTP method
* @param requestURL the HTTP request URL
* @param headers the HTTP request headers
*/
public NoHandlerFoundException(String httpMethod, String requestURL, HttpHeaders headers) {
super("No handler found for " + httpMethod + " " + requestURL + ", headers=" + headers);
this.httpMethod = httpMethod;
this.requestURL = requestURL;
this.headers = headers;
}
public String getHttpMethod() {
return this.httpMethod;
}
public String getRequestURL() {
return this.requestURL;
}
public HttpHeaders getHeaders() {
return this.headers;
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -42,6 +42,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
/**
@ -102,7 +103,8 @@ public abstract class ResponseEntityExceptionHandler {
HttpMessageNotWritableException.class,
MethodArgumentNotValidException.class,
MissingServletRequestPartException.class,
BindException.class
BindException.class,
NoHandlerFoundException.class
})
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
@ -160,6 +162,10 @@ public abstract class ResponseEntityExceptionHandler {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleBindException((BindException) ex, headers, status, request);
}
else if (ex instanceof NoHandlerFoundException) {
HttpStatus status = HttpStatus.NOT_FOUND;
return handleNoHandlerFoundException((NoHandlerFoundException) ex, headers, status, request);
}
else {
logger.warn("Unknown exception type: " + ex.getClass().getName());
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
@ -398,4 +404,20 @@ public abstract class ResponseEntityExceptionHandler {
return handleExceptionInternal(ex, null, headers, status, request);
}
/**
* Customize the response for NoHandlerFoundException.
* This method delegates to {@link #handleExceptionInternal(Exception, Object, HttpHeaders, HttpStatus, WebRequest)}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return a {@code ResponseEntity} instance
* @since 4.0
*/
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request);
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -46,6 +46,7 @@ import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
@ -147,6 +148,9 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
else if (ex instanceof BindException) {
return handleBindException((BindException) ex, request, response, handler);
}
else if (ex instanceof NoHandlerFoundException) {
return handleNoHandlerFoundException((NoHandlerFoundException) ex, request, response, handler);
}
}
catch (Exception handlerException) {
logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
@ -416,4 +420,24 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
return new ModelAndView();
}
/**
* Handle the case where no handler was found during the dispatch.
* <p>The default sends an HTTP 404 error, and returns
* an empty {@code ModelAndView}. Alternatively, a fallback view could be chosen,
* or the NoHandlerFoundException could be rethrown as-is.
* @param ex the NoHandlerFoundException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or {@code null} if none chosen
* at the time of the exception (for example, if multipart resolution failed)
* @return an empty ModelAndView indicating the exception was handled
* @throws IOException potentially thrown from response.sendError()
* @since 4.0
*/
protected ModelAndView handleNoHandlerFoundException(NoHandlerFoundException ex, HttpServletRequest request,
HttpServletResponse response, Object handler) throws IOException {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return new ModelAndView();
}
}

@ -540,6 +540,20 @@ public class DispatcherServletTests extends TestCase {
}
}
public void testThrowExceptionIfNoHandlerFound() throws ServletException, IOException {
DispatcherServlet complexDispatcherServlet = new DispatcherServlet();
complexDispatcherServlet.setContextClass(SimpleWebApplicationContext.class);
complexDispatcherServlet.setNamespace("test");
complexDispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
complexDispatcherServlet.init(new MockServletConfig(getServletContext(), "complex"));
MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "GET", "/unknown");
MockHttpServletResponse response = new MockHttpServletResponse();
complexDispatcherServlet.service(request, response);
assertTrue("correct error code", response.getStatus() == HttpServletResponse.SC_NOT_FOUND);
}
public void testCleanupAfterIncludeWithRemove() throws ServletException, IOException {
MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "GET", "/main.do");
MockHttpServletResponse response = new MockHttpServletResponse();

@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -37,6 +37,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.validation.BindException;
@ -52,6 +53,7 @@ import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
@ -184,6 +186,15 @@ public class ResponseEntityExceptionHandlerTests {
testException(ex);
}
@Test
public void noHandlerFoundException() {
ServletServerHttpRequest req = new ServletServerHttpRequest(
new MockHttpServletRequest("GET","/resource"));
Exception ex = new NoHandlerFoundException(req.getMethod().toString(),
req.getServletRequest().getRequestURI(),req.getHeaders());
testException(ex);
}
@Test
public void controllerAdvice() throws Exception {
StaticWebApplicationContext cxt = new StaticWebApplicationContext();

@ -26,6 +26,7 @@ import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.beans.TypeMismatchException;
import org.springframework.core.MethodParameter;
@ -43,6 +44,7 @@ import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
/** @author Arjen Poutsma */
@ -171,6 +173,18 @@ public class DefaultHandlerExceptionResolverTests {
assertEquals("Invalid status code", 400, response.getStatus());
}
@Test
public void handleNoHandlerFoundException() throws Exception {
ServletServerHttpRequest req = new ServletServerHttpRequest(
new MockHttpServletRequest("GET","/resource"));
NoHandlerFoundException ex = new NoHandlerFoundException(req.getMethod().name(),
req.getServletRequest().getRequestURI(),req.getHeaders());
ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex);
assertNotNull("No ModelAndView returned", mav);
assertTrue("No Empty ModelAndView returned", mav.isEmpty());
assertEquals("Invalid status code", 404, response.getStatus());
}
@Test
public void handleConversionNotSupportedException() throws Exception {
ConversionNotSupportedException ex =

@ -4244,6 +4244,12 @@ public class SimpleController {
<entry>400 (Bad Request)</entry>
</row>
<row>
<entry><classname>NoHandlerFoundException</classname></entry>
<entry>404 (Not Found)</entry>
</row>
<row>
<entry><classname>NoSuchRequestHandlingMethodException</classname></entry>

Loading…
Cancel
Save