【问题标题】:JEE6 REST Service @AroundInvoke Interceptor is injecting a null HttpServletRequest objectJEE6 REST 服务 @AroundInvoke 拦截器正在注入一个空的 HttpServletRequest 对象
【发布时间】:2013-07-08 05:47:39
【问题描述】:

我有一个 @AroundInvoke REST Web 服务拦截器,我想用它来记录常见数据,例如类和方法、远程 IP 地址和响应时间。

使用InvocationContext获取类和方法名很简单,通过HttpServletRequest获取远程IP,只要被拦截的Rest Service在其参数列表中包含@Context HttpServletRequest。

但是,一些 REST 方法的参数中没有 HttpServletRequest,我不知道在这些情况下如何获取 HttpServletRequest 对象。

例如,以下 REST Web 服务没有 @Context HttpServletRequest 参数

@Inject
@Default
private MemberManager memberManager;

@POST
@Path("/add")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Member add(NewMember member) throws MemberInvalidException {
    return memberManager.add(member);
}

我尝试将它直接注入到我的拦截器中,但是(在 JBoss 6.1 上)它始终为空...

public class RestLoggedInterceptorImpl implements Serializable {
    @Context
    HttpServletRequest req;

    @AroundInvoke
    public Object aroundInvoke(InvocationContext ic) throws Exception {

        logger.info(req.getRemoteAddr());  // <- this throws NPE as req is always null
        ...
        return ic.proceed();

我想要一个可靠的方法来访问 HttpServletRequest 对象 - 甚至只是 Http Headers ...无论 REST 服务是否包含该参数。

【问题讨论】:

    标签: jboss jax-rs java-ee-6 interceptor jboss-weld


    【解决方案1】:

    在研究了 Javadoc http://docs.oracle.com/javaee/6/api/javax/interceptor/package-summary.html 中的 Interceptor Lifecycle 之后,我认为除了 InvocationContext(由底层 REST 定义中的参数定义)中的信息之外,无法访问任何 servlet 上下文信息。这是因为拦截器实例与底层 bean 具有相同的生命周期,必须将 Servlet 请求 @Context 注入到方法而不是实例中。但是,如果方法签名中存在 InvocationContext 以外的任何内容,则包含 @AroundInvoke 的拦截器将不会部署;它不接受额外的@Context 参数。

    所以我能想出的让拦截器获取 HttpServletRequest 的唯一答案是修改底层 REST 方法定义以包含 @Context HttpServletRequest 参数(如果需要,还可以添加 HttpServletResponse)。

    @Inject
    @Default
    private MemberManager memberManager;
    
    @POST
    @Path("/add")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Member add(NewMember member, @Context HttpServletRequest request, @Context HttpServletResponse response) throws MemberInvalidException {
        ...
    }
    

    然后拦截器就可以遍历InvocationContext中的参数来获取HttpServletRequest

    @AroundInvoke
    public Object aroundInvoke(InvocationContext ic) throws Exception {
        HttpServletRequest req = getHttpServletRequest(ic);
        ...
        return ic.proceed();
    }
    
    private HttpServletRequest getHttpServletRequest(InvocationContext ic) {
        for (Object parameter : ic.getParameters()) {
            if (parameter instanceof HttpServletRequest) {
                return (HttpServletRequest) parameter;
            }
        }
        // ... handle no HttpRequest object.. e.g. log an error, throw an Exception or whatever
    

    【讨论】:

    • 这是否意味着您必须在应用程序的每个 REST 方法中包含一个 @Context HttpServletRequest 参数?它必须是一个更好的方法。我对 Java EE6 还很陌生,我正在努力解决一个非常相似(如果不是相同的话)的问题。我在这里写了一个问题,它不是问同样的问题,但归结为:我需要检查拦截器上的会话才能执行授权。 stackoverflow.com/questions/19453557/…
    • @nosuchnick 我还认为“必须有更好的方法”,这就是我问这个问题的原因。这似乎是 JEE6 的小烦恼之一,其中添加拦截器需要对拦截的代码进行样板更改。
    • 当然。我最终在@SessionScoped bean 中完成了所有会话管理并将其注入拦截器。然后我可以在那里使用它的方法。
    【解决方案2】:

    另一个避免在每个 REST 方法中创建额外参数的解决方法是为所有使用这种拦截器的 REST 服务创建一个超类:

    public abstract class RestService {
        @Context
        private HttpServletRequest httpRequest;
    
        // Add here any other @Context fields & associated getters 
    
        public HttpServletRequest getHttpRequest() {
            return httpRequest;
        }
    }
    

    因此原始 REST 服务可以在不更改任何方法签名的情况下对其进行扩展:

    public class AddService extends RestService{
        @POST
        @Path("/add")
        @Produces(MediaType.APPLICATION_JSON)
        @Consumes(MediaType.APPLICATION_JSON)
        public Member add(NewMember member) throws MemberInvalidException {
            return memberManager.add(member);
        }
        ...
    }
    

    最后在拦截器中恢复httpRequest:

    public class RestLoggedInterceptorImpl implements Serializable {
        @AroundInvoke
        public Object aroundInvoke(InvocationContext ic) throws Exception {
    
            // Recover the context field(s) from superclass:
            HttpServletRequest req = ((RestService) ctx.getTarget()).getHttpRequest();
    
            logger.info(req.getRemoteAddr());  // <- this will work now
            ...
            return ic.proceed();
        }
        ...
    }
    

    【讨论】:

      【解决方案3】:

      我正在使用 Glassfish 3.1.2.2 Jersey

      对于 http 标头,这对我有用:

      @Inject
      @HeaderParam("Accept")
      private String acceptHeader;
      

      要获取 UriInfo,您可以这样做:

      @Inject
      @Context
      private UriInfo uriInfo;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-11-30
        • 2012-02-17
        • 1970-01-01
        • 1970-01-01
        • 2013-09-21
        • 1970-01-01
        相关资源
        最近更新 更多