如何使用 Spring Boot 修改 HttpServletResponse 以在 json 中包含一些标准属性? [复制]
Posted
技术标签:
【中文标题】如何使用 Spring Boot 修改 HttpServletResponse 以在 json 中包含一些标准属性? [复制]【英文标题】:How to modify HttpServletResponse to include some standard attributes in json using Spring boot? [duplicate] 【发布时间】:2019-02-01 11:51:05 【问题描述】:我正在 Spring Boot 中编写一个 REST API 应用程序。我希望我的 json 响应的签名是这样的:
示例:1
"status": "Error"
"httpcode": 500
"dev_message": "ServerException"
"user_message": "Oops..something went wrong with the app. Please try again."
"response":
...
示例:2
"status": "Success"
"httpcode": 200
"dev_message": "APICallSuccess"
"user_message": "Successfully called API"
"response":
"userid": "test",
"age": 31
...
"country": "India"
其中status, httpcode, dev_message
和user_message
在请求生命周期的不同阶段动态更新。例如,如果 API 令牌不正确,授权过滤器应将 httpstatus
字段更新为 401
,将 dev_message
更新为 AuthException
。另一方面,如果一个用户试图访问另一个用户的数据,而他不允许访问其他用户的数据,控制器应该更新这些字段。
我在这里有两部分问题:
-
如何在 Spring MVC / Spring Boot 中实现这一点?我知道这涉及
RequestHandlerInterceptor
和Filters
,但信息太多,我无法缩小范围。
这是构建我的 API 的正确方法吗?我希望客户真正知道出了什么问题,而不是仅仅返回 http 状态代码。
编辑 1: 下面的很多答案都是针对控制器级别的。我希望使用过滤器在更高的执行级别上处理这个问题。假设身份验证失败,那么我的请求甚至不会到达控制器。在所有这些情况下,我想在一个地方实现成功和失败消息,以便它干净且易于理解。
【问题讨论】:
最简单的方法:创建一个基本响应类并返回它? HttpServeletResponse 不关心这个。 【参考方案1】:我相信你正在寻找这个:Using Spring ResponseEntity to Manipulate the HTTP Response https://www.baeldung.com/spring-response-entity
@GetMapping("/age")
ResponseEntity<String> age(
@RequestParam("yearOfBirth") int yearOfBirth)
if (isInFuture(yearOfBirth))
return new ResponseEntity<>(
"Year of birth cannot be in the future",
HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(
"Your age is " + calculateAge(yearOfBirth),
HttpStatus.OK);
您可以根据需要设置错误 404、403。请让我知道它是否有效
【讨论】:
【参考方案2】:无论是错误还是响应,我在这两种情况下都使用了非常相似的 JSON 响应。你可以试试看,对你有帮助:)
错误 JSON:
"data": null,
"message": "Content type 'text/plain;charset=UTF-8' not supported",
"infoType": "ERROR",
"statusCode": 415,
"errors": [
"text/plain;charset=UTF-8 media type is not supported. Supported media types are application/octet-stream text/plain application/xml text/xml application/x-www-form-urlencoded application/*+xml multipart/form-data application/json application/*+json */"
]
响应 JSON:
"data": "
"id": 0,
"name": null,
"nestedObject":
"id": 0,
"name": "",
"url": "",
// Other Details
,
"address": "address",
"city": null,
"country": "country"
",
"message": "Your success message comes here",
"infoType": "INFO",
"statusCode": 200
我创建了一个抽象类,即 Message。有两个类,Response 和 Error,它们扩展了 Message。
您可以查看以下代码:
1.用于 InfoType 的枚举:
public enum InfoType
ERROR, INFO, WARNING
2。消息抽象类:
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.http.HttpStatus;
public abstract class Message
private String message;
private InfoType infoType;
@JsonIgnore
private HttpStatus status;
private int statusCode;
public Message()
public Message(String message, InfoType infoType, HttpStatus status)
this.message = message;
this.infoType = infoType;
this.status = status;
this.statusCode = status.value();
//Getters and Setters
3. POJO 用于响应:
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.springframework.http.HttpStatus;
@JsonPropertyOrder( "data", "message", "infoType", "status", "statusCode")
public class ResponseMessage extends Message
private Object data;
private ResponseMessage()
public ResponseMessage(Object data, String message, InfoType infoType, HttpStatus status)
super(message, infoType, status);
this.data = data;
//Getters and Setters
4. POJO 用于错误:
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.springframework.http.HttpStatus;
import java.util.Collections;
import java.util.List;
@JsonPropertyOrder( "data", "message", "infoType", "status", "statusCode", "errors")
public class ErrorMessage extends Message
private Object data;
private List<String> errors;
private ErrorMessage()
public ErrorMessage(Object data, String message, InfoType infoType, HttpStatus status)
super(message, infoType, status);
this.data = data;
public ErrorMessage(String message, InfoType infoType, HttpStatus status)
super(message, infoType, status);
this.data = null;
public ErrorMessage(String message, InfoType infoType, HttpStatus status, List<String> errors)
super(message, infoType, status);
this.errors = errors;
this.data = null;
public ErrorMessage(String message, InfoType infoType, HttpStatus status, String error)
super(message, infoType, status);
this.errors = Collections.singletonList(error);
this.data = null;
//Getters and Setters
5.用于警告/错误/响应的服务:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
@Component
public class ResponseMessageService
@Autowired
private ResponseMessages responseMessages;
@Autowired
private ErrorMessages errorMessages;
public ResponseMessage generateResponseMessage(Object data, String messageKey, HttpStatus status)
return new ResponseMessage(data,responseMessages.getProperty(messageKey), InfoType.INFO, status);
public ResponseMessage generateWarningMessage(Object data,String messageKey, HttpStatus status)
return new ResponseMessage(data,responseMessages.getProperty(messageKey), InfoType.WARNING , status);
public ErrorMessage generateErrorMessage(Object data,String messageKey, HttpStatus status)
return new ErrorMessage(data,errorMessages.getProperty(messageKey), InfoType.ERROR , status);
6.从属性文件中读取错误的服务:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.text.MessageFormat;
import java.util.List;
@Component
@PropertySource("classpath:errormessage.properties")
public class ErrorMessages
@Autowired
private Environment env;
public String getProperty(String property)
return env.getProperty(property);
7.从属性文件读取消息的服务:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.text.MessageFormat;
import java.util.List;
@Component
@PropertySource("classpath:responsemessage.properties")
public class ResponseMessages
@Autowired
private Environment env;
public String getProperty(String property)
return env.getProperty(property);
8.最后利用上述事物的资源/API 类:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("user")
public class UserResource
@Autowired
ResponseMessageService responseMessageService;
@RequestMapping(value="/register", method = RequestMethod.POST)
public ResponseEntity register(@RequestBody User user)
if(success)
return ResponseEntity.ok(responseMessageService.generateResponseMessage(null,"register.success",HttpStatus.OK));
else
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(responseMessageService.generateErrorMessage(null,"register.fail",HttpStatus.BAD_REQUEST));
@RequestMapping(value="/login", method = RequestMethod.POST)
public ResponseEntity login(@RequestBody User user)
if(success)
return ResponseEntity.ok().body(responseMessageService.generateResponseMessage(data,"login.success",HttpStatus.OK));
else
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(responseMessageService.generateResponseMessage(null, "login.fail",HttpStatus.BAD_REQUEST));
为了处理异常,我使用的是 CustomExceptionHandler。在这个处理程序中,我也使用相同的错误格式。
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
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.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.ArrayList;
import java.util.List;
@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler
// 400
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request)
logger.info(ex.getClass().getName());
//
final List<String> errors = new ArrayList<String>();
for (final FieldError error : ex.getBindingResult().getFieldErrors())
errors.add(error.getField() + ": " + error.getDefaultMessage());
for (final ObjectError error : ex.getBindingResult().getGlobalErrors())
errors.add(error.getObjectName() + ": " + error.getDefaultMessage());
final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, errors);
return handleExceptionInternal(ex, errorMessage, headers, errorMessage.getStatus(), request);
@Override
protected ResponseEntity<Object> handleBindException(final BindException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request)
logger.info(ex.getClass().getName());
//
final List<String> errors = new ArrayList<String>();
for (final FieldError error : ex.getBindingResult().getFieldErrors())
errors.add(error.getField() + ": " + error.getDefaultMessage());
for (final ObjectError error : ex.getBindingResult().getGlobalErrors())
errors.add(error.getObjectName() + ": " + error.getDefaultMessage());
final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, errors);
return handleExceptionInternal(ex, errorMessage, headers, errorMessage.getStatus(), request);
@Override
protected ResponseEntity<Object> handleTypeMismatch(final TypeMismatchException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request)
logger.info(ex.getClass().getName());
//
final String error = ex.getValue() + " value for " + ex.getPropertyName() + " should be of type " + ex.getRequiredType();
final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, error);
return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
@Override
protected ResponseEntity<Object> handleMissingServletRequestPart(final MissingServletRequestPartException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request)
logger.info(ex.getClass().getName());
//
final String error = ex.getRequestPartName() + " part is missing";
final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, error);
return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
@Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(final MissingServletRequestParameterException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request)
logger.info(ex.getClass().getName());
//
final String error = ex.getParameterName() + " parameter is missing";
final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, error);
return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
//
@ExceptionHandler( MethodArgumentTypeMismatchException.class )
public ResponseEntity<Object> handleMethodArgumentTypeMismatch(final MethodArgumentTypeMismatchException ex, final WebRequest request)
logger.info(ex.getClass().getName());
//
final String error = ex.getName() + " should be of type " + ex.getRequiredType().getName();
final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, error);
return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
@ExceptionHandler( ConstraintViolationException.class )
public ResponseEntity<Object> handleConstraintViolation(final ConstraintViolationException ex, final WebRequest request)
logger.info(ex.getClass().getName());
//
final List<String> errors = new ArrayList<String>();
for (final ConstraintViolation<?> violation : ex.getConstraintViolations())
errors.add(violation.getRootBeanClass().getName() + " " + violation.getPropertyPath() + ": " + violation.getMessage());
final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, errors);
return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
// 404
@Override
protected ResponseEntity<Object> handleNoHandlerFoundException(final NoHandlerFoundException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request)
logger.info(ex.getClass().getName());
//
final String error = "No handler found for " + ex.getHttpMethod() + " " + ex.getRequestURL();
final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.NOT_FOUND, error);
return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
// 405
@Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(final HttpRequestMethodNotSupportedException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request)
logger.info(ex.getClass().getName());
//
final StringBuilder builder = new StringBuilder();
builder.append(ex.getMethod());
builder.append(" method is not supported for this request. Supported methods are ");
ex.getSupportedHttpMethods().forEach(t -> builder.append(t + " "));
final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.METHOD_NOT_ALLOWED, builder.toString());
return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
// 415
@Override
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(final HttpMediaTypeNotSupportedException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request)
logger.info(ex.getClass().getName());
//
final StringBuilder builder = new StringBuilder();
builder.append(ex.getContentType());
builder.append(" media type is not supported. Supported media types are ");
ex.getSupportedMediaTypes().forEach(t -> builder.append(t + " "));
final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.UNSUPPORTED_MEDIA_TYPE, builder.substring(0, builder.length() - 2));
return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
// 500
@ExceptionHandler( Exception.class )
public ResponseEntity<Object> handleAll(final Exception ex, final WebRequest request)
logger.error("Error Occurred in Class " + ex.getClass().getName() + " ", ex);
String message = ex.getMessage();
String stackTrace = ex.fillInStackTrace().toString();
final ErrorMessage errorMessage = new ErrorMessage(message, InfoType.ERROR, HttpStatus.INTERNAL_SERVER_ERROR, stackTrace);
return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
以下是我用过的参考资料:
1. https://www.baeldung.com/global-error-handler-in-a-spring-rest-api2. https://github.com/eugenp/tutorials/blob/master/spring-security-rest/src/main/java/org/baeldung/web/CustomRestExceptionHandler.java
【讨论】:
感谢分享。但是,回到这个问题,我想更新来自过滤器链过滤器和请求/响应拦截器的响应。在这种情况下,请求甚至不会到达 controlleradvice,并且您共享的代码将不起作用。因此,我正在寻找一种直接更新 HttpServletResponse 的方法。 您的所有控制器现在都具有相同的返回类型。 IMO 这是一个糟糕的设计。控制器应该以类型安全的方式返回数据,并且过滤器应该将该输出包装到标准的 ResponseEntity 类中。 我没有明白你关于在ResponseEntity
中包装回复的观点。但我同意这不是我建议您的最佳解决方案。我也做了一些研究,但我没有找到可以帮助我们的东西。很少有人建议使用Map,但这也将类似于我的方法(响应将不是类型安全的)。很少有人建议使用ResponseBodyAdvice,但在这种情况下,数据将是类型安全的,但消息呢?以上是关于如何使用 Spring Boot 修改 HttpServletResponse 以在 json 中包含一些标准属性? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
IDEA如何设置spring-boot-devtools即所见即所得
如何在 Spring Boot 中修改 Mono 对象的属性而不阻塞它