从 Jersey 过滤器或拦截器内部访问资源方法参数。或者将 AOP 与资源方法一起使用

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

我正在尝试根据每个请求使用用户 ID 丰富 SLF4J MDC。问题是 ID 可以通过多种方式传递,有时作为路径参数,有时在正文中,有时由首先解密它的自定义 ValueFactoryProvider 注入。

如果我能以某种方式访问​​所有注入的(即已经反序列化)参数值,我就可以轻松处理所有这些情况。

例如

资源如:

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

我可以有一个过滤器,例如:

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

但是我没有看到通过 ContainerRequestFilter 拦截器或其他任何东西来实现 somehowGetTheValue 的方法...

你可以这样获取

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

基本上,想法是复制流并进行任何处理,然后将流设置回去。因为如果你不这样做,那么在你的控制器方法中你会得到 null,因为流已经被读取了。

泽西岛使用 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。在 MethodInterceptor 中,您可以从 MethodInvocation 中获取所有参数,并且可以从 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;
    }
}

要使用拦截器,您需要配置 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;
    }
}

你决定在getMethodInterceptors()方法中拦截哪个方法。如果该方法应该被拦截,那么 return 一个拦截器列表,否则 return null。处理此问题的一种常见方法是创建自定义注释并仅注释该方法。在上面的方法中,只需检查

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

要使其全部正常工作,您只需向 HK2 注册 InteceptionService。您可以在 AbstractBinder 中执行此操作,这是 Jersey 应用程序中用于配置 DI 的内容。

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

您可以在 this GitHub repo 中看到完整的示例。 HK2 站点中也有一个官方示例。只需查看 "AOP support" post 顶部的 link。