如何使用基于JAX-RS的Restlet自动将@DefaultValue映射到枚举参数?

时间:2013-02-03 12:53:20

标签: java web enums jax-rs

我有一个网络API,用户可以(或可能不)转移网址参数,例如birddog等。

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

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

...

public enum AnimalType {
    BIG_BIRD,
    SMALL_CAT;
}

但它不起作用!

在处理网络请求时,正在调用Enum.valueOf()。当然它失败了,因为用户用作URL参数的birdEnumAnimalType.BIG_BIRD)中的标识符不匹配。

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

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

2 个答案:

答案 0 :(得分:15)

如果你有一个类似的枚举:

public enum AnimalType {
    BIG_BIRD, 
    SMALL_CAT, 
    MEDIUM_DOG;
}

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

查询参数的值被送到枚举的valueOf静态方法以获取实例。当然,如果您发送其他内容,例如bird,那么它将无法匹配任何内容,因为@QueryParam期望这样做,它将无效:

  

带注释的参数,字段或属性的类型T必须:      
- 是一种原始类型      
- 有一个接受单个String参数的构造函数      
- 有一个名为valueOf的静态方法,它接受一个String参数(例如,参见Integer.valueOf(String))      
- Be 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();
}

答案 1 :(得分:4)

使用JAX-RS和Jackson 2.5.0进行enum(de)序列化的行为让我感到困扰了一段时间,所以我将尝试详细讲述@Bogdan的回答,并展示什么为我工作。

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

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

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

  1. 是一种原始类型。
  2. 拥有一个接受单个String参数的构造函数。
  3. 有一个名为valueOffromString的静态方法接受一个 单String个参数(例如,请参阅Integer.valueOf(String))。
  4. 已注册ParamConverterProvider JAX-RS 扩展SPI返回一个能够执行的ParamConverter实例 &#34;来自字符串&#34;转换类型。
  5. List<T>Set<T>SortedSet<T>,其中T满足2,3或4 以上。生成的集合是只读的。
  6. ...符合Java EE 7 @QueryParam docs

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

    解决方案......

    我发现处理正常(反)序列化情况和@QueryParam情况的一种简单方法是a)通过实现fromString()满足条件#3,以及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());
        }
      }
    }
    

    希望有人会觉得这很有帮助。我花了太多时间在这上面转动轮子!