如何在Jersey GET请求中将多个查询参数映射到bean的字段?

54

一个服务类有一个@GET操作,可以接受多个参数。这些参数作为查询参数传递给@GET服务调用。

@GET
@Path("find")
@Produces(MediaType.APPLICATION_XML)
public FindResponse find(@QueryParam("prop1") String prop1, 
                         @QueryParam("prop2") String prop2, 
                         @QueryParam("prop3") String prop3, 
                         @QueryParam("prop4") String prop4, ...) 

这些参数的列表正在不断增长,因此我希望将它们放入一个单独的 bean 中,该 bean 包含所有这些参数。

@GET
@Path("find")
@Produces(MediaType.APPLICATION_XML)
public FindResponse find(ParameterBean paramBean) 
{
    String prop1 = paramBean.getProp1();
    String prop2 = paramBean.getProp2();
    String prop3 = paramBean.getProp3();
    String prop4 = paramBean.getProp4();
}

你会如何做到这一点?这是可能的吗?


3
从Jersey 2.0开始,我相信您会希望使用BeanParam - Patrick
1
@Patrick请将您的评论添加为答案。如果有新信息可用,则可以添加答案,以便用户无需查看评论即可找到新信息。 - Jonathan Spooner
2
@JonathanSpooner 现在2.0版本已经发布,这个想法看起来比我第一次评论时更好,所以我采纳了你的建议。谢谢! - Patrick
6个回答

106

Jersey 2.0 中,您将希望使用 BeanParam 以无缝方式提供您在普通 Jersey 风格中所需的内容。

从上面链接的文档页面中,您可以使用 BeanParam 来实现类似以下的操作:

@GET
@Path("find")
@Produces(MediaType.APPLICATION_XML)
public FindResponse find(@BeanParam ParameterBean paramBean) 
{
    String prop1 = paramBean.prop1;
    String prop2 = paramBean.prop2;
    String prop3 = paramBean.prop3;
    String prop4 = paramBean.prop4;
}

然后ParameterBean.java将包含:

public class ParameterBean {
     @QueryParam("prop1") 
     public String prop1;

     @QueryParam("prop2") 
     public String prop2;

     @QueryParam("prop3") 
     public String prop3;

     @QueryParam("prop4") 
     public String prop4;
}

我更喜欢在我的参数bean上使用公共属性,但如果您愿意,也可以使用getter/setter和私有字段。


6
是否有办法可以避免在我的bean中为每个属性都设置@QueryParam,如果我希望它们全部可用? - pavel_kazlou
2
@pavel_kazlou 我不知道有没有预定义的方法来表达“这个bean中的所有内容都是独立的QueryParam”。Apache CXF中存在这个链接。你也可以编写自己的Injectable Provider(就像这个页面上的选定答案)。最后,你可以通过@Context UriInfo将查询参数作为Map<String,String>。除此之外,我并不认为Jersey 2.x中有任何特定的内置功能。 - Patrick
问题在于这个bean必须知道jaxrs的存在。用户不应该知道他是否正在使用jaxrs或类似spring-ws的其他东西。另一种解决方案是使用@onejigtwojig提到的方法。 - kboom

25

尝试这样做。使用UriInfo将所有请求参数获取到一个map中并尝试访问它们,这样可以替代传递单个参数。

// showing only the relavent code
public FindResponse find( @Context UriInfo allUri ) {
    MultivaluedMap<String, String> mpAllQueParams = allUri.getQueryParameters();
    String prop1 = mpAllQueParams.getFirst("prop1");
}

4
我正在尝试避免这种情况。如果我能避免在我的服务类中依赖UriInfo类,那将会更加简洁。 - onejigtwojig
使用被接受的答案肯定更加简洁。 - Monarch Wadia
这显然更加简洁。 - koders

21

您可以使用com.sun.jersey.spi.inject.InjectableProvider

import java.util.List;
import java.util.Map.Entry;

import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;

import org.springframework.beans.BeanUtils;

import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.api.model.Parameter;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;

