简体   繁体   中英

Access resource method arguments from inside a Jersey filter or interceptor. Or use AOP with resource method

I'm trying to enrich the SLF4J MDC on each request with the user's ID. The problem is that the ID can be passed in many ways, sometimes as a path parameter, sometimes in the body, and sometimes injected by a custom ValueFactoryProvider that first decrypts it.

If I could somehow access all the injected (ie already deserialized ) parameter values, I could handle all these cases easily.

Eg

For a resource such as:

@GET
//@Encrypted params are injected by a custom ValueFactoryProvider
public Something getSomething(@Encrypted("userId") String userId) {
    return ...;
}

@POST
public Something getSomething(@RequestBody RequestWithUserId requestWithUserId) {
    return ...;
}

I could have a filter such as:

public class MdcFilter implements ContainerRequestFilter, ContainerResponseFilter {

    @Context
    private ResourceInfo resourceInfo;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        Method theMethod = resourceInfo.getResourceMethod();
        for (Parameter parameter : theMethod.getParameters()) {
            //Deal with the @Encrypted case
            if (parameter.isAnnotationPresent(Encrypted.class) && parameter.getAnnotation(Encrypted.class).value().equals("userId")) {
                MDC.put("userId", somehowGetTheValue());
            }
            //Deal with the @RequestBody case
            if (parameter.isAnnotationPresent(RequestBody.class) && parameter.getType().equals(RequestWithUserId.class)) {
                MDC.put("userId", ((RequestWithUserId)somehowGetTheValue()).getUserId());
            }
            ... //other possibilities
        }
    }

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
        MDC.clear();
    }
}

But I don't see a way to implement somehowGetTheValue either from a ContainerRequestFilter an interceptor or anything else...

Jersey uses HK2 under the hood for dependency injection. And HK2 has AOP support . One option for your use case would be use this AOP support. All you need to do is implement a MethodInterceptor and an InterceptionService . In the MethodInterceptor , you can get all the arguments from the MethodInvocation and you can get parameter annotation from the Method

class MyMethodInteceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        Object[] args = invocation.getArguments();

        // do your logging or whatever with the args.

        // invoke method and get return value.
        Object returnValue = invocation.proceed();
        // if you want to do something with the return
        // value before returning it, you can.

        return returnValue;
    }
}

To use the interceptor, you configure the InterceptionService .

public class MyInterceptionService implements InterceptionService {

    private final static MethodInterceptor METHOD_INTERCEPTOR 
            = new MyMethodInterceptor();
    private final static List<MethodInterceptor> METHOD_LIST
            = Collections.singletonList(METHOD_INTERCEPTOR);

    @Override
    public Filter getDescriptorFilter() {
        return BuilderHelper.allFilter();
    }

    @Override
    public List<MethodInterceptor> getMethodInterceptors(Method method) {
        // you implement shouldIntercept
        if (shouldIntercept(method)) {
            return METHOD_LIST;
        }
        return null;
    }

    @Override
    public List<ConstructorInterceptor> getConstructorInterceptors(Constructor<?> constructor) {
        return null;
    }
}

You determine which method should be intercepted in the getMethodInterceptors() method. If the method should be intercepted, then return a list of interceptors, otherwise return null. A common way of handling this is to create a custom annotation and just annotate the method. The in the above method, just check

if (method.isAnnotationPresent(YourAnno.class)) {
    return METHOD_LIST;
}

To make it all work, you just need to register the InteceptionService with HK2. You can do that in an AbstractBinder , which is what is used in a Jersey app to configure your DI.

ResourceConfig config = new ResourceConfig();
config.register(new AbstractBinder() {
    @Override
    protected void configure() {
        bind(MyInterceptionService.class)
                .to(InterceptionService.class)
                .in(Singleton.class);
    }
});

You can see a complete example in this GitHub repo . There is also an official example in the HK2 site. Just see "AOP support" the link at the top of the post.

You can get it like this

StringWriter stringWriter = new StringWriter();
IOUtils.copy(new InputStreamReader(requestContext.getEntityStream()), stringWriter);
System.out.println(stringWriter.toString());// String representation of the payload
requestContext.setEntityInputStream(new ByteArrayInputStream(requestEntity));

Basically the idea is to copy the stream and do any processing and then set the stream back. Because if you don't do that, then in your controller method you would get null, becuase the stream was already read.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM