【问题标题】:jersey 2: How to create custom HTTP param binding球衣 2:如何创建自定义 HTTP 参数绑定
【发布时间】:2025-11-28 06:20:03
【问题描述】:

我正在尝试为我的 restful 服务创建一个自定义 http 参数绑定。请看下面的例子。

@POST
@Path("/user/{userId}/orders")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public MyResult foo(@PathParam("userId") String someString, @UserAuthHeaderParam String authString){

}

可以看到函数签名中有一个UserAuthHeaderParam注解。我想要做的是有一个自定义的 http 参数绑定,而不是标准的 javax.ws.rs.*Param 。

我尝试实现 org.glassfish.hk2.api.InjectionResolver,它基本上从 http 标头中提取值:

public class ProtoInjectionResolver implements InjectionResolver<UserAuthHeaderParam>{
...
@Override
public Object resolve(Injectee injectee, ServiceHandle< ? > root)
{

    return "Hello World";
}
...

}

当我调用 restful 服务时,服务器出现以下异常。表示框架解析函数签名中的参数失败:

org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=String,parent=MyResource,qualifiers={}),position=0,optional=false,self=false,unqualified=null,2136594195), 

java.lang.IllegalArgumentException: While attempting to resolve the dependencies of rs.server.MyResource errors were found

请帮忙。任何建议表示赞赏。我确实在谷歌上进行了很多搜索,但未能成功。 Jersey 2.*. How to replace InjectableProvider and AbstractHttpContextInjectable of Jersey 1.* 可能是类似的问题。

-- 更新: 我使用 AbstractBinder 将我的解析器绑定到 UserAuthHeaderParam:

public class MyApplication extends ResourceConfig
{

public MyApplication()
{
    register(new AbstractBinder()
    {
        @Override
        protected void configure()
        {
            // bindFactory(UrlStringFactory.class).to(String.class);
            bind(UrlStringInjectResolver.class).to(new TypeLiteral<InjectionResolver<UrlInject>>()
            {
            }).in(Singleton.class);
        }
    });
    packages("rs");

}

}

谢谢!

【问题讨论】:

  • @Service 在哪里?
  • @MingtaoZhang 我注册了一个 AbstractBinder。编辑我的问题以添加此详细信息
  • 您使用的是哪个版本的球衣罐?你是在任何应用服务器或 grizzly 上运行它吗?
  • @DhanaKrishnasamy 我正在使用 jersey 2.0 和 grizzly。
  • @yzandrew 为什么要创建自定义 Http 参数?也许您可以使用@HeaderParam 并通过 HTTP 标头将 UserAuth 值传递给您的 REST API?

标签: java jersey-2.0 grizzly


【解决方案1】:

这是我对 UserAuthHeaderParamValueFactoryProvider 类的实际实现

import javax.inject.Inject;
import javax.inject.Singleton;

import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;

    import org.glassfish.jersey.server.model.Parameter;

    @Singleton
    public class UserAuthHeaderParamValueFactoryProvider extends AbstractValueFactoryProvider {

        @Inject
        protected UserAuthHeaderParamValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) {
            super(mpep, locator, Parameter.Source.UNKNOWN);
        }

        @Override
        protected Factory<?> createValueFactory(Parameter parameter) {
            Class<?> classType = parameter.getRawType();

            if (classType == null || (!classType.equals(String.class))) {
                return null;
            }

            return new AbstractContainerRequestValueFactory<String>() {
                @Override
                public String provide() {
                    //you can use get any header value.
                    return getContainerRequest().getHeaderString("Authorization");
                }

            };
        }

