WebFluxã§ã¯WebMVCã®ããã«ã¡ã½ãã弿°ã§BindingResult
ã§validationçµæãåå¾ã§ããªãã®ã§ããã®æ¸ãæãæ¹ã«ã¤ãã¦ã
WebMVCã§ã¯controllerã§validationçµæãåå¾ããã«ã¯ã¡ã½ããã®å¼æ°ã«BindingResult
ã追å ãããä¾ãã°ä»¥ä¸ã®ãããªã³ã¼ãã«ãªãã以ä¸ã§ã¯ãããvalidationã¨ã©ã¼ãçºçãããã䏿¦BindException
ã«å¤æãã¦controller-adviceã§ã¨ã©ã¼ãã³ããªã³ã°ãè¡ããã¨ããã®ãæ³å®ãã¦ããããããæ¸ãæãã¦ããã
import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class SpringMvcConrtoller { @PostMapping("/item1") public Item item(@Validated Item item, BindingResult result) throws BindException { if (result.hasErrors()) { throw new BindException(result); } return new Item(); } }
ã½ã¼ã¹ã³ã¼ã
build.gradle
plugins { id 'java' id 'org.springframework.boot' version '3.4.0' id 'io.spring.dependency-management' version '1.1.6' } group = 'com.example' version = '0.0.1-SNAPSHOT' java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-webflux' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.projectreactor:reactor-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { useJUnitPlatform() }
æ¸ãæãä¾
弿°ã«BindingResult
ãããã¨å®è¡æã¨ã©ã¼ã«ãªãã®ã§åé¤ãããã¾ãã弿°ãªãã¸ã§ã¯ããMono
ã¨ãã§ã©ãããããã®Mono
ã使ç¨ãã¦å¦çãè¨è¿°ããã以ä¸ã¯map
ã§åã«ç©ºãªãã¸ã§ã¯ããè¿ãã ãã ããããããæãã«ãã¸ãã¯ãè¨è¿°ãããããvalidationã失æããã¨WebExchangeBindException
ã«ãªãã®ã§ãããonErrorMap
ãªã©ã¨ã©ã¼ãã³ãã©ã§å¦çãããããã§ã¯æåã®SpringMVCã®ä¾ã«åããã¦åã«BindException
ã«è©°ãæ¿ããã¦ããã*1
import org.springframework.validation.BindException; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.support.WebExchangeBindException; import reactor.core.publisher.Mono; @RestController public class WebFluxController { @PostMapping("item001") public Mono<Item> item(@Validated Mono<Item> request) { return request .map(item -> new Item()) .onErrorMap(WebExchangeBindException.class, e -> { return new BindException(e.getBindingResult()); }); } }
ã¨ã©ã¼ãã³ããªã³ã°ã¯AbstractErrorWebExceptionHandler
ã®ç¶æ¿ã¯ã©ã¹ãç¨æããä¾å¤ãã¨ã®å¦çãè¨è¿°ãããããããã®ã¯JDK 21ã®switchã®ä½¿ç¨ä¾ã«ãªãã ãããã
import org.springframework.boot.autoconfigure.web.WebProperties.Resources; import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler; import org.springframework.boot.web.error.ErrorAttributeOptions; import org.springframework.boot.web.reactive.error.ErrorAttributes; import org.springframework.context.ApplicationContext; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.stereotype.Component; import org.springframework.validation.BindException; import org.springframework.web.reactive.function.server.RequestPredicates; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunctions; import org.springframework.web.reactive.function.server.ServerResponse; @Component public class CustomErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler { public CustomErrorWebExceptionHandler(ErrorAttributes errorAttributes, ApplicationContext applicationContext, ServerCodecConfigurer serverCodecConfigurer) { super(errorAttributes, new Resources(), applicationContext); super.setMessageWriters(serverCodecConfigurer.getWriters()); super.setMessageReaders(serverCodecConfigurer.getReaders()); } @Override protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions.route(RequestPredicates.all(), r -> { ErrorAttributeOptions eao = ErrorAttributeOptions.defaults(); Throwable error = errorAttributes.getError(r); return switch (error) { case BindException e -> ServerResponse.status(400).bodyValue("validation error"); default -> ServerResponse.status(500).bodyValue("default"); } }); } }
試è¡é¯èª¤
以ä¸ã¯ãã¼ã ãã¼ã ã¨èª¿ã¹ãã試ãããããéã®ä½æ¥ã¡ã¢ã
ã¾ãã¯åã«æ»ãå¤åãMono
ã«å¤ããã ãã§å®è¡ãã¦ã¿ãã
import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; @RestController public class WebFluxController { @PostMapping("item001") public Mono<Item> item(@RequestBody @Validated Item item, BindingResult result) throws BindException { if (result.hasErrors()) { throw new BindException(result); } return Mono.just(new Item()); }
以ä¸ã®ã¨ã©ã¼ã«ãªãã
java.lang.IllegalStateException: An Errors/BindingResult argument is expected immediately after the @ModelAttribute argument to which it applies. For @RequestBody and @RequestPart arguments, please declare them with a reactive type wrapper and use its onError operators to handle WebExchangeBindException: public reactor.core.publisher.Mono org.example.app.WebFluxController.item(org.example.app.Item,org.springframework.validation.BindingResult) throws org.springframework.validation.BindException at org.springframework.util.Assert.state(Assert.java:101) ~[spring-core-6.2.0.jar:6.2.0] Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): *__checkpoint ? HTTP POST "/item001" [ExceptionHandlingWebHandler] Original Stack Trace:
ã¨ã©ã¼ã¡ãã»ã¼ã¸ã«å¾ãã弿°ãªãã¸ã§ã¯ããMono
ã§ã©ããããã
@PostMapping("item001") public Mono<Item> item(@RequestBody @Validated Mono<Item> item, BindingResult result) throws BindException { ...
以ä¸ã®ã¨ã©ã¼ã«ãªãã
java.lang.IllegalStateException: An @ModelAttribute and an Errors/BindingResult argument cannot both be declared with an async type wrapper. Either declare the @ModelAttribute without an async wrapper type or handle a WebExchangeBindException error signal through the async type. at org.springframework.util.Assert.state(Assert.java:79) ~[spring-core-6.2.0.jar:6.2.0] Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): *__checkpoint ? HTTP POST "/item001" [ExceptionHandlingWebHandler] Original Stack Trace:
ã¨ã©ã¼ã¡ãã»ã¼ã¸ã«å¾ãã弿°ããBindingResult
ãåé¤ãã¦WebExchangeBindException
ã®ã¨ã©ã¼ãã³ããªã³ã°ã«åãæ¿ãããããã¯ä¸è¿°ã®éãã
ãã以ä¸ã®ããã«WebExchangeBindException
ããã®ã¾ã¾è¿ãã ãã«ãããã©ããªããï¼ãAbstractErrorWebExceptionHandler
ã«è¡ããªãã
@PostMapping("item001") public Mono<Item> item(@Validated Mono<Item> request) { return request .map(item -> new Item()) .onErrorMap(WebExchangeBindException.class, e -> { return e; }); }
åèæç®
- https://www.baeldung.com/spring-webflux-errors
- https://www.vinsguru.com/spring-webflux-validation/ -
@ControllerAdvice
ã使ããããã ãã©ãWebExceptionHandler
ã¨ã®ä½¿ãåããã¤ãã¤ãåãã£ã¦ãªãã2ãæã«åãããã®ããªããå¾®å¦ãªæ°ãããã
*1:WebFluxã»ã¼çµé¨ã¼ããªã®ã§reactiveç±æ¥ã®ç¨èªã®ä½¿ãæ¹ãå¤ã ã¨æããã¹ã«ã¼ãã¦æ¬²ãã