@Provider
public final class ParameterBeanProvider implements InjectableProvider<QueryParam, Parameter> {

    @Context
    private final HttpContext hc;

    public ParameterBeanProvider(@Context HttpContext hc) {
        this.hc = hc;
    }

    @Override
    public ComponentScope getScope() {
        return ComponentScope.PerRequest;
    }

    @Override
    public Injectable<ParameterBean> getInjectable(ComponentContext ic, final QueryParam a, final Parameter c) {

        if (ParameterBean.class != c.getParameterClass()) {
            return null;
        }

        return new Injectable<ParameterBean>() {

            public ParameterBean getValue() {
                ParameterBean parameterBean = new ParameterBean();
                MultivaluedMap<String, String> params = hc.getUriInfo().getQueryParameters();
                // Populate the parameter bean properties
                for (Entry<String, List<String>> param : params.entrySet()) {
                    String key = param.getKey();
                    Object value = param.getValue().iterator().next();

                    // set the property
                    BeanUtils.setProperty(parameterBean, key, value);
                }
                return parameterBean;
            }
        };
    }
}

在你的资源中,你只需要使用 @QueryParam("valueWeDontCare")

@GET
@Path("find")
@Produces(MediaType.APPLICATION_XML)
public FindResponse find(@QueryParam("paramBean") ParameterBean paramBean) {
    String prop1 = paramBean.getProp1();
    String prop2 = paramBean.getProp2();
    String prop3 = paramBean.getProp3();
    String prop4 = paramBean.getProp4();
}
提供者将会被自动调用。

5
在其他资源方法中尝试使用@QueryParam时,这种实现POJO绑定的方法可能会导致问题。最好创建自己的注释并使用该注释,而不是使用@QueryParam。 - Tristan
6
相反,创建一个名为QueryBeanParam的注释: @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface QueryBeanParam {} 然后,在ParameterBeanProvider中实现InjectableProvider<QueryBeanParam, Parameter>而不是InjectableProvider<QueryParam, Parameter>。最后,在您的资源中使用@QueryBeanParampublic FindResponse find(@QueryBeanParam ParameterBean paramBean) {...} - Tristan
1
这似乎是为了结果而付出了大量的工作。它在所讨论的REST方法或DTO上也不可见,因此需要一些挖掘工作,以便开发人员了解它。@BeanParam答案感觉更接近JAX-RS的注释驱动风格。 - Michael Haefele

7
您可以创建自定义的提供者。
@Provider
@Component
public class RequestParameterBeanProvider implements MessageBodyReader
{
    // save the uri
    @Context
    private UriInfo uriInfo;

    // the list of bean classes that need to be marshalled from
    // request parameters
    private List<Class> paramBeanClassList;

    // list of enum fields of the parameter beans
    private Map<String, Class> enumFieldMap = new HashMap<String, Class>();

    @Override
    public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType)
    {
        return paramBeanClassList.contains(type);
    }

    @Override
    public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException
    {
        MultivaluedMap<String, String> params = uriInfo.getQueryParameters();

        Object newRequestParamBean;
        try
        {
            // Create the parameter bean
            newRequestParamBean = type.newInstance();

            // Populate the parameter bean properties
            for (Entry<String, List<String>> param : params.entrySet())
            {
                String key = param.getKey();
                Object value = param.getValue().iterator().next();

                // set the property
                BeanUtils.setProperty(newRequestParamBean, key, value);
            }
        }
        catch (Exception e)
        {
            throw new WebApplicationException(e, 500);
        }

        return newRequestParamBean;
    }

    public void setParamBeanClassList(List<Class> paramBeanClassList)
    {
        this.paramBeanClassList = paramBeanClassList;

    }

2
你能添加如何使用这个提供程序的说明吗?谢谢。 - manikanta
如果您无法使用jaxrs注释为bean提供注释,则此解决方案最有效。 - kboom

