如何使用 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_messageuser_message 在请求生命周期的不同阶段动态更新。例如,如果 API 令牌不正确,授权过滤器应将 httpstatus 字段更新为 401,将 dev_message 更新为 AuthException。另一方面,如果一个用户试图访问另一个用户的数据,而他不允许访问其他用户的数据,控制器应该更新这些字段。

我在这里有两部分问题:

    如何在 Spring MVC / Spring Boot 中实现这一点?我知道这涉及RequestHandlerInterceptorFilters,但信息太多,我无法缩小范围。 这是构建我的 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 H2数据库新增修改查询删除

如何使我的Spring Boot微服务使用HTTPS运行?

如何在 Spring Boot 中修改 Mono 对象的属性而不阻塞它

Spring Boot项目如何同时支持HTTP和HTTPS协议

Spring boot应用如何支持https