JEE6 REST服务@AroundInvoke拦截器正在注入一个空的HttpServletRequest对象

时间:2013-07-08 05:47:39

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

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

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

但是有些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服务是否包含参数。

3 个答案:

答案 0 :(得分:5)

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

答案 1 :(得分:4)

避免在每个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();
    }
    ...
}

答案 2 :(得分:0)

我使用的是Glassfish 3.1.2.2 Jersey

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

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

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

@Inject
@Context
private UriInfo uriInfo;