如何在自定义BadRequest响应中获取无效QueryParam的名称?
如何在JAX-RS/RestEasy中返回包含无效QueryParam名称的自定义400响应
问题背景
现有一个JAX-RS/RestEasy的GET端点,通过@QueryParam接收参数并自动转换为自定义类型MyParamType,该类型内部包含格式验证逻辑。需要在验证失败时返回包含无效参数名称的自定义HTTP 400响应,但遇到以下问题:
- RestEasy仅允许对
WebApplicationException子类应用ExceptionMapper,普通IllegalArgumentException会被忽略 - 参数转换时(
ParamConverter.fromString方法内)无法获取QueryParam的名称,无法在异常中携带该信息 ParamConverterProvider的catch块永远不会进入,无法在此处抛出带参数名的异常
标准解决方案
方案一:通过ParamConverterProvider传递参数名到Converter
该方案遵循JAX-RS规范,通过Provider将参数名传递给Converter,使其能抛出携带参数名的BadRequestException,最终由ExceptionMapper处理并返回自定义响应。
1. 修改MyParamConverter,添加参数名接收逻辑
import javax.ws.rs.BadRequestException; import javax.ws.rs.ext.ParamConverter; public class MyParamConverter implements ParamConverter<MyParamType> { private final String paramName; // 构造函数接收QueryParam名称 public MyParamConverter(String paramName) { this.paramName = paramName; } @Override public MyParamType fromString(final String value) { if (value == null) { return null; } try { return new MyParamType(value); } catch (final IllegalArgumentException e) { // 抛出携带参数名的BadRequestException throw new BadRequestException(String.format("参数[%s]无效: %s", paramName, e.getMessage())); } } @Override public String toString(final MyParamType value) { return value != null ? value.toString() : null; } }
2. 修改MyParamConverterProvider,提取并传递参数名
import javax.ws.rs.QueryParam; import javax.ws.rs.ext.ParamConverter; import javax.ws.rs.ext.ParamConverterProvider; import javax.ws.rs.ext.Provider; import java.lang.annotation.Annotation; import java.lang.reflect.Type; @Provider public class MyParamConverterProvider implements ParamConverterProvider { @Override public <T> ParamConverter<T> getConverter( final Class<T> rawType, final Type genericType, final Annotation[] annotations ) { if (rawType.isAssignableFrom(MyParamType.class)) { // 从注解数组中提取QueryParam的名称 String paramName = extractQueryParamName(annotations); return (ParamConverter<T>) new MyParamConverter(paramName); } return null; } private String extractQueryParamName(Annotation[] annotations) { for (Annotation annotation : annotations) { if (annotation instanceof QueryParam) { return ((QueryParam) annotation).value(); } } // 兜底返回通用名称 return "unknown-param"; } }
3. 修改ExceptionMapper返回自定义响应
import javax.ws.rs.BadRequestException; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; @Provider public class MyExceptionMapper implements ExceptionMapper<BadRequestException> { @Override public Response toResponse(final BadRequestException exception) { return Response.status(Response.Status.BAD_REQUEST) .entity(exception.getMessage()) .build(); } }
方案二:结合Bean Validation(JSR-380)实现标准验证
如果项目已集成Bean Validation,这是更符合Java EE规范的方案,通过自定义验证注解和验证器,自动获取参数名并返回带名称的错误信息。
1. 创建自定义验证注解
import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; @Documented @Constraint(validatedBy = MyParamTypeValidator.class) @Target({ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface ValidMyParam { String message() default "参数格式无效"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
2. 实现验证器逻辑
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class MyParamTypeValidator implements ConstraintValidator<ValidMyParam, MyParamType> { @Override public boolean isValid(MyParamType value, ConstraintValidatorContext context) { if (value == null || value.value() == null) { // 空值交由@NotNull注解处理 return true; } if (!value.value().contains("-")) { // 禁用默认错误信息,自定义带参数名的提示 context.disableDefaultConstraintViolation(); // 获取参数名称(需RestEasy启用Bean Validation参数名解析) String paramName = context.getConstraintDescriptor().getAttributes().get("paramName").toString(); context.buildConstraintViolationWithTemplate(String.format("参数[%s]缺少'-'符号", paramName)) .addConstraintViolation(); return false; } return true; } }
3. 在端点中使用验证注解
import javax.validation.constraints.NotNull; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @GET @Path("/my-endpoint") @Produces(MediaType.TEXT_PLAIN) public Response myEndpoint( @QueryParam("mySuperParam") @NotNull @ValidMyParam final MyParamType myParam ) { return Response.ok(myParam.toString()).build(); }
注意:需确保项目添加了RestEasy Bean Validation依赖,例如
org.jboss.resteasy:resteasy-validator-provider-11,并在配置中启用Bean Validation支持。
内容的提问来源于stack exchange,提问作者jeancallisti
相关产品推荐
相关产品推荐

