You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Quarkus 3.2.3无注解POJO接收表单数据(application/x-www-form-urlencoded)遇415错误

问题描述

我有一个基于Quarkus 3.2.3的Java应用,希望将HTTP POST发送的表单数据接收为无注解POJO,但始终收到HTTP 415(不支持的媒体类型)响应。为保证可移植性与跨框架兼容性,我不想给存储表单数据的POJO添加任何注解。

用于接收表单数据的POJO如下:

public class DummyForm {
  public String firstName;
  public String lastName;
}

接收表单数据的方法如下:

@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("dummyForm")
public String formResult(DummyForm dummyForm) {
  return "";
}

默认情况下,发送Content-Type为application/x-www-form-urlencoded的请求会返回415状态码。相比之下,若发送JSON数据,使用相同的无注解POJO配合以下方法可正常工作:

@POST
@Consumes(MediaType.APPLICATION_JSON)
@Path("dummyForm")
public String formResult(DummyForm dummyForm) {
  return "";
}

是否有办法让无注解POJO也能接收application/x-www-form-urlencoded格式的表单数据?补充说明:POST请求体示例为firstName=John&lastName=Doe。

解决方案

Quarkus默认的application/x-www-form-urlencoded媒体类型处理器,不会自动绑定无注解POJO——这和JSON处理器的行为不同(JSON处理器依赖Jackson,默认支持无注解POJO序列化/反序列化)。要实现无注解POJO接收表单数据,有两种可行方案:

方案一:自定义MessageBodyReader(指定POJO)

实现JAX-RS的MessageBodyReader接口,手动解析表单数据并映射到目标POJO,无需给POJO添加任何注解:

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.MessageBodyReader;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;

@Provider
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public class FormToDummyFormReader implements MessageBodyReader<DummyForm> {

    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return type.isAssignableFrom(DummyForm.class);
    }

    @Override
    public DummyForm readFrom(Class<DummyForm> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
        // 读取并解析表单数据
        String formData = new String(entityStream.readAllBytes(), StandardCharsets.UTF_8);
        Map<String, String> formParams = parseFormData(formData);

        // 实例化POJO并赋值
        DummyForm form = new DummyForm();
        form.firstName = formParams.get("firstName");
        form.lastName = formParams.get("lastName");
        return form;
    }

    private Map<String, String> parseFormData(String formData) {
        return Map.ofEntries(formData.split("&")
                .map(param -> param.split("="))
                .map(parts -> Map.entry(
                        URLDecoder.decode(parts[0], StandardCharsets.UTF_8),
                        parts.length > 1 ? URLDecoder.decode(parts[1], StandardCharsets.UTF_8) : ""
                ))
                .toArray(Map.Entry[]::new));
    }
}

这个Provider会被Quarkus自动发现,专门处理指向DummyForm的表单请求。

方案二:通用无注解POJO表单绑定(反射实现)

如果需要支持多个无注解POJO,可以通过反射实现通用的MessageBodyReader,自动将表单参数映射到POJO的同名公有字段:

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.MessageBodyReader;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;

@Provider
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public class GenericFormToPojoReader implements MessageBodyReader<Object> {

    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        // 仅处理自定义POJO,排除JDK自带类
        return !type.isPrimitive() && !type.getName().startsWith("java.");
    }

    @Override
    public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
        String formData = new String(entityStream.readAllBytes(), StandardCharsets.UTF_8);
        Map<String, String> formParams = parseFormData(formData);

        try {
            Object pojo = type.getDeclaredConstructor().newInstance();
            // 遍历POJO公有字段,按参数名赋值
            for (Field field : type.getFields()) {
                String paramValue = formParams.get(field.getName());
                if (paramValue != null) {
                    field.set(pojo, paramValue);
                }
            }
            return pojo;
        } catch (Exception e) {
            throw new WebApplicationException("表单数据映射POJO失败", e);
        }
    }

    private Map<String, String> parseFormData(String formData) {
        return Map.ofEntries(formData.split("&")
                .map(param -> param.split("="))
                .map(parts -> Map.entry(
                        URLDecoder.decode(parts[0], StandardCharsets.UTF_8),
                        parts.length > 1 ? URLDecoder.decode(parts[1], StandardCharsets.UTF_8) : ""
                ))
                .toArray(Map.Entry[]::new));
    }
}

该方案无需为每个POJO单独编写逻辑,只要表单参数名与POJO公有字段名完全一致(大小写敏感)即可自动绑定。

注意事项
  • 两种方案都不需要修改POJO,完全符合可移植性与跨框架兼容性要求。
  • 若需处理复杂类型(如数字、日期),可在反射赋值环节添加类型转换逻辑。

内容的提问来源于stack exchange,提问作者Leendert Gravendeel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 19:40:11