当前位置: 首页 > 知识库问答 >
问题:

从spring boot自定义验证器返回不同的HTTP状态代码

皇甫波峻
2023-03-14

我正在使用spring启动版本:2.0.5

格雷德尔:

buildscript {
    ext {
        springBootVersion = '2.0.5.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'io.reflectoring'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 11

repositories {
    mavenCentral()
}

dependencies {
    implementation('org.springframework.boot:spring-boot-starter-data-jpa')
    implementation('org.springframework.boot:spring-boot-starter-validation')
    implementation('org.springframework.boot:spring-boot-starter-web')
    runtimeOnly('com.h2database:h2')
    testImplementation('org.springframework.boot:spring-boot-starter-test')
    testImplementation('org.junit.jupiter:junit-jupiter-engine:5.0.1')

    // these dependencies are needed when running with Java 11, since they
    // are no longer part of the JDK
    implementation('javax.xml.bind:jaxb-api:2.3.1')
    implementation('org.javassist:javassist:3.23.1-GA')
}

test{
    useJUnitPlatform()
}

控制器

@RestController
class ValidateRequestBodyController {

  @PostMapping("/validateBody")
  ResponseEntity<String> validateBody(@Valid @RequestBody Input input) {
    return ResponseEntity.ok("valid");
  }

}

验证器类

class InputWithCustomValidator {

  @IpAddress
  private String ipAddress;
  
  // ...

}


class IpAddressValidator implements ConstraintValidator<IpAddress, String> {

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    Pattern pattern = 
      Pattern.compile("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$");
    Matcher matcher = pattern.matcher(value);
    //step 1
    if (!matcher.matches()) {
        return 400;
      }
    //Step 2
      if (ipAddress already in DB) {
        return 409; //conflict with other IP address
      }
      //Also I need to return different exception based on diff validations

  }
}

控制器建议

@ExceptionHandler(ValidationException.class)
public ResponseEntity<ErrorResponse> handle(ValidationException e) {
    return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .body(e.getMessage());
}

    @ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
    return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .body(e.getMessage());
}

如果我从验证器中抛出customException,那么尽管我有相应的controllerAdvice,

{
  "code": "invalid_request"
    "description": "HV000028: Unexpected exception during isValid call."
}

总是,我收到400个坏的请求,因为我有一个controllerAdvice总是返回400。

我想在这里实现的是,有没有可能返回带有状态代码的customException或者有没有从Validator返回不同的状态代码?我在StackOverflow看到了类似的帖子,但是没有答案。我也查了其他的帖子,但没有发现有用。

共有2个答案

卫高谊
2023-03-14

在模型包下为错误创建一个类

@NoArgsConstructor
@Geeter
@Setter
public class ErrorMessage
{
    private int errorCode;
    private String errorMsg;
    private String documentation;

     public ErrorMessage(int errorCode,String errorMsg,String documentation)
       {
          this.errorCode=errorCode;
          this.errorMsg=errorMsg;
          this.documentation=documentation;
       }
}

在异常包下创建自定义验证异常类

public class CustomValidationException extends RuntimeException
{
          public CustomValidationException(String msg)
         {
              super(msg);
         }
}

在同一(exception)包下创建ExceptionHandler类

@RestControllerAdvice
public class CustomValidationExceptionHandler
{
   @ExceptionHandler
   public ResponseEntity toResponse(CustomValidationException ex)
   {
        ErrorMessage errorMessage=new 
       ErrorMessage(400,ex.getMessage,"www.stackoverflow.com");
      return new ResponseEntity<ErrorMessage>(errorMessage,HttpStatus.BAD_REQUES);
   }

}
彭炳
2023-03-14

当前行为

