Micronaut GET方法复杂POJO参数引发400错误及自定义Binder未生效问题
问题:Micronaut中GET请求复杂POJO查询参数绑定失败,自定义Binder未执行
问题场景
在Micronaut框架中,将FilterSortRequest这类包含集合类型的复杂POJO作为GET请求的查询参数时,接口返回400 Bad Request错误,且自定义的FilterSortRequestBinder的bind方法从未被执行。
实体类定义
@Serdeable public record FilterSortRequest(@JsonProperty("filters")List<FilterOption> filters, @JsonProperty("sorts")Map<String, SortOperators> sorts) { }
控制器代码
@Controller("/tag") @Version("1") @Tag(name = "Tag") public class TagController implements IHttpAction<TagRequest, TagResponse> { public TagController(IServiceAction<FilterSortRequest, TagRequest, TagResponse> iServiceAction) { this.iServiceAction = iServiceAction; } @Override public HttpResponse<?> find(@QueryValue FilterSortRequest searchCriteria) { var result = this.iServiceAction.find(searchCriteria); return Result.match(()-> HttpResponse.ok(result.value), ()->HttpResponse.notFound(result.exception.getMessage())); } }
HTTP客户端接口
@Validated @ExecuteOn(TaskExecutors.BLOCKING) public interface IHttpAction<C, T, R> { @Get(value = "/{?searchCriteria*}") HttpResponse<?> find(@QueryValue FilterSortRequest searchCriteria); @Get(uri = "/{id}") HttpResponse<?> get(@PathVariable(name = "id") @Nonnull UUID id); @Post HttpResponse<?> post(@Body T request); @Put(uri = "/{id}") HttpResponse<?> put(@PathVariable(name = "id") @NotNull UUID id, @Body T request); @Delete(uri = "/{id}") HttpResponse<?> delete(@PathVariable(name = "id") @NotNull UUID id); }
自定义Binder实现
@Singleton public class FilterSortRequestBinder implements TypedRequestArgumentBinder<FilterSortRequest> { private final ObjectMapper objectMapper; public FilterSortRequestBinder(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public Argument<FilterSortRequest> argumentType() { return Argument.of(FilterSortRequest.class); } @Override public BindingResult<FilterSortRequest> bind(ArgumentConversionContext<FilterSortRequest> context, HttpRequest<?> source) { try { // Extract the 'filters' parameter String filtersParam = source.getParameters().get("filters"); // Extract the 'sorts' parameter String sortsParam = source.getParameters().get("sorts"); // Deserialize filters and sorts List<FilterOption> filters = filtersParam != null ? objectMapper.readValue(filtersParam, Argument.listOf(FilterOption.class)) : Collections.emptyList(); Map<String, SortOperators> sorts = sortsParam != null ? objectMapper.readValue(sortsParam, Argument.mapOf(String.class, SortOperators.class)) : Collections.emptyMap(); // Create the FilterSortRequest object FilterSortRequest filterSortRequest = new FilterSortRequest(filters, sorts); return () -> Optional.of(filterSortRequest); } catch (Exception e) { e.printStackTrace(); return BindingResult.EMPTY; } } }
请求示例
http://localhost:8080/tag?filters=[{"field":"string","operator":"EQUAL","values":{"additionalProp1":"string","additionalProp2":"string","additionalProp3":"string"}}]&sorts={"additionalProp1":"ASC","additionalProp2":"ASC","additionalProp3":"ASC"}
错误响应
{ "_links": { "self": [ { "href": "/tag?filters=%5B%7B%22field%22%3A%22string%22%2C%22operator%22%3A%22EQUAL%22%2C%22values%22%3A%7B%22additionalProp1%22%3A%22string%22%2C%22additionalProp2%22%3A%22string%22%2C%22additionalProp3%22%3A%22string%22%7D%7D%5D&sorts=%7B%22additionalProp1%22%3A%22ASC%22%2C%22additionalProp2%22%3A%22ASC%22%2C%22additionalProp3%22%3A%22ASC%22%7D", "templated": false } ] }, "_embedded": { "errors": [ { "message": "Failed to convert argument [searchCriteria] for value [null] due to: Unexpected token VALUE_STRING, expected START_OBJECT \n at [Source: UNKNOWN; byte offset: #UNKNOWN]", "path": "/searchCriteria" } ] }, "message": "Bad Request" }
问题原因及修复方案
核心原因
- Micronaut默认优先使用内置序列化器(如Jackson)绑定复杂对象,而请求中是将
FilterSortRequest拆分为filters和sorts两个独立查询参数,并非完整的JSON对象,内置转换器尝试转换时直接失败,返回400,未触发自定义Binder。 - 控制器参数的
@QueryValue未设置required = false,内置转换失败时直接抛出错误,不会降级到自定义Binder逻辑。
修复步骤
指定控制器参数使用自定义Binder
修改控制器find方法的参数注解,明确绑定自定义Binder,并设置required = false避免提前报错:@Override public HttpResponse<?> find(@QueryValue(required = false) @Bindable(binder = FilterSortRequestBinder.class) FilterSortRequest searchCriteria) { var result = this.iServiceAction.find(searchCriteria); return Result.match(()-> HttpResponse.ok(result.value), ()->HttpResponse.notFound(result.exception.getMessage())); }提升自定义Binder优先级
给自定义Binder添加@Primary注解,确保Micronaut优先使用该Binder而非内置转换器:@Singleton @Primary public class FilterSortRequestBinder implements TypedRequestArgumentBinder<FilterSortRequest> { // 原有逻辑保持不变 }调试与优化Binder逻辑
将bind方法中的e.printStackTrace()替换为日志框架记录,便于排查绑定失败原因:catch (Exception e) { LoggerFactory.getLogger(FilterSortRequestBinder.class).error("绑定FilterSortRequest失败", e); return BindingResult.EMPTY; }移除冲突注解
移除FilterSortRequest上的@JsonProperty注解,避免Micronaut Serde与Jackson注解冲突:@Serdeable public record FilterSortRequest(List<FilterOption> filters, Map<String, SortOperators> sorts) { }
内容的提问来源于stack exchange,提问作者San Jaisy
相关产品推荐
相关产品推荐

