次のコントローラーメソッドがあります。
@RequestMapping(value="/map/update", method=RequestMethod.POST, produces = "application/json; charset=utf-8")
@ResponseBody
public ResponseEntityWrapper updateMapTheme(
HttpServletRequest request,
@RequestBody @Valid List<CompanyTag> categories,
HttpServletResponse response
) throws ResourceNotFoundException, AuthorizationException {
...
}
CompanyTagは次のように定義されます。
public class CompanyTag {
@StringUUIDValidation String key;
String value;
String color;
String icon;
Icon iconObj;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
...
}
問題は、検証がトリガーされず、CompanyTagリストが検証されず、「StringUUIDValidation」バリデーターが呼び出されないことです。
リストを削除して、次の代わりに単一のCompanyTagのみを送信しようとした場合:
@RequestBody @Valid List<CompanyTag> categories,
つかいます:
@RequestBody @Valid CompanyTag category,
それは期待通りに動作するので、明らかにSpringは物事のリストを検証することを好まない(代わりに配列で試したが、どちらも動作しなかった)。
誰が何が欠けているのか分かりますか?
うまくいく別のアプローチを見つけました。基本的な問題は、サービスの入力ペイロードとしてリストを作成したいが、javax.validationはリストを検証せず、JavaBeanだけを検証することです。秘Theは、List and JavaBeanの両方として機能するカスタムリストクラスを使用することです。
@RequestBody @Valid List<CompanyTag> categories
への変更:
@RequestBody @Valid ValidList<CompanyTag> categories
リストのサブクラスは次のようになります。
public class ValidList<E> implements List<E> {
@Valid
private List<E> list;
public ValidList() {
this.list = new ArrayList<E>();
}
public ValidList(List<E> list) {
this.list = list;
}
// Bean-like methods, used by javax.validation but ignored by JSON parsing
public List<E> getList() {
return list;
}
public void setList(List<E> list) {
this.list = list;
}
// List-like methods, used by JSON parsing but ignored by javax.validation
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
// Other list methods ...
}
私はプロジェクトでPaulの方法を使用しようとしましたが、一部の人々はそれが複雑すぎると言いました。それから間もなく、次のコードのように機能する別の簡単な方法を見つけました。
@Validated
@RestController
@RequestMapping("/parent")
public class ParentController {
private FatherRepository fatherRepository;
/**
* DI
*/
public ParentController(FatherRepository fatherRepository) {
this.fatherRepository = fatherRepository;
}
@PostMapping("/test")
public void test(@RequestBody @Valid List<Father> fathers) {
}
}
動作し、使いやすいです。キーポイントは、クラスの@Valiatedアノテーションです。ところで、私が使用しているのはspringBootVersion = '2.0.4.RELEASE'です。
コメントで述べたように、ここでは次のコードのように例外を処理する必要があります。
@RestControllerAdvice
@Component
public class ControllerExceptionHandler {
/**
* handle controller methods parameter validation exceptions
*
* @param exception ex
* @return wrapped result
*/
@ExceptionHandler
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public DataContainer handle(ConstraintViolationException exception) {
Set<ConstraintViolation<?>> violations = exception.getConstraintViolations();
StringBuilder builder = new StringBuilder();
for (ConstraintViolation<?> violation : violations) {
builder.append(violation.getMessage());
break;
}
DataContainer container = new DataContainer(CommonCode.PARAMETER_ERROR_CODE, builder.toString());
return container;
}
}
ネットワークを表すものとしてhttpステータスコードを使用しても問題ありません。最初の違反メッセージのみがここに返されます。カスタマイズした要件を満たすように変更できます。
リストカテゴリをDTO Beanにラップして検証することをお勧めします。有効な検証に加えて、より柔軟なAPIを利用できます。
@RequestMapping(value="/map/update", method=RequestMethod.POST, produces = "application/json; charset=utf-8")
@ResponseBody
public ResponseEntityWrapper updateMapTheme(
HttpServletRequest request,
@RequestBody @Valid TagRequest tagRequest,
HttpServletResponse response
) throws ResourceNotFoundException, AuthorizationException {
...
}
public static class TagRequest {
@Valid
List<CompanyTag> categories;
// Gettes setters
}
最もエレガントなソリューションは、コレクションのカスタムバリデーターとWebDataBindersでそのバリデーターを登録する@ControllerAdviceを作成することだと思います コントローラーメソッドのコレクションにバインドされたRequestBodyパラメーターの春の検証
@Paul Strack's great solution Lombok magicと混合:
@Data
public class ValidList<E> implements List<E> {
@Valid
@Delegate
private List<E> list = new ArrayList<>();
}
使用法(ValidListのスワップリスト):
public ResponseEntityWrapper updateMapTheme(
@RequestBody @Valid ValidList<CompanyTag> categories, ...)
(Needs Lombok 、しかしまだ使用していない場合は、実際に試してみたい)
コレクションの検証は直接機能しません。
たとえば、複数の要素が検証に失敗した場合はどうすればよいですか?最初の検証後に停止しますか?すべてを検証する(もしそうなら、メッセージのコレクションをどうするか)
構成でSpringがHibernate ValidatorなどのBean Validatorプロバイダーに委任する場合は、そこでコレクションバリデーターを実装する方法を調べる必要があります。
Hibernateについては、同様の問題について説明します here
@ Validatedコントローラーに注釈を付ける
use @ Valid @RequestBodyに注釈を付けます
Spring-boot 1.5.19.RELEASEを使用しています
サービスに注釈を付ける@validated
を適用してから@Valid
をメソッドのList
パラメーターに追加すると、リスト内のアイテムが検証されます。
型
@Data
@ApiModel
@Validated
public class SubscriptionRequest {
@NotBlank()
private String soldToBpn;
@NotNull
@Size(min = 1)
@Valid
private ArrayList<DataProducts> dataProducts;
private String country;
@NotNull
@Size(min = 1)
@Valid
private ArrayList<Contact> contacts;
}
サービスインターフェイス(またはインターフェイスがない場合は具象型で使用)
@Validated
public interface SubscriptionService {
List<SubscriptionCreateResult> addSubscriptions(@NonNull @Size(min = 1) @Valid List<SubscriptionRequest> subscriptionRequestList)
throws IOException;
}
グローバル例外ハンドラーメソッド(ApiError Typeは私の設計ではありません)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = ConstraintViolationException.class)
@ResponseBody
public ApiError[] handleConstraintViolationException(ConstraintViolationException exception) {
List<InvalidField> invalidFields = exception.getConstraintViolations().stream()
.map(constraintViolation -> new InvalidField(constraintViolation.getPropertyPath().toString(),
constraintViolation.getMessage(),
constraintViolation.getInvalidValue()))
.collect(Collectors.toList());
return new ApiError[] {new ApiError(ErrorCodes.INVALID_PARAMETER, "Validation Error", invalidFields)};
}
コントローラーからの悪いメソッド呼び出しの例
LinkedList<SubscriptionRequest> list = new LinkedList<>();
list.add(new SubscriptionRequest());
return subscriptionService.addSubscriptions(list);
応答本文(インデックス[0]に注意)
[
{
"errorCode": "invalid.parameter",
"errorMessage": "Validation Error",
"invalidFields": [
{
"name": "addSubscriptions.arg0[0].soldToBpn",
"message": "may not be empty",
"value": null
},
{
"name": "addSubscriptions.arg0[0].dataProducts",
"message": "may not be null",
"value": null
},
{
"name": "addSubscriptions.arg0[0].contacts",
"message": "may not be null",
"value": null
}
]
}
]
エンティティクラスを作成します。
import javax.validation.Valid;
import Java.util.List;
public class ValidList<E> {
@Valid
private List<E> list;
public List<E> getList() {
return list;
}
public void setList(List<E> list) {
this.list = list;
}
}
コントローラーを使用する
@RequestMapping(value = "/sku", method = RequestMethod.POST)
public JsonResult createSKU(@Valid @RequestBody ValidList<Entity> entityList, BindingResult bindingResult) {
if (bindingResult.hasErrors())
return ErrorTools.build().handlerError(bindingResult);
return new JsonResult(200, "result");
}