SpringBoot 3.2.0升级后Swagger接口实现触发验证约束异常求助
问题场景
项目升级至SpringBoot 3.2.0,包含两个模块:
commons-api模块通过OpenAPI生成器从YAML文件生成Swagger接口与DTO,依赖及插件配置见下文。api-nomenclature模块实现生成的MenuApi接口时,执行GET请求触发jakarta.validation.ConstraintDeclarationException:
jakarta.validation.ConstraintDeclarationException: HV000151: 重写方法不得重新定义参数约束配置,但方法MenuController#getAllMenus(Optional, Optional, Optional, Optional, Optional)重新定义了MenuApi#getAllMenus(Optional, Optional, Optional, Optional, Optional)的配置。
相关代码与配置
commons-api模块依赖
<dependencies> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>${springdoc-openapi-webmvc-ui.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.openapitools</groupId> <artifactId>jackson-databind-nullable</artifactId> <version>${jackson-databind-nullable.version}</version> </dependency> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> </dependency> </dependencies>
OpenAPI生成器插件配置
<plugin> <groupId>org.openapitools</groupId> <artifactId>openapi-generator-maven-plugin</artifactId> <version>${openapitools.version}</version> <executions> <execution> <id>contrat-nomenclature</id> <phase>generate-sources</phase> <goals> <goal>generate</goal> </goals> <configuration> <inputSpec>${project.basedir}/src/main/resources/contratnomenclature.yaml</inputSpec> <generatorName>spring</generatorName> <configOptions> <sourceFolder>main/java</sourceFolder> <interfaceOnly>true</interfaceOnly> <skipDefaultInterface>true</skipDefaultInterface> <hideGenerationTimestamp>true</hideGenerationTimestamp> <dateLibrary>java</dateLibrary> <useTags>true</useTags> <useOptional>true</useOptional> <useJakartaEe>true</useJakartaEe> <useSpringBoot3>true</useSpringBoot3> </configOptions> <modelPackage>${project.groupId}....model</modelPackage> <apiPackage>${project.groupId}....api</apiPackage> ... </configuration> </execution> </executions> </plugin>
接口实现代码(问题代码)
@CrossOrigin @RestController public class MenuController implements MenuApi { @Override public ResponseEntity<List<Menu>> getAllMenus(Optional<String> q, Optional<Integer> page, Optional<@Min(1) @Max(50) Integer> size, Optional<Boolean> full, Optional<String> sort) { return new ResponseEntity<>(menuService.getAllMenus(q, page, size, full, sort), HttpStatus.OK); } }
生成的MenuApi接口方法
@Operation( operationId = "getAllMenus", summary = "Retrieve all menus", description = "Retrieve all menus", tags = { "Menu" }, responses = { @ApiResponse(responseCode = "200", description = "Successful operation", content = { @Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = Menu.class))) }), @ApiResponse(responseCode = "400", description = "Invalid status value"), @ApiResponse(responseCode = "404", description = "Menu not found") } ) @RequestMapping( method = RequestMethod.GET, value = "/nomenclature/menu", produces = { "application/json" } ) ResponseEntity<List<Menu>> getAllMenus( @Parameter(name = "q", description = "Terme pour filter sur le nom", in = ParameterIn.QUERY) @Valid @RequestParam(value = "q", required = false) Optional<String> q, @Parameter(name = "page", description = "Numéro de la page", in = ParameterIn.QUERY) @Valid @RequestParam(value = "page", required = false) Optional<Integer> page, @Parameter(name = "size", description = "Nombre d'éléments par page", in = ParameterIn.QUERY) @Valid @RequestParam(value = "size", required = false, defaultValue = "20") Optional<@Min(1) @Max(50) Integer> size, @Parameter(name = "full", description = "Retourne la propriete et les sous éléments", in = ParameterIn.QUERY) @Valid @RequestParam(value = "full", required = false) Optional<Boolean> full, @Parameter(name = "sort", description = "Tri", in = ParameterIn.QUERY) @Valid @RequestParam(value = "sort", required = false) Optional<String> sort );
原因分析
Jakarta Validation(Hibernate Validator作为实现)的规则明确:子类重写父类/接口方法时,不能重新定义参数的约束配置。这里生成的MenuApi接口中,size参数已经带有@Min(1)和@Max(50)约束,而实现类MenuController的重写方法中再次添加了这两个注解,导致约束重复定义,触发异常。
解决方案
方案1:移除实现类方法参数的重复约束
直接删除实现类中size参数上的@Min和@Max注解,接口已定义的约束会自动生效:
@CrossOrigin @RestController public class MenuController implements MenuApi { @Override public ResponseEntity<List<Menu>> getAllMenus(Optional<String> q, Optional<Integer> page, Optional<Integer> size, Optional<Boolean> full, Optional<String> sort) { return new ResponseEntity<>(menuService.getAllMenus(q, page, size, full, sort), HttpStatus.OK); } }
方案2:调整OpenAPI生成器配置(可选)
如果希望生成的接口不带参数约束,或者需要自定义约束逻辑,可以修改生成器的configOptions,添加如下配置:
<configOptions> <!-- 其他已有配置 --> <useBeanValidation>false</useBeanValidation> </configOptions>
此配置会禁止生成器在接口参数上添加Bean Validation注解,之后可以在实现类中按需添加约束。
方案3:使用Validation分组(复杂场景)
如果需要在子类中调整约束逻辑,可以通过Jakarta Validation的分组机制实现:
- 定义分组接口:
public interface UpdateGroup {}
- 修改生成的接口(或调整YAML文件),给约束指定分组:
Optional<@Min(value = 1, groups = UpdateGroup.class) @Max(value = 50, groups = UpdateGroup.class) Integer> size
- 在实现类方法上指定生效的分组:
@Override @Validated(UpdateGroup.class) public ResponseEntity<List<Menu>> getAllMenus(Optional<String> q, Optional<Integer> page, Optional<@Min(1) @Max(50) Integer> size, Optional<Boolean> full, Optional<String> sort) { // 业务逻辑 }
注意:此方案需要确保生成器支持分组配置,可能需要调整OpenAPI YAML文件中的约束定义。
内容的提问来源于stack exchange,提问作者asmiou