>

  • 当验证器代码抛出一个异常(它不扩展ConstraintDeclarationException)而不是返回false时,javax.validation将异常包装在ValidationException中。这是验证者框架的行为,而不是spring框架的问题。

    当验证器代码抛出扩展ConstraintDeclarationException的异常而不是返回false时,javax.validation框架会传播它。

    如果验证器返回false而不是抛出异常,spring会将所有验证错误转换为全局错误字段错误并将其包装在MethodArgumentNotValidexception中并抛出。

    问题

    • 第二个选项有字段错误和全局错误,只能通过检查字段名和错误代码返回自定义状态代码。因此这是不可行的,因为可以用该注释添加许多字段。
    • 在第一个选项中,验证器中引发的自定义异常被包装在validationexception中,因此不可能使用特定于异常的处理程序。

    可能的解决办法

    • 解压缩未扩展ConstraintDeclarationException的特定异常,并对其进行映射
        @ExceptionHandler(ValidationException.class)
        public ResponseEntity handle(ValidationException e) {
            Throwable cause = e.getCause();
            if (cause instanceof InvalidIpException) {
                return ResponseEntity
                        .status(HttpStatus.BAD_REQUEST)//may be different code
                        .body(cause.getMessage());
            }
            if (cause instanceof InuseIpException) {
                return ResponseEntity
                        .status(HttpStatus.BAD_REQUEST)//may be different code
                        .body(cause.getMessage());
            }
            return ResponseEntity
                    .status(HttpStatus.BAD_REQUEST)
                    .body(e.getMessage());
        }
    
    • 创建特定的异常以扩展ConstraintDeclarationException,然后为其设置特定的处理程序。
        public class InvalidIpException extends 
                                        ConstraintDeclarationException {
    
        @ExceptionHandler(InvalidIpException.class)
        public ResponseEntity handle(InvalidIpException e) {
         ...
        }
    

    参考代码

    • https://github.com/hibernate/hibernate-validator/blob/master/engine/src/main/Java/org/hibernate/validator/internal/engine/constraintvalidation/constrainttree.Java#L183

  •  类似资料:
    • 我正在处理WebAPI 2中的一个服务,endpoint当前返回一个<code>IHttpActionResult<code>。我想返回一个状态代码,但由于它不在枚举中,我不知道如何发送它,因为所有构造函数都需要一个参数 目前,我正在返回<code>BadResult(消息),但返回<code>422

    • 我考虑在项目中使用fluent-http。 所以我尝试注入: 返回正确的字符串,但似乎没有使用状态代码。在这两种情况下,响应都有一个代码HTTP200。 注意:我发现一些状态代码是预先实现的: null

    • 在Spring MVC@RequestMapping注释中,我返回JSP页面名称作为结果。这将返回HTTP状态代码200OK。如何将此状态代码更改为创建的201? @ResponseStatus不起作用。另外,HttpServletResponse也不能工作,因为我只需要返回自定义JSP页面。

    • 在Spring Boot控制器中,我使用检查用户是否登录(使用基本auth+)。 但是如果用户没有登录,即他们没有经过身份验证,控制器返回。 根据我对身份验证与授权的了解,用于身份验证,而用于授权。 根据我的理解,总是检查用户的授权(顾名思义),但是有什么方法可以根据我们传递给它的参数自定义它,即? 我知道还有另一种解决方案:在中使用基于URL的安全配置,但如果我不想这样做,该怎么办。 有什么想法

    • 我试图为web api控制器中的GET方法返回一个状态代码304 not modified。 我成功的唯一方法是这样的: 这里的问题是,它并不是一个例外,它只是没有被修改,所以客户端缓存是可以的。我还希望返回类型为User(正如所有web api示例使用GET显示的那样),而不是返回HttpResponseMessage或类似的内容。

    • 问题内容: 我看到了一些类似的问题(像这样),但是没有一个问题专门解决这个问题。 因此,Google 现在支持SPA,并且大多数网络浏览器都支持HTML5 pushState 。 我的AngularJS(但可能是任何JS东西)网站都使用URL来确定API路由。然后执行API调用,然后相应地呈现内容。 但是,由于 每个URL都 返回HTTP 200状态代码(包括example.com/get-fre