How do you map multiple query parameters to the fields of a bean on Jersey GET request?

后端 未结 5 1622
傲寒
傲寒 2020-11-30 19:27

A service class has a @GET operation that accepts multiple parameters. These parameters are passed in as query parameters to the @GET service call.

相关标签:
5条回答
  • 2020-11-30 20:03

    In Jersey 2.0, you'll want to use BeanParam to seamlessly provide what you're looking for in the normal Jersey style.

    From the above linked doc page, you can use BeanParam to do something like:

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

    And then ParameterBean.java would contain:

    public class ParameterBean {
         @QueryParam("prop1") 
         public String prop1;
    
         @QueryParam("prop2") 
         public String prop2;
    
         @QueryParam("prop3") 
         public String prop3;
    
         @QueryParam("prop4") 
         public String prop4;
    }
    

    I prefer public properties on my parameter beans, but you can use getters/setters and private fields if you like, too.

    0 讨论(0)
  • 2020-11-30 20:06

    You might want to use the following approach. This is a very standard-compliant solution and there are no hacks in there. The above solution also works but is somewhat hacky because it suggests it deals only with request body whereas it extracts the data from the context instead.

    In my case I wanted to create an annotation which would allow to map query parameters "limit" and "offset" to a single object. The solution is as follows:

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

    What you also need to do is registering it.

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

    You also need the annotation itself

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

    and a bean

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

    Then you might use it like this

    @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 讨论(0)
  • 2020-11-30 20:20

    You can create a custom Provider.

    @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;
    
        }
    
    0 讨论(0)
  • 2020-11-30 20:29

    You can use 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;
                }
            };
        }
    }
    

    In your resource you just have to use @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();
    }
    

    The provider will be automatically called.

    0 讨论(0)
  • 2020-11-30 20:29

    Try something like this. Use UriInfo to get all the request parameters into a map and try to access them. This is done inplace of passing individual parameters.

    // showing only the relavent code
    public FindResponse find( @Context UriInfo allUri ) {
        MultivaluedMap<String, String> mpAllQueParams = allUri.getQueryParameters();
        String prop1 = mpAllQueParams.getFirst("prop1");
    }
    
    0 讨论(0)
提交回复
热议问题