【讨论】:

    【解决方案2】:

    如果您只想将值直接从标头传递给不需要创建自定义注释的方法。假设您有一个标头Authorization,那么您可以通过这样声明您的方法轻松访问它:

    @GET
    public String authFromHeader(@HeaderParam("Authorization") String authorization) {
        return "Header Value: " + authorization + "\n";
    }
    

    您可以通过调用curl来测试它,例如

    $ curl --header "Authorization: 1234" http://localhost:8080/rest/resource
    Header Value: 1234
    

    鉴于您的问题的答案,如何创建自定义绑定如下。

    首先你必须像这样声明你的注解:

    @java.lang.annotation.Target(PARAMETER)
    @java.lang.annotation.Retention(RUNTIME)
    @java.lang.annotation.Documented
    public @interface UserAuthHeaderParam {
    }
    

    声明注释后,您必须定义如何解决它。声明 Value Factory Provider(这是您可以访问标头参数的地方 - 请参阅我的评论):

    @Singleton
    public class UserAuthHeaderParamValueFactoryProvider extends AbstractValueFactoryProvider {
    
        @Inject
        protected UserAuthHeaderParamValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) {
            super(mpep, locator, Parameter.Source.UNKNOWN);
        }
    
        @Override
        protected Factory<?> createValueFactory(Parameter parameter) {
            Class<?> classType = parameter.getRawType();
    
            if (classType == null || (!classType.equals(String.class))) {
                return null;
            }
    
            return new AbstractHttpContextValueFactory<String>() {
                @Override
                protected String get(HttpContext httpContext) {
                    // you can get the header value here
                    return "testString";
                }
            };
        }
    }
    

    现在声明一个注入解析器

    public class UserAuthHeaderParamResolver extends ParamInjectionResolver<UserAuthHeaderParam> {
        public UserAuthHeaderParamResolver() {
            super(UserAuthHeaderParamValueFactoryProvider.class);
        }
    }
    

    以及用于您的配置的 Binder

    public class HeaderParamResolverBinder extends AbstractBinder {
    
        @Override
        protected void configure() {
            bind(UserAuthHeaderParamValueFactoryProvider.class)
                    .to(ValueFactoryProvider.class)
                    .in(Singleton.class);
    
            bind(UserAuthHeaderParamResolver.class)
                    .to(new TypeLiteral<InjectionResolver<UserAuthHeaderParam>>() {})
                    .in(Singleton.class);
        }
    }
    

    现在最后一件事,在你的 ResourceConfig 中添加register(new HeaderParamResolverBinder()),像这样

    @ApplicationPath("rest")
    public class MyApplication extends ResourceConfig {
        public MyApplication() {
            register(new HeaderParamResolverBinder());
            packages("your.packages");
        }
    }
    

    鉴于此,您现在应该可以随意使用该值了:

    @GET
    public String getResult(@UserAuthHeaderParam String param) {
        return "RESULT: " + param;
    }
    

    我希望这会有所帮助。

    【讨论】:

    • 当您与其他现有注释混合时,这是否有效,例如 @PathParam 在同一方法中?
    • @DhanaKrishnasamy 很明显,你不能在同一个方法参数上添加注释,但你可以有像 @Path("{id}") public String getResult(@UserAuthHeaderParam String user, @PathParam("id") Integer id) 这样的方法,如果这是你所要求的?
    • @lpiepiora 感谢您的解决方案!我真正需要的是将所有 HTTP 标头绑定到一个对象中。所以我必须创建一个自定义绑定。我对迟到的回复感到抱歉。我正在从事几个项目,所以我没有机会尝试您的解决方案。我会在核实后回复。应该是下周初!再次感谢!
    • @lpiepiora 您的解决方案对我有用!这正是我所需要的。
    • 谢谢,非常有用的帖子!只有一个问题,我在 Jersey2.6 上找不到AbstractHttpContextValueFactory。你知道我们可以在哪里使用吗?再次感谢!
    【解决方案3】:

    如果您需要检索绑定到一个对象中的所有 http 标头,则可以使用@Context 注释来获取javax.ws.rs.core.HttpHeaders;其中包含所有请求标头的列表。

    @POST
    @Path("/user/{userId}/orders")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public MyResult foo(@PathParam("userId") String someString, @Context HttpHeaders headers){
     // You can list all available HTTP request headers via following code :
       for(String header : headers.getRequestHeaders().keySet()){
         System.out.println(header);
       }
    }
    

    【讨论】:

    • 感谢您的回复。我认为自定义绑定对我来说是更好的方法。我可以将 http headers/query param/path param 绑定到一个 protobuff 对象中,然后在我的业务逻辑中将其用作模型对象的一部分。
    【解决方案4】:

    我不知道如何解决您的异常。但是,我可以建议您以不同的方式来做同样的事情。希望对你有帮助。

    我遇到了完全相同的问题:我需要在 http 标头中添加额外的参数(顺便说一句,也与身份验证有关)。此外,我需要在每次通话中发送它们,因为我想做一个“典型的”休息实现,而不是维护一个会话。

    我正在使用 Jersey 2.7 - 但我想说它应该在 2.0 中工作。我遵循了他们的文档 https://jersey.java.net/documentation/2.0/filters-and-interceptors.html

    那里很清楚,但无论如何我在下面复制粘贴我的实现。 它工作正常。确实有一些其他方法可以确保休息服务,例如这是一个很好的方法: http://www.objecthunter.net/tinybo/blog/articles/89

    但它们取决于应用服务器的实现和您使用的数据库。在我看来,过滤器更灵活,更容易实现。

    复制粘贴:我为身份验证定义了一个过滤器,它适用于每个调用,并且在服务之前执行(感谢@PreMatching)。

    @PreMatching
    public class AuthenticationRequestFilter implements ContainerRequestFilter {
    
        @Override
        public void filter(final ContainerRequestContext requestContext) throws IOException {
            final MultivaluedMap<String, String> headers = requestContext.getHeaders();
            if (headers == null) {
                throw new...
            }
    
            // here I get parameters from the header, via headers.get("parameter_name")
            // In particular, I get the profile, which I plan to use as a Jersey role
            // then I authenticate
            // finally, I inform the Principal and the role in the SecurityContext object, so that I can use @RolesAllowed later
            requestContext.setSecurityContext(new SecurityContext() {
    
                @Override
                public boolean isUserInRole(final String arg0) {
                    //...
                }
    
                @Override
                public boolean isSecure() {
                    //...
                }
    
                @Override
                public Principal getUserPrincipal() {
                    //...
                }
    
                @Override
                public String getAuthenticationScheme() {
                    //...
                }
            });
    
        }
    
    }
    

    您必须在 ResourceConfig 的实现中包含此过滤器类,

    public class MyResourceConfig extends ResourceConfig {
    
        public MyResourceConfig() {
    
            // my init
            // my packages
            register(AuthenticationRequestFilter.class); // filtro de autenticación
            // other register
    
        }
    
    }
    

    希望对你有帮助!

    【讨论】:

    • 感谢您的解决方案!我必须坚持自定义绑定。有关详细原因,请参阅我对 lpiepiora 的 cmets。但是,您的解决方案可能会帮助其他有类似问题的人。
    最近更新 更多