Spring @Valid 消息未通过响应
Spring @Valid Message Not Coming Through in Response
我有一个使用 Spring 和 Axon 的 CQRS Rest API。使用 javax.validation
库为输入设置验证。验证工作正常,发现 'username' 需要至少 2 个字符长。但是,与验证失败相关的消息未显示在对 Post 请求的响应中。
回复最终缺少 'errors' 和 'message' 信息,我不确定为什么。 MethodArgumentNotValidException
不会被推送到 try-catch 块并得到处理吗?如果验证工作正常,为什么消息没有以“400”状态响应发出?
我错过了什么?
这是我想要得到的回应:
{
"timestamp": "2022-04-24T01:23:27.809+00:00",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Size.registerUserCommand.user.account.username",
"Size.user.account.username",
"Size.username",
"Size.java.lang.String",
"Size"
],
"arguments": [
{
"codes": [
"registerUserCommand.user.account.username",
"user.account.username"
],
"arguments": null,
"defaultMessage": "user.account.username",
"code": "user.account.username"
},
2134838498,
2
],
"defaultMessage": "username must have a minimum of 2 characters",
"objectName": "registerUserCommand",
"field": "user.account.username",
"rejectedValue": "m",
"bindingFailure": false,
"code": "Size
}
],
"message": "Validation failed for object='registerUserCommand'. Error count: 1",
"path": "/api/v1/registerUser"
}
这是我实际得到的回复:
{
"timestamp": "2022-04-24T01:23:27.809+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/api/v1/registerUser"
}
Post 正在发送到控制器的请求:
{
"user": {
"firstname": "Mike",
"lastname": "Jacobs",
"emailAddress": "mike@spripgbank.com",
"account": {
"username": "m",
"password": "mik959593e0",
"roles": [
"READ_PRIVILEGE"
]
}
}
}
Post 请求的控制台输出:
2022-04-24 20:32:21.339 INFO 11040 --- [nio-8081-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-04-24 20:32:21.340 INFO 11040 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-04-24 20:32:21.343 INFO 11040 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 3 ms
2022-04-24 20:32:22.120 WARN 11040 --- [nio-8081-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<com.springbank.user.cmd.api.dto.RegisterUserResponse> com.springbank.user.cmd.api.controllers.RegisterUserController.registerUser(com.springbank.user.cmd.api.commands.RegisterUserCommand): [Field error in object 'registerUserCommand' on field 'user.account.username': rejected value [m]; codes [Size.registerUserCommand.user.account.username,Size.user.account.username,Size.username,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [registerUserCommand.user.account.username,user.account.username]; arguments []; default message [user.account.username],2147483647,2]; default message [username must have a min of 2 characters]] ]
接受请求的控制器:
package com.springbank.user.cmd.api.controllers;
import com.springbank.user.cmd.api.commands.RegisterUserCommand;
import com.springbank.user.cmd.api.dto.RegisterUserResponse;
import org.axonframework.commandhandling.gateway.CommandGateway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.UUID;
@RestController
@RequestMapping(path = "/api/v1/registerUser")
public class RegisterUserController {
private final CommandGateway commandGateway;
@Autowired
public RegisterUserController(CommandGateway commandGateway) {
this.commandGateway = commandGateway;
}
@PostMapping
public ResponseEntity<RegisterUserResponse> registerUser(@Valid @RequestBody RegisterUserCommand command) {
var id = UUID.randomUUID().toString();
command.setId(id);
try{
commandGateway.send(command);
return new ResponseEntity<>(new RegisterUserResponse( id, "User Successfully Registered"), HttpStatus.CREATED);
}catch (Exception e) {
var safeErrorMessage = "Error while processing register user request for id - " + id;
System.out.println(e.toString());
return new ResponseEntity<>(new RegisterUserResponse( id, safeErrorMessage), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
命令:
package com.springbank.user.cmd.api.commands;
import com.springbank.user.core.models.User;
import lombok.Builder;
import lombok.Data;
import org.axonframework.modelling.command.TargetAggregateIdentifier;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@Data
@Builder
public class RegisterUserCommand {
@TargetAggregateIdentifier
private String id;
@NotNull(message = "Need to supply User info")
@Valid
private User user;
}
用户模型:
package com.springbank.user.core.models;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import javax.validation.Valid;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Document(collection = "users")
public class User {
@Id
private String id;
@NotEmpty(message = "firstname is mandatory")
private String firstname;
@NotEmpty(message = "lastname is mandatory")
private String lastname;
@Email(message = "please provide a valid email")
private String emailAddress;
@NotNull(message = "Please provide account Credentials")
@Valid
private Account account;
}
进行验证的帐户模型:
package com.springbank.user.core.models;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Account {
@Size(min = 2, message = "username must have a min of 2 characters")
private String username;
@Size(min = 7, message = "password must have a min of 7 characters")
private String password;
@NotNull(message = "specify at least one user role")
private List<Role> roles;
}
RegisterUserResponse:
package com.springbank.user.cmd.api.dto;
public class RegisterUserResponse extends BaseResponse{
private String id;
public RegisterUserResponse(String id, String message) {
super(message);
this.id = id;
}
}
基本响应:
package com.springbank.user.cmd.api.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class BaseResponse {
private String Message;
}
由于您使用 @Valid RegisterUserCommand
注释控制器方法参数,因此将在执行进入您的 try/catch 块之前对对象进行验证。
如果您查看位于 ResponseEntityExceptionHandler 中的以下方法,您会发现默认实现如下所示。如您所见,默认是将null作为body发送,这导致细节被遗漏了。
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request);
}
因此,如果您想以不同方式处理该错误,则需要为该异常类型添加自定义异常处理并覆盖默认处理程序。
我有一个使用 Spring 和 Axon 的 CQRS Rest API。使用 javax.validation
库为输入设置验证。验证工作正常,发现 'username' 需要至少 2 个字符长。但是,与验证失败相关的消息未显示在对 Post 请求的响应中。
回复最终缺少 'errors' 和 'message' 信息,我不确定为什么。 MethodArgumentNotValidException
不会被推送到 try-catch 块并得到处理吗?如果验证工作正常,为什么消息没有以“400”状态响应发出?
我错过了什么?
这是我想要得到的回应:
{
"timestamp": "2022-04-24T01:23:27.809+00:00",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Size.registerUserCommand.user.account.username",
"Size.user.account.username",
"Size.username",
"Size.java.lang.String",
"Size"
],
"arguments": [
{
"codes": [
"registerUserCommand.user.account.username",
"user.account.username"
],
"arguments": null,
"defaultMessage": "user.account.username",
"code": "user.account.username"
},
2134838498,
2
],
"defaultMessage": "username must have a minimum of 2 characters",
"objectName": "registerUserCommand",
"field": "user.account.username",
"rejectedValue": "m",
"bindingFailure": false,
"code": "Size
}
],
"message": "Validation failed for object='registerUserCommand'. Error count: 1",
"path": "/api/v1/registerUser"
}
这是我实际得到的回复:
{
"timestamp": "2022-04-24T01:23:27.809+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/api/v1/registerUser"
}
Post 正在发送到控制器的请求:
{
"user": {
"firstname": "Mike",
"lastname": "Jacobs",
"emailAddress": "mike@spripgbank.com",
"account": {
"username": "m",
"password": "mik959593e0",
"roles": [
"READ_PRIVILEGE"
]
}
}
}
Post 请求的控制台输出:
2022-04-24 20:32:21.339 INFO 11040 --- [nio-8081-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-04-24 20:32:21.340 INFO 11040 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-04-24 20:32:21.343 INFO 11040 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 3 ms
2022-04-24 20:32:22.120 WARN 11040 --- [nio-8081-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<com.springbank.user.cmd.api.dto.RegisterUserResponse> com.springbank.user.cmd.api.controllers.RegisterUserController.registerUser(com.springbank.user.cmd.api.commands.RegisterUserCommand): [Field error in object 'registerUserCommand' on field 'user.account.username': rejected value [m]; codes [Size.registerUserCommand.user.account.username,Size.user.account.username,Size.username,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [registerUserCommand.user.account.username,user.account.username]; arguments []; default message [user.account.username],2147483647,2]; default message [username must have a min of 2 characters]] ]
接受请求的控制器:
package com.springbank.user.cmd.api.controllers;
import com.springbank.user.cmd.api.commands.RegisterUserCommand;
import com.springbank.user.cmd.api.dto.RegisterUserResponse;
import org.axonframework.commandhandling.gateway.CommandGateway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.UUID;
@RestController
@RequestMapping(path = "/api/v1/registerUser")
public class RegisterUserController {
private final CommandGateway commandGateway;
@Autowired
public RegisterUserController(CommandGateway commandGateway) {
this.commandGateway = commandGateway;
}
@PostMapping
public ResponseEntity<RegisterUserResponse> registerUser(@Valid @RequestBody RegisterUserCommand command) {
var id = UUID.randomUUID().toString();
command.setId(id);
try{
commandGateway.send(command);
return new ResponseEntity<>(new RegisterUserResponse( id, "User Successfully Registered"), HttpStatus.CREATED);
}catch (Exception e) {
var safeErrorMessage = "Error while processing register user request for id - " + id;
System.out.println(e.toString());
return new ResponseEntity<>(new RegisterUserResponse( id, safeErrorMessage), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
命令:
package com.springbank.user.cmd.api.commands;
import com.springbank.user.core.models.User;
import lombok.Builder;
import lombok.Data;
import org.axonframework.modelling.command.TargetAggregateIdentifier;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@Data
@Builder
public class RegisterUserCommand {
@TargetAggregateIdentifier
private String id;
@NotNull(message = "Need to supply User info")
@Valid
private User user;
}
用户模型:
package com.springbank.user.core.models;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import javax.validation.Valid;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Document(collection = "users")
public class User {
@Id
private String id;
@NotEmpty(message = "firstname is mandatory")
private String firstname;
@NotEmpty(message = "lastname is mandatory")
private String lastname;
@Email(message = "please provide a valid email")
private String emailAddress;
@NotNull(message = "Please provide account Credentials")
@Valid
private Account account;
}
进行验证的帐户模型:
package com.springbank.user.core.models;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Account {
@Size(min = 2, message = "username must have a min of 2 characters")
private String username;
@Size(min = 7, message = "password must have a min of 7 characters")
private String password;
@NotNull(message = "specify at least one user role")
private List<Role> roles;
}
RegisterUserResponse:
package com.springbank.user.cmd.api.dto;
public class RegisterUserResponse extends BaseResponse{
private String id;
public RegisterUserResponse(String id, String message) {
super(message);
this.id = id;
}
}
基本响应:
package com.springbank.user.cmd.api.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class BaseResponse {
private String Message;
}
由于您使用 @Valid RegisterUserCommand
注释控制器方法参数,因此将在执行进入您的 try/catch 块之前对对象进行验证。
如果您查看位于 ResponseEntityExceptionHandler 中的以下方法,您会发现默认实现如下所示。如您所见,默认是将null作为body发送,这导致细节被遗漏了。
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request);
}
因此,如果您想以不同方式处理该错误,则需要为该异常类型添加自定义异常处理并覆盖默认处理程序。