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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14672649/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 17:02:47  来源:igfitidea点击:

How to automatically map @DefaultValue to an enum parameter using JAX-RS based Restlet?

javawebenumsjax-rs

提问by Sophie

I have a web API where the user may (or may not) transfer an URL parameter like for example bird, dogetc.

我有一个Web API,其中用户可以(或者可以不)传送一个URL参数像例如birddog

I want this parameter to be mapped to an enum on the server side, something like:

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

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

...

public enum AnimalType {
    BIG_BIRD,
    SMALL_CAT;
}

But it doesn't work!

但它不起作用!

While processing the web request, Enum.valueOf()is being called. And of course it fails, because the birdthat user uses as URL parameter doesn't match the identifier in the Enum(AnimalType.BIG_BIRD).

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

There is no way to override to valueOf()method (it's static...) and setting constructor doesn't help (it's the opposite logical direction).

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

So maybe you know of a nice solution to this, instead of just using if...else...?

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

回答by Bogdan

If you have an enum like:

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

public enum AnimalType {
    BIG_BIRD, 
    SMALL_CAT, 
    MEDIUM_DOG;
}

then in order for JAX-RS to know what instance to return, your query parameter must be ?animal=BIG_BIRD, ?animal=SMALL_CATor ?animal=MEDIUM_DOG.

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

The value of the query parameter is fed to the valueOfstatic method of the enum to get an instance. Off course, if you send something else like birdit won't match anything and it won't work because @QueryParamexpects this:

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

The type T of the annotated parameter, field or property must either:
- Be a primitive type
- Have a constructor that accepts a single String argument
- Have a static method named valueOf that accepts a single String argument (see, for example, Integer.valueOf(String))
- Be List, Set or SortedSet, where T satisfies 2 or 3 above. The resulting collection is read-only.

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

The same applies for the @DefaultValuealso. You have to specify @DefaultValue("BIG_BIRD"), @DefaultValue("SMALL_CAT")or @DefaultValue("MEDIUM_DOG"):

这同样适用于@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();
}

If you don't want to expose the names on your Java types to the client, you can transform the proper query string value into an enum instance. An if ... else ... if is a very simple way to achieve this but if you want something fancier you could create a wrapper like this:

如果不想将 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;
    }
}

and in your resource method have:

并在您的资源方法中有:

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

回答by jeffwtribble

The behavior of enum (de)serialization with JAX-RS and Hymanson 2.5.0 tripped me up for a while, so I'm going to try and elaborate on @Bogdan's answer, and show what worked for me.

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

The thing that wasn't clear to me was that @QueryParamand @FormParamdon't follow standard procedure to deserialize enums - so if you're trying to accept an enum as a query param, like so:

我不清楚的是,@QueryParam并且@FormParam不遵循标准程序来反序列化枚举 - 因此,如果您尝试接受枚举作为查询参数,如下所示:

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

...then the only way your animalTypeargument will be deserialized properly is if your type T(in our case, AnimalType) satisfies one of the following properties:

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

  1. Be a primitive type.
  2. Have a constructor that accepts a single Stringargument.
  3. Have a static method named valueOfor fromStringthat accepts a single Stringargument (see, for example, Integer.valueOf(String)).
  4. Have a registered implementation of ParamConverterProviderJAX-RS extension SPI that returns a ParamConverterinstance capable of a "from string" conversion for the type.
  5. Be List<T>, Set<T>or SortedSet<T>, where Tsatisfies 2, 3 or 4 above. The resulting collection is read-only.
  1. 是一个原始类型。
  2. 有一个接受单个String参数的构造函数。
  3. 有一个名为valueOf或的静态方法fromString,该方法接受单个String参数(例如,参见Integer.valueOf(String))。
  4. 拥有ParamConverterProviderJAX-RS 扩展 SPI的注册实现,该实现返回一个ParamConverter能够对该类型进行“从字符串”转换的实例。
  5. List<T>Set<T>SortedSet<T>,其中T满足以上 2、3 或 4。生成的集合是只读的。

...per the Java EE 7 @QueryParam docs.

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

This means that, in addition to implementing custom (de)serialization for your normal use cases, you will also need to satisfy one of the five conditions listed above. Then, and only then!, you'll be able to handle the @QueryParamdeserialization case.

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

The solution...

解决方案...

A simple way that I found to handle both the normal (de)serialization cases and the @QueryParamcase is to a) satisfy condition #3 by implementing fromString(), and b) implement a mapper class that contains both a serializer and a deserializer, the latter of which will rely on fromString(), so we have consistent deserialization:

我发现处理正常(反)序列化情况和@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());
    }
  }
}

Hopefully someone out there will find this helpful. I spent way too much time spinning my wheels on this!

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