我已经构建了一个错误控制器,它应该是我在Spring REST服务中捕获异常的“最后一行”。但是,我似乎无法将POJO作为响应类型返回。为什么杰克逊不为这个案子工作?
我的班级看起来像:
@RestController
public class CustomErrorController implements ErrorController
{
private static final String PATH = "/error";
@Override
public String getErrorPath()
{
return PATH;
}
@RequestMapping (value = PATH)
public ResponseEntity<WebErrorResponse> handleError(HttpStatus status, HttpServletRequest request)
{
WebErrorResponse response = new WebErrorResponse();
// original requested URI
String uri = String.valueOf(request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI));
// status code
String code = String.valueOf(request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
// status message
String msg = String.valueOf(request.getAttribute(RequestDispatcher.ERROR_MESSAGE));
response.title = "Internal Server Error";
response.type = request.getMethod() + ": " + uri;
response.code = Integer.valueOf(code);
response.message = msg;
// build headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
// build the response
return new ResponseEntity<>(response, headers, status);
}
public class WebErrorResponse
{
/**
* The error message.
*/
public String message;
/**
* The status code.
*/
public int code;
/**
* The error title.
*/
public String title;
/**
* The error type.
*/
public String type;
}
这应该可以,但唯一的响应是Jetty错误消息 406 - 不可接受。
将响应实体主体类型更改为String非常有效。 怎么了?也许这是一个错误?
P.S:使用Spring 4.2.8,Spring Boot 1.3.8。
答案 0 :(得分:6)
最终解决方案
在谷歌的许多尝试和错误循环和往返之后,我终于找到了一个能够满足我想要的解决方案。 Spring中错误处理的主要问题是由默认行为和小文档引起的。
仅使用没有Spring Boot的Spring是没有问题的。但是使用两者来构建 网络(REST)服务就像地狱一样。
所以我想分享我的解决方案,以帮助所有人来到同一个b ** lsh * t ...
您需要的是:
配置(剪切重要部分)
@Configuration
public class SpringConfig extends WebMvcConfigurerAdapter
{
// ... init stuff if needed
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
{
// setup content negotiation (automatic detection of content types)
configurer
// use format parameter and extension to detect mimetype
.favorPathExtension(true).favorParameter(true)
// set default mimetype
.defaultContentType(MediaType.APPLICATION_XML)
.mediaType(...)
// and so on ....
}
/**
* Configuration of the {@link DispatcherServlet} bean.
*
* <p>This is needed because Spring and Spring Boot auto-configuration override each other.</p>
*
* @see <a href="http://stackoverflow.com/questions/28902374/spring-boot-rest-service-exception-handling">
* Stackoverflow - Spring Boot REST service exception handling</a>
*
* @param dispatcher dispatcher servlet instance
*/
@Autowired
@SuppressWarnings ("SpringJavaAutowiringInspection")
public void setupDispatcherServlet(DispatcherServlet dispatcher)
{
// FIX: for global REST error handling
// enable exceptions if endpoint not found (instead of static error page)
dispatcher.setThrowExceptionIfNoHandlerFound(true);
}
/**
* Creates the error properties used to setup the global REST error controller.
*
* <p>Using {@link ErrorProperties} is compliant to base implementation if Spring Boot's
* {@link org.springframework.boot.autoconfigure.web.BasicErrorController}.</p>
*
*
* @return error properties
*/
@Bean
public ErrorProperties errorProperties()
{
ErrorProperties properties = new ErrorProperties();
properties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.NEVER);
properties.setPath("/error");
return properties;
}
// ...
}
Spring异常处理程序:
@ControllerAdvice(annotations = RestController.class)
public class WebExceptionHandler extends ResponseEntityExceptionHandler
{
/**
* This function handles the exceptions.
*
* @param e the thrown exception
*
* @return error message as XML-document
*/
@ExceptionHandler (Exception.class)
public ResponseEntity<Object> handleErrorResponse(Exception e)
{
logger.trace("Catching Exception in REST API.", e);
return handleExceptionInternal(e, null, null, null, null);
}
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex,
Object body,
HttpHeaders headers,
HttpStatus status,
WebRequest request)
{
logger.trace("Catching Spring Exception in REST API.");
logger.debug("Using " + getClass().getSimpleName() + " for exception handling.");
// fatal, should not happen
if(ex == null) throw new NullPointerException("empty exception");
// set defaults
String title = "API Error";
String msg = ex.getMessage();
if(status == null) status = HttpStatus.BAD_REQUEST;
// build response body
WebErrorResponse response = new WebErrorResponse();
response.type = ex.getClass().getSimpleName();
response.title = title;
response.message = msg;
response.code = status.value();
// build response headers
if(headers == null) headers = new HttpHeaders();
try {
headers.setContentType(getContentType(request));
}
catch(NullPointerException e)
{
// ignore (empty headers will result in default)
}
catch(IllegalArgumentException e)
{
// return only status code
return new ResponseEntity<>(status);
}
return new ResponseEntity<>(response, headers, status);
}
/**
* Checks the given request and returns the matching response content type
* or throws an exceptions if the requested content type could not be delivered.
*
* @param request current request
*
* @return response content type matching the request
*
* @throws NullPointerException if the request does not an accept header field
* @throws IllegalArgumentException if the requested content type is not supported
*/
private static MediaType getContentType(WebRequest request) throws NullPointerException, IllegalArgumentException
{
String accepts = request.getHeader(HttpHeaders.ACCEPT);
if(accepts==null) throw new NullPointerException();
// XML
if(accepts.contains(MediaType.APPLICATION_XML_VALUE) ||
accepts.contains(MediaType.TEXT_XML_VALUE) ||
accepts.contains(MediaType.APPLICATION_XHTML_XML_VALUE))
return MediaType.APPLICATION_XML;
// JSON
else if(accepts.contains(MediaType.APPLICATION_JSON_VALUE))
return MediaType.APPLICATION_JSON_UTF8;
// other
else throw new IllegalArgumentException();
}
}
Spring Boot的错误控制器:
@Controller
@RequestMapping("/error")
public class CustomErrorController extends AbstractErrorController
{
protected final Logger logger = LoggerFactory.getLogger(getClass());
/**
* The global settings for this error controller.
*/
private final ErrorProperties properties;
/**
* Bean constructor.
*
* @param properties global properties
* @param attributes default error attributes
*/
@Autowired
public CustomErrorController(ErrorProperties properties, ErrorAttributes attributes)
{
super(attributes);
this.properties = new ErrorProperties();
}
@Override
public String getErrorPath()
{
return this.properties.getPath();
}
/**
* Returns the configuration properties of this controller.
*
* @return error properties
*/
public ErrorProperties getErrorProperties()
{
return this.properties;
}
/**
* This function handles runtime and application errors.
*
* @param request the incorrect request instance
*
* @return error message as XML-document
*/
@RequestMapping (produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_XML_VALUE})
@ResponseBody
public ResponseEntity<Object> handleError(HttpServletRequest request)
{
logger.trace("Catching Exception in REST API.");
logger.debug("Using {} for exception handling." , getClass().getSimpleName());
// original requested REST endpoint
String endpoint = String.valueOf(request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI));
// status code
String code = String.valueOf(request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
// thrown exception
Exception ex = ((Exception) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION));
if(ex == null) {
ex = new RuntimeException(String.valueOf(request.getAttribute(RequestDispatcher.ERROR_MESSAGE)));
}
// release nested exceptions (we want source exception only)
if(ex instanceof NestedServletException && ex.getCause() instanceof Exception) {
ex = (Exception) ex.getCause();
}
// build response body
WebErrorResponse response = new WebErrorResponse();
response.title = "Internal Server Error";
response.type = ex.getClass().getSimpleName();
response.code = Integer.valueOf(code);
response.message = request.getMethod() + ": " + endpoint+"; "+ex.getMessage();
// build response headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(getResponseType(request));
// build the response
return new ResponseEntity<>(response, headers, getStatus(request));
}
/*@RequestMapping (produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<Map<String, Object>> handleError(HttpServletRequest request)
{
Boolean stacktrace = properties.getIncludeStacktrace().equals(ErrorProperties.IncludeStacktrace.ALWAYS);
Map<String, Object> r = getErrorAttributes(request, stacktrace);
return new ResponseEntity<Map<String, Object>>(r, getStatus(request));
}*/
/**
* Extracts the response content type from the "Accept" HTTP header field.
*
* @param request request instance
*
* @return response content type
*/
private MediaType getResponseType(HttpServletRequest request)
{
String accepts = request.getHeader(HttpHeaders.ACCEPT);
// only XML or JSON allowed
if(accepts.contains(MediaType.APPLICATION_JSON_VALUE))
return MediaType.APPLICATION_JSON_UTF8;
else return MediaType.APPLICATION_XML;
}
}
就是这样,POJO WebErrorResponse 是一个只使用公共字符串和int字段的普通类。
上述类适用于支持XML和JSON的REST API。 工作原理:
我希望这会为那些困惑我的人澄清事情。
致以最诚挚的问候,
Zipunrar
答案 1 :(得分:0)
这是内容协商问题。基本上,请求是要求以特定格式进行响应,服务器说它无法以该格式提供响应。
这里可能存在一些问题。
application/json
标题的Accept
。Accept
标头,其值为application/json
,但Spring Web MVC配置未设置为处理JSON内容类型。在第一种情况下,指定请求者可以处理的类型是明智的。无论是否是您的确切问题,我都会这样做。
在第二种情况下,Spring默认通过XML进行内容协商。您可以通过向ApplicationContext添加WebMvcConfigurer来修改此默认行为:
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
}
此外,明确对@RequestMapping
注释更明确。请务必使用consumes
和produces
的“提示”参数(通过Accepts
和Content-type
请求标题帮助映射请求。)