ogr*_*yjd 156 java rest spring exception-handling spring-boot
我正在尝试建立一个大型REST服务服务器.我们使用的是Spring Boot 1.2.1 Spring 4.1.5和Java 8.我们的控制器正在实现@RestController和标准的@RequestMapping注释.
我的问题是Spring Boot为控制器异常设置了默认重定向/error.来自文档:
Spring Boot默认提供/错误映射,以合理的方式处理所有错误,并在servlet容器中注册为"全局"错误页面.
从使用Node.js编写REST应用程序多年来,对我来说,这对任何事情都是明智的.服务端点生成的任何异常都应在响应中返回.我无法理解为什么你会发送重定向到最有可能是Angular或JQuery SPA消费者的消费者,该消费者只是寻找答案而不能或不会对重定向采取任何行动.
我想要做的是设置一个全局错误处理程序,可以接受任何异常 - 有意地从请求映射方法抛出或由Spring自动生成(如果没有找到请求路径签名的处理程序方法,则为404),并返回标准格式化错误响应(400,500,503,404)到客户端没有任何MVC重定向.具体来说,我们将采用错误,使用UUID将其记录到NoSQL,然后使用JSON正文中日志条目的UUID向客户端返回正确的HTTP错误代码.
对于如何做到这一点,文档一直含糊不清.在我看来,你必须创建自己的ErrorController实现或以某种方式使用ControllerAdvice,但我看到的所有示例仍然包括将响应转发到某种错误映射,这没有帮助.其他示例表明,您必须列出要处理的每个Exception类型,而不是仅列出"Throwable"并获取所有内容.
任何人都可以告诉我我错过了什么,或指出我如何做到这一点的正确方向,而不建议Node.js更容易处理的链?
ogr*_*yjd 125
新答案(2016-04-20)
使用Spring Boot 1.3.1.RELEASE
新步骤1 -将以下属性添加到application.properties很容易且不那么干扰:
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
Run Code Online (Sandbox Code Playgroud)
比修改现有的DispatcherServlet实例(如下所示)容易得多! - JO'
如果使用完整的RESTful应用程序,禁用静态资源的自动映射非常重要,因为如果使用Spring Boot的默认配置来处理静态资源,那么资源处理程序将处理请求(它最后排序并映射到/**这意味着它会获取应用程序中任何其他处理程序尚未处理的任何请求),因此调度程序servlet没有机会抛出异常.
新答案(2015-12-04)
使用Spring Boot 1.2.7.RELEASE
新的第1步 -我发现设置"throExceptionIfNoHandlerFound"标志的方式要少得多.在应用程序初始化类中将此替换DispatcherServlet替换代码(步骤1):
@ComponentScan()
@EnableAutoConfiguration
public class MyApplication extends SpringBootServletInitializer {
private static Logger LOG = LoggerFactory.getLogger(MyApplication.class);
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(MyApplication.class, args);
DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,我们在现有DispatcherServlet上设置标志,该标志保留Spring Boot框架的任何自动配置.
我发现还有一件事 - @EnableWebMvc注释对Spring Boot来说是致命的.是的,该注释能够捕获所有控制器异常,如下所述,但它也会杀死Spring Boot通常提供的大量有用的自动配置.使用Spring Boot时,请极其谨慎地使用该注释.
原答案:
经过大量研究并跟进这里发布的解决方案(感谢您的帮助!)以及对Spring代码的少量运行时跟踪,我终于找到了一个可以处理所有异常的配置(不是错误,而是继续阅读)包括404s.
第1步 -告诉SpringBoot停止使用MVC进行"未找到处理程序"的情况.我们希望Spring抛出异常,而不是返回到客户端,视图重定向到"/ error".为此,您需要在其中一个配置类中包含一个条目:
// NEW CODE ABOVE REPLACES THIS! (2015-12-04)
@Configuration
public class MyAppConfig {
@Bean // Magic entry
public DispatcherServlet dispatcherServlet() {
DispatcherServlet ds = new DispatcherServlet();
ds.setThrowExceptionIfNoHandlerFound(true);
return ds;
}
}
Run Code Online (Sandbox Code Playgroud)
这样做的缺点是它取代了默认的调度程序servlet.这对我们来说还不是问题,没有出现副作用或执行问题.如果您出于其他原因要对调度程序servlet执行任何其他操作,则可以执行此操作.
第2步 -现在Spring引导将在没有找到处理程序时抛出异常,可以使用统一异常处理程序中的任何其他异常处理该异常:
@EnableWebMvc
@ControllerAdvice
public class ServiceExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Throwable.class)
@ResponseBody
ResponseEntity<Object> handleControllerException(HttpServletRequest req, Throwable ex) {
ErrorResponse errorResponse = new ErrorResponse(ex);
if(ex instanceof ServiceException) {
errorResponse.setDetails(((ServiceException)ex).getDetails());
}
if(ex instanceof ServiceHttpException) {
return new ResponseEntity<Object>(errorResponse,((ServiceHttpException)ex).getStatus());
} else {
return new ResponseEntity<Object>(errorResponse,HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Override
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
Map<String,String> responseBody = new HashMap<>();
responseBody.put("path",request.getContextPath());
responseBody.put("message","The URL you have reached is not in service at this time (404).");
return new ResponseEntity<Object>(responseBody,HttpStatus.NOT_FOUND);
}
...
}
Run Code Online (Sandbox Code Playgroud)
请记住,我认为"@EnableWebMvc"注释在这里很重要.似乎如果没有它,这一切都无效.就是这样 - 您的Spring启动应用程序现在将捕获上述处理程序类中的所有异常,包括404,您可以随意使用它们.
最后一点 - 似乎没有办法让它捕获抛出的错误.我有一个古怪的想法,使用方面来捕获错误,并将它们转换为上述代码可以处理的异常,但我还没有时间实际尝试实现它.希望这有助于某人.
任何评论/更正/改进将不胜感激.
mag*_*ter 39
使用Spring Boot 1.4+增加了新的酷类,以便更轻松地处理异常处理,这有助于删除样板代码.
@RestControllerAdvice为异常处理提供了一个新的,它是@ControllerAdvice和的组合@ResponseBody.您可以删除@ResponseBody的@ExceptionHandler时候使用这个新的标注方法.
即
@RestControllerAdvice
public class GlobalControllerExceptionHandler {
@ExceptionHandler(value = { Exception.class })
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiErrorResponse unknownException(Exception ex, WebRequest req) {
return new ApiErrorResponse(...);
}
}
Run Code Online (Sandbox Code Playgroud)
为了处理404错误@EnableWebMvc,向application.properties 添加注释和以下内容就足够了:
spring.mvc.throw-exception-if-no-handler-found=true
你可以在这里找到并玩这些资源:https:
//github.com/magiccrafter/spring-boot-exception-handling
Efe*_*man 27
我认为ResponseEntityExceptionHandler符合您的要求.HTTP 400的示例代码:
@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentNotValidException.class,
HttpRequestMethodNotSupportedException.class})
public ResponseEntity<Object> badRequest(HttpServletRequest req, Exception exception) {
// ...
}
}
Run Code Online (Sandbox Code Playgroud)
你可以查看这篇文章
And*_*ser 15
虽然这是一个较老的问题,但我想就此分享我的想法.我希望,这会对你们中的一些人有所帮助.
我目前正在构建一个REST API,它使用Spring Boot 1.5.2.RELEASE和Spring Framework 4.3.7.RELEASE.我使用Java Config方法(而不是XML配置).此外,我的项目使用@RestControllerAdvice注释使用全局异常处理机制(请参阅下面的内容).
我的项目与您的项目具有相同的要求:我希望我的REST API HTTP 404 Not Found在尝试向不存在的URL发送请求时,在API响应的HTTP响应中返回带有附带JSON有效负载的a.在我的例子中,JSON有效负载看起来像这样(明显不同于Spring Boot默认值,顺便说一句.):
{
"code": 1000,
"message": "No handler found for your request.",
"timestamp": "2017-11-20T02:40:57.628Z"
}
Run Code Online (Sandbox Code Playgroud)
我终于成功了.以下是您需要做的主要任务:
NoHandlerFoundException如果API客户端调用不存在处理程序方法的URL,请确保抛出该值(请参阅下面的步骤1).ApiError),其中包含应该返回给API客户端的所有数据(参见步骤2).NoHandlerFoundException
并向API客户端返回正确的错误消息(请参阅步骤3).好的,现在详细说明:
第1步:配置application.properties
我必须将以下两个配置设置添加到项目的application.properties文件中:
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
Run Code Online (Sandbox Code Playgroud)
这确保了NoHandlerFoundException在客户端尝试访问不存在能够处理请求的控制器方法的URL的情况下抛出.
第2步:为API错误创建一个类
我创建了一个类似于本文中关于Eugen Paraschiv博客的建议.此类表示API错误.如果出现错误,此信息将发送到HTTP响应正文中的客户端.
public class ApiError {
private int code;
private String message;
private Instant timestamp;
public ApiError(int code, String message) {
this.code = code;
this.message = message;
this.timestamp = Instant.now();
}
public ApiError(int code, String message, Instant timestamp) {
this.code = code;
this.message = message;
this.timestamp = timestamp;
}
// Getters and setters here...
}
Run Code Online (Sandbox Code Playgroud)
第3步:创建/配置全局异常处理程序
我使用以下类来处理异常(为简单起见,我删除了import语句,记录代码和其他一些不相关的代码片段):
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ApiError noHandlerFoundException(
NoHandlerFoundException ex) {
int code = 1000;
String message = "No handler found for your request.";
return new ApiError(code, message);
}
// More exception handlers here ...
}
Run Code Online (Sandbox Code Playgroud)
第4步:编写测试
我想确保,即使在出现故障的情况下,API也会始终向调用客户端返回正确的错误消息.因此,我写了一个这样的测试:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SprintBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ActiveProfiles("dev")
public class GlobalExceptionHandlerIntegrationTest {
public static final String ISO8601_DATE_REGEX =
"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$";
@Autowired
private MockMvc mockMvc;
@Test
@WithMockUser(roles = "DEVICE_SCAN_HOSTS")
public void invalidUrl_returnsHttp404() throws Exception {
RequestBuilder requestBuilder = getGetRequestBuilder("/does-not-exist");
mockMvc.perform(requestBuilder)
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.code", is(1000)))
.andExpect(jsonPath("$.message", is("No handler found for your request.")))
.andExpect(jsonPath("$.timestamp", RegexMatcher.matchesRegex(ISO8601_DATE_REGEX)));
}
private RequestBuilder getGetRequestBuilder(String url) {
return MockMvcRequestBuilders
.get(url)
.accept(MediaType.APPLICATION_JSON);
}
Run Code Online (Sandbox Code Playgroud)
该@ActiveProfiles("dev")注解可以留下了.我只在使用不同的配置文件时使用它.这RegexMatcher是我用来更好地处理时间戳字段的自定义Hamcrest匹配器.这是代码(我在这里找到):
public class RegexMatcher extends TypeSafeMatcher<String> {
private final String regex;
public RegexMatcher(final String regex) {
this.regex = regex;
}
@Override
public void describeTo(final Description description) {
description.appendText("matches regular expression=`" + regex + "`");
}
@Override
public boolean matchesSafely(final String string) {
return string.matches(regex);
}
// Matcher method you can call on this matcher class
public static RegexMatcher matchesRegex(final String string) {
return new RegexMatcher(regex);
}
}
Run Code Online (Sandbox Code Playgroud)
我方的一些进一步说明:
@EnableWebMvc注释.在我的情况下,这不是必要的.小智 13
这段代码怎么样?我使用回退请求映射来捕获404错误.
@Controller
@ControllerAdvice
public class ExceptionHandlerController {
@ExceptionHandler(Exception.class)
public ModelAndView exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {
//If exception has a ResponseStatus annotation then use its response code
ResponseStatus responseStatusAnnotation = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
return buildModelAndViewErrorPage(request, response, ex, responseStatusAnnotation != null ? responseStatusAnnotation.value() : HttpStatus.INTERNAL_SERVER_ERROR);
}
@RequestMapping("*")
public ModelAndView fallbackHandler(HttpServletRequest request, HttpServletResponse response) throws Exception {
return buildModelAndViewErrorPage(request, response, null, HttpStatus.NOT_FOUND);
}
private ModelAndView buildModelAndViewErrorPage(HttpServletRequest request, HttpServletResponse response, Exception ex, HttpStatus httpStatus) {
response.setStatus(httpStatus.value());
ModelAndView mav = new ModelAndView("error.html");
if (ex != null) {
mav.addObject("title", ex);
}
mav.addObject("content", request.getRequestURL());
return mav;
}
}
Run Code Online (Sandbox Code Playgroud)
@RestControllerAdvice 是 Spring Framework 4.3 的一个新特性,它通过一个横切关注点解决方案使用 RestfulApi 处理异常:
package com.khan.vaquar.exception;
import javax.servlet.http.HttpServletRequest;
import org.owasp.esapi.errors.IntrusionException;
import org.owasp.esapi.errors.ValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.khan.vaquar.domain.ErrorResponse;
/**
* Handles exceptions raised through requests to spring controllers.
**/
@RestControllerAdvice
public class RestExceptionHandler {
private static final String TOKEN_ID = "tokenId";
private static final Logger log = LoggerFactory.getLogger(RestExceptionHandler.class);
/**
* Handles InstructionExceptions from the rest controller.
*
* @param e IntrusionException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = IntrusionException.class)
public ErrorResponse handleIntrusionException(HttpServletRequest request, IntrusionException e) {
log.warn(e.getLogMessage(), e);
return this.handleValidationException(request, new ValidationException(e.getUserMessage(), e.getLogMessage()));
}
/**
* Handles ValidationExceptions from the rest controller.
*
* @param e ValidationException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = ValidationException.class)
public ErrorResponse handleValidationException(HttpServletRequest request, ValidationException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
if (e.getUserMessage().contains("Token ID")) {
tokenId = "<OMITTED>";
}
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getUserMessage());
}
/**
* Handles JsonProcessingExceptions from the rest controller.
*
* @param e JsonProcessingException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = JsonProcessingException.class)
public ErrorResponse handleJsonProcessingException(HttpServletRequest request, JsonProcessingException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getOriginalMessage());
}
/**
* Handles IllegalArgumentExceptions from the rest controller.
*
* @param e IllegalArgumentException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = IllegalArgumentException.class)
public ErrorResponse handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = UnsupportedOperationException.class)
public ErrorResponse handleUnsupportedOperationException(HttpServletRequest request, UnsupportedOperationException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getMessage());
}
/**
* Handles MissingServletRequestParameterExceptions from the rest controller.
*
* @param e MissingServletRequestParameterException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = MissingServletRequestParameterException.class)
public ErrorResponse handleMissingServletRequestParameterException( HttpServletRequest request,
MissingServletRequestParameterException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.BAD_REQUEST.value(),
e.getClass().getSimpleName(),
e.getMessage());
}
/**
* Handles NoHandlerFoundExceptions from the rest controller.
*
* @param e NoHandlerFoundException
* @return error response POJO
*/
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(value = NoHandlerFoundException.class)
public ErrorResponse handleNoHandlerFoundException(HttpServletRequest request, NoHandlerFoundException e) {
String tokenId = request.getParameter(TOKEN_ID);
log.info(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.NOT_FOUND.value(),
e.getClass().getSimpleName(),
"The resource " + e.getRequestURL() + " is unavailable");
}
/**
* Handles all remaining exceptions from the rest controller.
*
* This acts as a catch-all for any exceptions not handled by previous exception handlers.
*
* @param e Exception
* @return error response POJO
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(value = Exception.class)
public ErrorResponse handleException(HttpServletRequest request, Exception e) {
String tokenId = request.getParameter(TOKEN_ID);
log.error(e.getMessage(), e);
return new ErrorResponse( tokenId,
HttpStatus.INTERNAL_SERVER_ERROR.value(),
e.getClass().getSimpleName(),
"An internal error occurred");
}
}
Run Code Online (Sandbox Code Playgroud)
默认情况下,Spring Boot为json提供错误详细信息.
curl -v localhost:8080/greet | json_pp
[...]
< HTTP/1.1 400 Bad Request
[...]
{
"timestamp" : 1413313361387,
"exception" : "org.springframework.web.bind.MissingServletRequestParameterException",
"status" : 400,
"error" : "Bad Request",
"path" : "/greet",
"message" : "Required String parameter 'name' is not present"
}
Run Code Online (Sandbox Code Playgroud)
它也适用于所有类型的请求映射错误.查看这篇文章 http://www.jayway.com/2014/10/19/spring-boot-error-responses/
如果要创建将其登录到NoSQL.您可以在其中创建@ControllerAdvice,然后重新抛出异常.文档https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc中有示例
| 归档时间: |
|
| 查看次数: |
210576 次 |
| 最近记录: |