10

我有一个 Web API,用户可以(或不可以)传输 URL 参数,例如birddog等等。

我希望将此参数映射到服务器端的枚举,例如:

@POST
@Path("/zoo")
public Response createNewAnimal(
                        @QueryParam("animal") 
                        @DefaultValue("CAT") AnimalType type 
                ) throws Exception 

...

public enum AnimalType {
    BIG_BIRD,
    SMALL_CAT;
}

但它不起作用!

在处理 Web 请求时,Enum.valueOf()正在调用。当然它会失败,因为该bird用户用作 URL 参数的参数与Enum( AnimalType.BIG_BIRD) 中的标识符不匹配。

无法覆盖valueOf()方法(它是静态的......)并且设置构造函数没有帮助(这是相反的逻辑方向)。

所以也许你知道一个很好的解决方案,而不是仅仅使用 if...else...?

4

2 回答 2

17

如果你有一个像这样的枚举:

public enum AnimalType {
    BIG_BIRD, 
    SMALL_CAT, 
    MEDIUM_DOG;
}

那么为了让 JAX-RS 知道要返回什么实例,您的查询参数必须是 ?animal=BIG_BIRD,?animal=SMALL_CAT?animal=MEDIUM_DOG.

查询参数的值被馈送到valueOf枚举的静态方法以获取实例。当然,如果你发送其他类似的bird东西,它不会匹配任何东西,它也不会工作,因为@QueryParam期望这样:

带注释的参数、字段或属性的类型 T 必须:
- 是原始类型
- 有一个接受单个字符串参数的构造函数
- 有一个名为 valueOf 的静态方法,它接受单个字符串参数(例如,参见 Integer. valueOf(String))
- 是 List、Set 或 SortedSet,其中 T 满足上述 2 或 3。生成的集合是只读的。

同样适用于@DefaultValue也。您必须指定@DefaultValue("BIG_BIRD"),@DefaultValue("SMALL_CAT")@DefaultValue("MEDIUM_DOG"):

@POST
@Path("/zoo")
public Response createNewAnimal(
        @QueryParam("animal") 
        @DefaultValue("SMALL_CAT") AnimalType type) {
    // ...
    return Response.ok().entity(type.toString()).build();
}

如果您不想将 Java 类型上的名称公开给客户端,则可以将正确的查询字符串值转换为枚举实例。if ... else ... if 是实现此目的的一种非常简单的方法,但是如果您想要更高级的东西,则可以创建如下包装器:

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class AnimalTypeWrapper {
    private static final Map<String, AnimalType> MAPPER = Collections
            .unmodifiableMap(new HashMap<String, AnimalType>() {
                {
                    put("bird", AnimalType.BIG_BIRD);
                    put("dog", AnimalType.MEDIUM_DOG);
                    put("cat", AnimalType.SMALL_CAT);
                }
            });

    private AnimalType type;

    public static AnimalTypeWrapper valueOf(String value) {
        AnimalType type = AnimalTypeWrapper.MAPPER.get(value.toLowerCase());
        if (type == null) {
            // if nothing found just set the desired default value
            type = AnimalType.SMALL_CAT;
        }
        return new AnimalTypeWrapper(type);
    }

    private AnimalTypeWrapper(AnimalType type) {
        this.type = type;
    }

    public AnimalType getType() {
        return this.type;
    }
}

并且在您的资源方法中有:

@POST
@Path("/zoo")
public Response createNewAnimal(
        @QueryParam("animal") 
        AnimalTypeWrapper typeWrapper) {
    // ...
    AnimalType type = typeWrapper.getType();
    return Response.ok().entity(type.toString()).build();
}
于 2013-02-09T10:47:03.503 回答
7

使用 JAX-RS 和 Jackson 2.5.0 进行枚举(反)序列化的行为让我迷惑了一段时间,所以我将尝试详细说明 @Bogdan 的答案,并展示对我有用的方法。

我不清楚的是,@QueryParam@FormParam遵循标准程序来反序列化枚举 - 所以如果你试图接受一个枚举作为查询参数,就像这样:

@GET
public Response getAnimals(@QueryParam("animalType") AnimalType animalType) {}

animalType...那么正确反序列化您的参数的唯一方法是您的类型T(在我们的例子中,AnimalType)是否满足以下属性之一:

  1. 成为原始类型。
  2. 有一个接受单个String参数的构造函数。
  3. 有一个名为valueOffromString接受单个String参数的静态方法(例如,参见Integer.valueOf(String))。
  4. 有一个ParamConverterProviderJAX-RS 扩展 SPI 的注册实现,它返回一个ParamConverter能够对该类型进行“从字符串”转换的实例。
  5. 是或List<T>,满足上述 2、3 或 4 的情况。生成的集合是只读的。Set<T>SortedSet<T>T

...根据Java EE 7 @QueryParam 文档

这意味着,除了为您的正常用例实现自定义(反)序列化之外,您还需要满足上面列出的五个条件之一。然后,只有那时!,您将能够处理@QueryParam反序列化案例。

解决方案...

我发现处理正常(反)序列化情况和@QueryParam情况的一种简单方法是a)通过实现满足条件#3 fromString(),并且b)实现一个包含序列化器和反序列化器的映射器类,后者将依赖fromString(),所以我们有一致的反序列化:

// Our example enum class...

@JsonSerialize(using = AnimalTypeMapper.Serializer.class)
@JsonDeserialize(using = AnimalTypeMapper.Deserializer.class)
public enum AnimalType {
  CAT("cat"),
  BIRD("bird"),
  DOG("doggy");

  private final String name;

  AnimalType(String name) {
    this.name = name;
  }

  private static Map<String, AnimalType> VALUES_BY_NAME = Arrays.stream(values())
    .collect(Collectors.toMap(AnimalType::getName, Function.identity()));

  public String getName() {
    return name;
  }

  // Implementing this method allows us to accept AnimalType's as @QueryParam
  // and @FormParam arguments. It's also used in our custom deserializer.
  public static AnimalType fromString(String name) {
    return VALUES_BY_NAME.getOrDefault(name, DOG);
  }
}

// Our custom (de)serialization class...

public class AnimalTypeMapper {
  public static class Serializer extends JsonSerializer<AnimalType> {
    @Override
    public void serialize(AnimalType animalType, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
      jsonGenerator.writeString(animalType.getName());
    }
  }

  public static class Deserializer extends JsonDeserializer<AnimalType> {
    @Override
    public AnimalType deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
      return AnimalType.fromString(jsonParser.getValueAsString());
    }
  }
}

希望有人会发现这很有帮助。我花了太多时间在这上面旋转我的轮子!

于 2016-10-11T23:59:15.320 回答