2
您可能想使用以下方法。这是一个非常符合标准的解决方案,其中没有任何技巧。上面的解决方案也可以工作,但有些不太正规,因为它只处理请求体,而是从上下文中提取数据。在我的情况下,我想创建一个注释,允许将查询参数“limit”和“offset”映射到单个对象。解决方案如下:
@Provider
public class SelectorParamValueFactoryProvider extends AbstractValueFactoryProvider {

    public static final String OFFSET_PARAM = "offset";

    public static final String LIMIT_PARAM = "limit";

    @Singleton
    public static final class InjectionResolver extends ParamInjectionResolver<SelectorParam> {

        public InjectionResolver() {
            super(SelectorParamValueFactoryProvider.class);
        }

    }

    private static final class SelectorParamValueFactory extends AbstractContainerRequestValueFactory<Selector> {

        @Context
        private ResourceContext  context;

        private Parameter parameter;

        public SelectorParamValueFactory(Parameter parameter) {
            this.parameter = parameter;
        }

        public Selector provide() {
            UriInfo uriInfo = context.getResource(UriInfo.class);
            MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
            SelectorParam selectorParam = parameter.getAnnotation(SelectorParam.class);
            long offset = selectorParam.defaultOffset();
            if(params.containsKey(OFFSET_PARAM)) {
                String offsetString = params.getFirst(OFFSET_PARAM);
                offset = Long.parseLong(offsetString);
            }
            int limit = selectorParam.defaultLimit();
            if(params.containsKey(LIMIT_PARAM)) {
                String limitString = params.getFirst(LIMIT_PARAM);
                limit = Integer.parseInt(limitString);
            }
            return new BookmarkSelector(offset, limit);
        }

    }

    @Inject
    public SelectorParamValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator injector) {
        super(mpep, injector, Parameter.Source.UNKNOWN);
    }

    @Override
    public AbstractContainerRequestValueFactory<?> createValueFactory(Parameter parameter) {
        Class<?> classType = parameter.getRawType();
        if (classType == null || (!classType.equals(Selector.class))) {
            return null;
        }

        return new SelectorParamValueFactory(parameter);
    }

}

你还需要做的是注册它。
public class JerseyApplication extends ResourceConfig {

    public JerseyApplication() {
        register(JacksonFeature.class);
        register(new InjectionBinder());
    }

    private static final class InjectionBinder extends AbstractBinder {

        @Override
        protected void configure() {
            bind(SelectorParamValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
            bind(SelectorParamValueFactoryProvider.InjectionResolver.class).to(
                    new TypeLiteral<InjectionResolver<SelectorParam>>() {
                    }).in(Singleton.class);
        }

    }

}

您需要的注释本身也很重要。
@Target({java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD})
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
public @interface SelectorParam {

    long defaultOffset() default 0;

    int defaultLimit() default 25;

}

和一颗豆子

public class BookmarkSelector implements Bookmark, Selector {

    private long offset;

    private int limit;

    public BookmarkSelector(long offset, int limit) {
        this.offset = offset;
        this.limit = limit;
    }

    @Override
    public long getOffset() {
        return 0;
    }

    @Override
    public int getLimit() {
        return 0;
    }

    @Override
    public boolean matches(Object object) {
        return false;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        BookmarkSelector that = (BookmarkSelector) o;

        if (limit != that.limit) return false;
        if (offset != that.offset) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = (int) (offset ^ (offset >>> 32));
        result = 31 * result + limit;
        return result;
    }

}

然后你可以像这样使用它。
@GET
@Path(GET_ONE)
public SingleResult<ItemDTO> getOne(@NotNull @PathParam(ID_PARAM) String itemId, @SelectorParam Selector selector) {
    Item item = auditService.getOneItem(ItemId.create(itemId));
    return singleResult(mapOne(Item.class, ItemDTO.class).select(selector).using(item));
}

0

我知道我的答案不适用于特定的上下文。但是由于WEB传输机制应该与核心应用程序分开,因此可以选择更改为其他Web框架。像Spring webmvc已经在盒子外完成了所有这些工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接