Как вы сопоставляете несколько параметров запроса с полями bean-компонента в запросе GET Джерси?

Класс обслуживания имеет операцию @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-компоненте, содержащем все эти параметры.

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

Как бы вы это сделали? Это вообще возможно?


person onejigtwojig    schedule 19.04.2011    source источник
comment
Начиная с Джерси 2.0, я думаю, вы захотите использовать BeanParam   -  person Patrick    schedule 19.02.2013
comment
@Patrick, пожалуйста, добавьте свой комментарий в качестве ответа. Если доступна новая информация, можно добавить ответ, чтобы пользователи могли найти новую информацию, не просматривая комментарии.   -  person Jonathan Spooner    schedule 25.06.2013
comment
@JonathanSpooner Теперь, когда версия 2.0 действительно выпущена, это кажется лучшей идеей, чем когда я впервые сделал комментарий, поэтому я принял ваше предложение. Спасибо!   -  person Patrick    schedule 26.06.2013


Ответы (6)


Вы можете использовать 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();
}

Провайдер будет вызван автоматически.

person Vlagorce    schedule 25.05.2011
comment
Такой подход к реализации привязки POJO может создать проблемы позже, когда вы попытаетесь использовать @QueryParam в других методах ресурсов. Лучше создать свою собственную аннотацию и использовать ее вместо @QueryParam. - person Tristan; 07.02.2014
comment
Вместо этого создайте аннотацию с именем QueryBeanParam: @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface QueryBeanParam {}. Затем внедрите InjectableProvider<QueryBeanParam, Parameter> вместо InjectableProvider<QueryParam, Parameter> в ParameterBeanProvider. Наконец, используйте @QueryBeanParam в своем ресурсе: public FindResponse find(@QueryBeanParam ParameterBean paramBean) {...} - person Tristan; 07.02.2014
comment
Это похоже на тонну работы для результата. Его также не видно ни в методе REST, ни в рассматриваемом DTO, поэтому разработчику, который не знал об этом, потребовалось бы некоторое копание. Ответ @BeanParam выглядит так, как будто он гораздо более точно отображает стиль JAX-RS, основанный на аннотациях. - person Michael Haefele; 30.09.2016

В Jersey 2.0 вам следует использовать BeanParam, чтобы беспрепятственно предоставить то, что вы ищете, в обычном стиле Джерси.

На приведенной выше странице документа вы можете использовать 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-компонентах параметров, но вы также можете использовать геттеры/сеттеры и частные поля, если хотите.

person Patrick    schedule 26.06.2013
comment
Есть ли способ избежать размещения @QueryParam для каждого свойства в моем bean-компоненте, если я хочу, чтобы они все были доступны? - person pavel_kazlou; 05.05.2014
comment
@pavel_kazlou Я не знаю предопределенного способа сказать, что все в этом bean-компоненте является отдельным QueryParam. Существует это из Apache CXF. Вы также можете написать свой собственный Injectable Provider (например, выбранный ответ на этой странице). Наконец, вы можете просто взять параметры запроса как Map<String,String> через @Context UriInfo. Помимо этого, я действительно не ожидаю, что Джерси 2.x запечет что-то конкретное только для этого. - person Patrick; 07.05.2014
comment
Проблема в том, что этот компонент должен знать о jaxrs. Пользователь не должен видеть, использует ли он jaxrs или что-то другое, например spring-ws. Другое решение использует метод, упомянутый @onejigtwojig. - person kboom; 02.11.2014

Попробуйте что-нибудь вроде этого. Используйте UriInfo, чтобы вывести все параметры запроса на карту и попытаться получить к ним доступ. Это делается вместо передачи отдельных параметров.

// showing only the relavent code
public FindResponse find( @Context UriInfo allUri ) {
    MultivaluedMap<String, String> mpAllQueParams = allUri.getQueryParameters();
    String prop1 = mpAllQueParams.getFirst("prop1");
}
person kensen john    schedule 19.04.2011
comment
Я пытаюсь избежать этого. Было бы намного чище, если бы я мог избежать зависимости от класса UriInfo в моем классе обслуживания. - person onejigtwojig; 20.04.2011
comment
Определенно намного чище использовать принятый ответ. - person Monarch Wadia; 21.02.2017
comment
Это явно чище. - person eaorak; 06.09.2017

Вы можете создать собственного поставщика.

@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;

    }
person onejigtwojig    schedule 26.04.2011
comment
можете добавить как пользоваться этим провайдером? Спасибо - person manikanta; 09.09.2011
comment
Это решение работает лучше всего, если вы не можете аннотировать свой компонент с помощью аннотаций jaxrs. - person kboom; 02.11.2014

Возможно, вы захотите использовать следующий подход. Это очень стандартное решение, и в нем нет никаких хаков. Вышеупомянутое решение также работает, но несколько хакерское, потому что оно предполагает, что оно имеет дело только с телом запроса, тогда как вместо этого оно извлекает данные из контекста.

В моем случае я хотел создать аннотацию, которая позволила бы сопоставить параметры запроса «предел» и «смещение» с одним объектом. Решение выглядит следующим образом:

@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));
}
person kboom    schedule 02.11.2014

Я знаю, что мой ответ неприменим к конкретному контексту. Но поскольку транспортный механизм WEB должен быть отделен от основного приложения, в любом случае можно было бы перейти на другую веб-инфраструктуру. Как и Spring, webmvc делает все это из коробки.

person takacsot    schedule 27.01.2021