【问题标题】:Get Jersey to work with Optional parameters让 Jersey 使用可选参数
【发布时间】:2020-12-04 17:37:07
【问题描述】:

我正在尝试让 Jersey 使用可选参数。我有一个非常简单的网络服务:

    @Path("helloworld")
    public static class HelloWorldResource {
        public static final String CLICHED_MESSAGE = "Hello World!";

        @GET
        @Produces("text/plain")
        public String getHello(@QueryParam("maybe") Optional<String> maybe) {
            return CLICHED_MESSAGE;
        }
    }

还有一个简单的安全带:

    public static void main(String[] arg) throws IOException {
        ResourceConfig config = new ResourceConfig(HelloWorldResource.class);

        String baseUri = "http://localhost:8080/api/";
        HttpServer server = GrizzlyHttpServerFactory
                .createHttpServer(URI.create(baseUri), config, false);
        server.start();
    }

但是我收到以下错误:

Exception in thread "main" org.glassfish.jersey.server.model.ModelValidationException: Validation of the application resource model has failed during application initialization.
[[FATAL] No injection source found for a parameter of type public java.lang.String com.mercuria.odyssey.server.GrizllyOptional$HelloWorldResource.getHello(java.util.Optional) at index 0.; source='ResourceMethod{httpMethod=GET, consumedTypes=[], producedTypes=[text/plain], suspended=false, suspendTimeout=0, suspendTimeoutUnit=MILLISECONDS, invocable=Invocable{handler=ClassBasedMethodHandler{handlerClass=class com.mercuria.odyssey.server.GrizllyOptional$HelloWorldResource, handlerConstructors=[org.glassfish.jersey.server.model.HandlerConstructor@a3d9978]}, definitionMethod=public java.lang.String com.mercuria.odyssey.server.GrizllyOptional$HelloWorldResource.getHello(java.util.Optional), parameters=[Parameter [type=class java.util.Optional, source=maybe, defaultValue=null]], responseType=class java.lang.String}, nameBindings=[]}']
    at org.glassfish.jersey.server.ApplicationHandler.initialize(ApplicationHandler.java:555)
    at org.glassfish.jersey.server.ApplicationHandler.access$500(ApplicationHandler.java:184)
    at org.glassfish.jersey.server.ApplicationHandler$3.call(ApplicationHandler.java:350)
    at org.glassfish.jersey.server.ApplicationHandler$3.call(ApplicationHandler.java:347)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    at org.glassfish.jersey.internal.Errors.processWithException(Errors.java:255)
    at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:347)
    at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:311)
    at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer.<init>(GrizzlyHttpContainer.java:337)
    at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory.createHttpServer(GrizzlyHttpServerFactory.java:140)
    at com.mercuria.odyssey.server.GrizllyOptional.main(GrizllyOptional.java:33)

我想我需要做点什么让 Jersey 知道如何处理 Optional 参数,但我不知道是什么!

【问题讨论】:

  • Optional 的目的是让 api 在有和没有查询参数的情况下工作吗?如果是这种情况,则查询参数必须是字符串类型。如果您不传递值,“可能”将为空

标签: java jersey jax-rs


【解决方案1】:

因此,允许作为@xxxParam 的参数类型,您需要满足以下要求之一:

  • 成为原始类型

  • 有一个接受单个字符串参数的构造函数

  • 有一个名为 valueOf()fromString() 的静态方法,它接受单个字符串参数(例如,请参阅 Integer.valueOf(String)

  • 有一个ParamConverterProvider JAX-RS 扩展 SPI 的注册实现,它返回一个能够对类型进行“来自字符串”转换的 ParamConverter 实例。

  • 成为List&lt;T&gt;Set&lt;T&gt;SortedSet&lt;T&gt;,其中T 满足上述2、3 或4。生成的集合是只读的。

所以在这种Optional 的情况下,从列表中向下;它不是原始的;它没有 String 构造函数;它没有静态的valueOf()fromString()

所以基本上,剩下的唯一选择就是为它实现一个ParamConverter/ParamConverterProvider 对。 Dropwizard(建立在 Jersey 之上的框架)有一个good implementation for it。我会在这里发布,以防链接失效

import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.internal.inject.Providers;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.internal.util.collection.ClassTypePair;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Optional;
import java.util.Set;

@Singleton
public class OptionalParamConverterProvider implements ParamConverterProvider {
    private final ServiceLocator locator;

    @Inject
    public OptionalParamConverterProvider(final ServiceLocator locator) {
        this.locator = locator;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType, final Annotation[] annotations) {
        if (Optional.class.equals(rawType)) {
            final List<ClassTypePair> ctps = ReflectionHelper.getTypeArgumentAndClass(genericType);
            final ClassTypePair ctp = (ctps.size() == 1) ? ctps.get(0) : null;

            if (ctp == null || ctp.rawClass() == String.class) {
                return new ParamConverter<T>() {
                    @Override
                    public T fromString(final String value) {
                        return rawType.cast(Optional.ofNullable(value));
                    }

                    @Override
                    public String toString(final T value) {
                        return value.toString();
                    }
                };
            }

            final Set<ParamConverterProvider> converterProviders = Providers.getProviders(locator, ParamConverterProvider.class);
            for (ParamConverterProvider provider : converterProviders) {
                final ParamConverter<?> converter = provider.getConverter(ctp.rawClass(), ctp.type(), annotations);
                if (converter != null) {
                    return new ParamConverter<T>() {
                        @Override
                        public T fromString(final String value) {
                            return rawType.cast(Optional.ofNullable(value).map(s -> converter.fromString(value)));
                        }

                        @Override
                        public String toString(final T value) {
                            return value.toString();
                        }
                    };
                }
            }
        }

        return null;
    }
}

注意,如果您使用的是 Jersey 版本 2.26+,而不是注入 ServiceLocator,您将使用 InjectionManager。此外,接受locator 的参数,您将需要更改经理。

有了这个类,你只需要将它注册到你的 Jersey 应用程序中。

【讨论】:

  • 谢谢!这些要求从何而来?似乎如果开箱即用地支持List&lt;X&gt;,那么也可以支持Optional&lt;X&gt;(假设X符合等)。我想这是其中之一。
  • 不确定 List 如何与 Optional 等效。你是说因为 List 有效,所以你 image Optional 应该有效,对吧?我不明白这种联系。
  • 要求来自 JAX-RS 规范,但您将在 javadoc 中看到任何 @XxxParam 注释的相同列表。
【解决方案2】:

这是部分解决方案,但 DropWizard 似乎有一个专门支持此的功能:

https://github.com/dropwizard/dropwizard/blob/master/dropwizard-jersey/src/main/java/io/dropwizard/jersey/optional/OptionalParamBinder.java

所以你可以简单地使用他们的代码:

    import io.dropwizard.jersey.optional.*;

    class DirtyBinder extends AbstractBinder {
        @Override
        protected void configure() {
            bind(OptionalParamConverterProvider.class).to(ParamConverterProvider.class).in(Singleton.class);
            bind(OptionalDoubleParamConverterProvider.class).to(ParamConverterProvider.class).in(Singleton.class);
            bind(OptionalIntParamConverterProvider.class).to(ParamConverterProvider.class).in(Singleton.class);
            bind(OptionalLongParamConverterProvider.class).to(ParamConverterProvider.class).in(Singleton.class);
        }
    }

然后添加:

config.register(new DirtyBinder());

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-29
    • 1970-01-01
    • 1970-01-01
    • 2023-02-07
    • 1970-01-01
    • 1970-01-01
    • 2018-06-23
    相关资源
    最近更新 更多