【问题标题】:Using Joda DateTime as a Jersey parameter?使用 Joda DateTime 作为 Jersey 参数?
【发布时间】:2012-11-20 21:56:40
【问题描述】:

我想在 Jersey 中使用 Joda 的 DateTime 查询参数,但 Jersey 开箱即用不支持此功能。我假设实现InjectableProvider 是添加DateTime 支持的正确方法。

有人能指出我对DateTimeInjectableProvider 的良好实现吗?或者有没有值得推荐的替代方法? (我知道我可以在我的代码中从 DateString 转换,但这似乎是一个较小的解决方案。

谢谢。

解决方案:

我在下面修改了 Gili 的答案,以使用 JAX-RS 中的 @Context 注入机制而不是 Guice。

更新:如果 UriInfo 未注入您的服务方法参数,这可能无法正常工作。

import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.PerRequestTypeInjectableProvider;
import java.util.List;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Provider;
import org.joda.time.DateTime;

/**
 * Enables DateTime to be used as a QueryParam.
 * <p/>
 * @author Gili Tzabari
 */
@Provider
public class DateTimeInjector extends PerRequestTypeInjectableProvider<QueryParam, DateTime>
{
    private final UriInfo uriInfo;

    /**
     * Creates a new DateTimeInjector.
     * <p/>
     * @param uriInfo an instance of {@link UriInfo}
     */
    public DateTimeInjector( @Context UriInfo uriInfo)
    {
        super(DateTime.class);
        this.uriInfo = uriInfo;
    }

    @Override
    public Injectable<DateTime> getInjectable(final ComponentContext cc, final QueryParam a)
    {
        return new Injectable<DateTime>()
        {
            @Override
            public DateTime getValue()
            {
                final List<String> values = uriInfo.getQueryParameters().get(a.value());

                if( values == null || values.isEmpty())
                    return null;
                if (values.size() > 1)
                {
                    throw new WebApplicationException(Response.status(Status.BAD_REQUEST).
                        entity(a.value() + " may only contain a single value").build());
                }
                return new DateTime(values.get(0));
            }
        };
    }
}

【问题讨论】:

    标签: java jersey jax-rs jodatime


    【解决方案1】:

    这是一个依赖于 Guice 的实现。您可以使用不同的注射器,只需稍作修改:

    import com.google.inject.Inject;
    import com.sun.jersey.core.spi.component.ComponentContext;
    import com.sun.jersey.spi.inject.Injectable;
    import com.sun.jersey.spi.inject.PerRequestTypeInjectableProvider;
    import java.util.List;
    import javax.ws.rs.QueryParam;
    import javax.ws.rs.WebApplicationException;
    import javax.ws.rs.core.Response;
    import javax.ws.rs.core.Response.Status;
    import javax.ws.rs.core.UriInfo;
    import javax.ws.rs.ext.Provider;
    import org.joda.time.DateTime;
    
    /**
     * Enables DateTime to be used as a QueryParam.
     * <p/>
     * @author Gili Tzabari
     */
    @Provider
    public class DateTimeInjector extends PerRequestTypeInjectableProvider<QueryParam, DateTime>
    {
        private final com.google.inject.Provider<UriInfo> uriInfo;
    
        /**
         * Creates a new DateTimeInjector.
         * <p/>
         * @param uriInfo an instance of {@link UriInfo}
         */
        @Inject
        public DateTimeInjector(com.google.inject.Provider<UriInfo> uriInfo)
        {
            super(DateTime.class);
            this.uriInfo = uriInfo;
        }
    
        @Override
        public Injectable<DateTime> getInjectable(final ComponentContext cc, final QueryParam a)
        {
            return new Injectable<DateTime>()
            {
                @Override
                public DateTime getValue()
                {
                    final List<String> values = uriInfo.get().getQueryParameters().get(a.value());
                    if (values.size() > 1)
                    {
                        throw new WebApplicationException(Response.status(Status.BAD_REQUEST).
                            entity(a.value() + " may only contain a single value").build());
                    }
                    if (values.isEmpty())
                        return null;
                    return new DateTime(values.get(0));
                }
            };
        }
    }
    

    没有 Guice 绑定。 @Provider 是一个 JAX-RS 注释。 Guice 只需要能够注入 UriInfo 并且 Jersey-Guice 提供该绑定。

    【讨论】:

    • 谢谢,吉利。我没有使用Guice,但我想知道是否可以使用JAX-RS的@Context注入来注入UriInfo?
    • 是的,@Context 用于注入 UriInfo。我还稍微修改了您的代码以处理空 DateTime 查询参数的情况。我的改编作为对我的问题的编辑发布。谢谢!!
    【解决方案2】:

    处理在客户端-服务器之间发送 Joda DateTime 对象的另一个选项是使用适配器和相应的注释显式地编组/解编组它们。 原理是将其编组为 Long 对象,而 de-marshalling 使用 Long 对象为构造函数调用实例化一个新的 DateTime 对象。 Long 对象是通过 getMillis 方法获得的。 要进行这项工作,请指定要在具有 DateTime 对象的类中使用的适配器:

    @XmlElement(name="capture_date")
    @XmlJavaTypeAdapter(XmlDateTimeAdapter.class)
    public DateTime getCaptureDate() { return this.capture_date; }
    public void setCaptureDate(DateTime capture_date) { this.capture_date = capture_date; }
    

    然后编写适配器和XML类来封装Long对象:

    import javax.xml.bind.annotation.adapters.XmlAdapter;
    import org.joda.time.DateTime;
    import org.joda.time.DateTimeZone;
    
    /**
     * Convert between joda datetime and XML-serialisable millis represented as long
     */
    public class XmlDateTimeAdapter  extends XmlAdapter<XmlDateTime, DateTime> {
    
        @Override
        public XmlDateTime marshal(DateTime v) throws Exception {
    
            if(v != null)
                return new XmlDateTime(v.getMillis());
            else
                return new XmlDateTime(0); 
    
    
        }
    
        @Override
        public DateTime unmarshal(XmlDateTime v) throws Exception {
    
            return new DateTime(v.millis, DateTimeZone.UTC);
        }
    }
    
    
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    
    /**
     * XML-serialisable wrapper for joda datetime values.
     */
    @XmlRootElement(name="joda_datetime")
    public class XmlDateTime {
    
        @XmlElement(name="millis") public long millis;
    
        public XmlDateTime() {};
    
        public XmlDateTime(long millis) { this.millis = millis; }   
    
    }
    

    如果一切按计划进行,则应使用适配器对 DateTime 对象进行编组/解编组;通过在适配器中设置断点来检查这一点。

    【讨论】:

      【解决方案3】:

      从阅读the documentation 来看,您似乎需要让您的方法返回一个字符串,然后将其转换为日期时间,我想使用DateTime(long) constructor,有一个(相对)易于-关注example at codehale,如果您希望我尝试一下,请告诉我。

      【讨论】:

        【解决方案4】:

        @Gili,对不起,我没有直接评论您的帖子所需的声誉,但请您:

        • 添加用于您的实施的导入语句?
        • 添加一个如何使用 Guice 绑定所有内容的示例?

        非常感谢您。

        M.


        问题

        我有兴趣做和 HolySamosa 一样的事情,我也使用 Guice,但我面临以下问题。

        如果我添加:

        bind(DateTimeInjector.class);
        

        在我的GuiceServletContextListener 中,我得到:

        java.lang.RuntimeException: 
        The scope of the component class com.foo.mapping.DateTimeInjector must be a singleton
        

        如果我在 DateTimeInjector 类上添加 @Singleton,我会得到:

        GRAVE: The following errors and warnings have been detected with resource and/or provider classes:
        SEVERE: Missing dependency for method public java.util.List com.foo.ThingService.getThingByIdAndDate(java.lang.String,org.joda.time.DateTime) at parameter at index 1
        SEVERE: Method, public java.util.List com.foo.ThingService.getThingByIdAndDate(java.lang.String,org.joda.time.DateTime), annotated with GET of resource, class com.foo.ThingService, is not recognized as valid resource method.
        

        建议/解决方案

        • 注意你使用的注释(不像我)!例如,我实际上使用的是@PathParam 而不是@QueryParam
        • 在您的服务中,您不需要在方法的签名中包含UriInfo uriInfo。只需要功能参数就足够了,无论UriInfo 是否存在,它都应该可以工作。
        • Guice 需要配置以下内容才能拾取注射器。

        例子:

        // Configure Jersey with Guice:
        Map<String, String> settings = new HashMap<String, String>();
        settings.put(PackagesResourceConfig.PROPERTY_PACKAGES, "com.foo.mapping");
        serve("/*").with(GuiceContainer.class, settings);
        

        完整解决方案

        import java.util.List;
        
        import javax.ws.rs.PathParam;
        import javax.ws.rs.WebApplicationException;
        import javax.ws.rs.core.Response;
        import javax.ws.rs.core.Response.Status;
        import javax.ws.rs.core.UriInfo;
        import javax.ws.rs.ext.Provider;
        
        import org.joda.time.DateTime;
        
        import com.google.inject.Inject;
        import com.foo.utils.DateTimeAdapter;
        import com.sun.jersey.core.spi.component.ComponentContext;
        import com.sun.jersey.spi.inject.Injectable;
        import com.sun.jersey.spi.inject.PerRequestTypeInjectableProvider;
        
        /**
         * Enables DateTime to be used as a PathParam.
         */
        @Provider
        public class DateTimeInjector extends PerRequestTypeInjectableProvider<PathParam, DateTime> {
            private final com.google.inject.Provider<UriInfo> uriInfo;
        
            /**
             * Creates a new DateTimeInjector.
             * 
             * @param uriInfo
             *            an instance of {@link UriInfo}
             */
            @Inject
            public DateTimeInjector(com.google.inject.Provider<UriInfo> uriInfo) {
                super(DateTime.class);
                this.uriInfo = uriInfo;
            }
        
            @Override
            public Injectable<DateTime> getInjectable(final ComponentContext context, final PathParam annotation) {
                return new Injectable<DateTime>() {
                    @Override
                    public DateTime getValue() {
                        final List<String> values = uriInfo.get().getPathParameters().get(annotation.value());
        
                        if (values == null) {
                            throwInternalServerError(annotation);
                        }
        
                        if (values.size() > 1) {
                            throwBadRequestTooManyValues(annotation);
                        }
        
                        if (values.isEmpty()) {
                            throwBadRequestMissingValue(annotation);
                        }
        
                        return parseDate(annotation, values);
                    }
        
                    private void throwInternalServerError(final PathParam annotation) {
                        String errorMessage = String.format("Failed to extract parameter [%s] using [%s]. This is likely to be an implementation error.",
                                annotation.value(), annotation.annotationType().getName());
                        throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(errorMessage).build());
                    }
        
                    private void throwBadRequestTooManyValues(final PathParam annotation) {
                        String errorMessage = String.format("Parameter [%s] must only contain one single value.", annotation.value());
                        throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(errorMessage).build());
                    }
        
                    private void throwBadRequestMissingValue(final PathParam annotation) {
                        String errorMessage = String.format("Parameter [%s] must be provided.", annotation.value());
                        throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(errorMessage).build());
                    }
        
                    private DateTime parseDate(final PathParam annotation, final List<String> values) {
                        try {
                            return DateTimeAdapter.parse(values.get(0));
                        } catch (Exception e) {
                            String errorMessage = String.format("Parameter [%s] is formatted incorrectly: %s", annotation.value(), e.getMessage());
                            throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(errorMessage).build());
                        }
                    }
        
                };
            }
        }
        

        【讨论】:

        • @HolySamosa:如果您不在服务的方法参数中提供UriInfo uriInfo,则在没有 Guice 的情况下使用 @Context 似乎不起作用。您是否只有 DateTime 参数或其他一些 Jersey 特定参数?例如public void saveResource(@Context UriInfo uriInfo, @QueryParam("date") DateTime date) {...} 还是你使用了其他“技巧”?
        • @Marc:碰巧,我已经在我的服务方法参数中提供了 UriInfo,所以我没有意识到这是一个问题。诚然,我还没有真正了解 Provider 类。我认为有一种方法可以在不需要注入 UriInfo 的情况下完成此操作,但我可能是错的......
        猜你喜欢
        • 2013-10-17
        • 2014-03-06
        • 2011-06-07
        • 2014-10-13
        • 1970-01-01
        • 1970-01-01
        • 2015-01-03
        • 2018-04-02
        • 2023-03-30
        相关资源
        最近更新 更多