使用Joda DateTime作为Jersey参数



我想在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));
}
};
}
}

这里有一个依赖于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绑定@提供程序是JAX-RS注释。Guice只需要能够注入UriInfo,而Jersey Guice提供了绑定。

处理Joda DateTime对象在客户端-服务器之间发送的另一个选项是使用适配器和相应的注释显式封送/取消封送它们。其原理是将其封送为Long对象,而解封则使用构造函数调用的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对象进行编组/解编组;通过在适配器中设置断点来进行检查。

从阅读文档中,您似乎需要让您的方法返回一个String,然后将其转换为DateTime,我想使用DateTime(long)构造函数,在codehale中有一个(相对)易于遵循的示例,如果您想让我尝试一下,请告诉我。

@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());
}
}
};
}
}

另一种选择是编写一个带有String参数构造函数的简单包装类。这样的类就不需要向Jersey注册的自定义注入器来作为查询参数注入。

public class DateTimeParam {
private final DateTime value;
public DateTimeParam(String string) {
this.value = DateTime.parse(string);
}
public DateTime get() {
return value;
}
}

尽管这需要在代码中从包装器转换为DateTime,但这比在代码中将String转换为DateTime要好,因为您不必处理解析失败-它将由Jersey以与任何其他类型相同的方式处理。

最新更新