リクエストのパラメーターのカスタム検証を行う ContainerRequestFilter
を実装しようとしています。メソッドのパラメーターからカスタムアノテーションを取得できるように、URIに一致するリソースメソッドを検索する必要があります。
この答え に基づいて ExtendedUriInfo
を注入し、それを使用してメソッドに一致させることができます:
public final class MyRequestFilter implements ContainerRequestFilter {
@Context private ExtendedUriInfo uriInfo;
@Override
public ContainerRequest filter(ContainerRequest containerRequest) {
System.out.println(uriInfo.getMatchedMethod());
return containerRequest;
}
}
しかし getMatchedMethod
は明らかに、メソッドが実際に呼び出されるまでずっとnull
を返します(その時点で、検証を行うには遅すぎます)。
リソースメソッドが呼び出される前に、特定のURIに一致するMethod
を取得するにはどうすればよいですか?
興味のある方のために、私は JERSEY-351 で説明されているように、自分の必須パラメーター検証をロールしようとしています。
ジャージーだけで問題を解決する方法を見つけました。少なくともJersey 1.xでは、リクエストのURIを、そのメソッドが呼び出される前に照合されるメソッドと照合する方法がないようです。ただし、 ResourceFilterFactory
を使用して、個々のリソースメソッドごとにResourceFilter
を作成することができました。これにより、これらのフィルターは宛先メソッドについて事前に知ることができます。
必要なクエリパラメータの検証を含む(GuavaとJSR 305を使用)私のソリューションは次のとおりです。
public final class ValidationFilterFactory implements ResourceFilterFactory {
@Override
public List<ResourceFilter> create(AbstractMethod abstractMethod) {
//keep track of required query param names
final ImmutableSet.Builder<String> requiredQueryParamsBuilder =
ImmutableSet.builder();
//get the list of params from the resource method
final ImmutableList<Parameter> params =
Invokable.from(abstractMethod.getMethod()).getParameters();
for (Parameter param : params) {
//if the param isn't marked as @Nullable,
if (!param.isAnnotationPresent(Nullable.class)) {
//try getting the @QueryParam value
@Nullable final QueryParam queryParam =
param.getAnnotation(QueryParam.class);
//if it's present, add its value to the set
if (queryParam != null) {
requiredQueryParamsBuilder.add(queryParam.value());
}
}
}
//return the new validation filter for this resource method
return Collections.<ResourceFilter>singletonList(
new ValidationFilter(requiredQueryParamsBuilder.build())
);
}
private static final class ValidationFilter implements ResourceFilter {
final ImmutableSet<String> requiredQueryParams;
private ValidationFilter(ImmutableSet<String> requiredQueryParams) {
this.requiredQueryParams = requiredQueryParams;
}
@Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
@Override
public ContainerRequest filter(ContainerRequest request) {
final Collection<String> missingRequiredParams =
Sets.difference(
requiredQueryParams,
request.getQueryParameters().keySet()
);
if (!missingRequiredParams.isEmpty()) {
final String message =
"Required query params missing: " +
Joiner.on(", ").join(missingRequiredParams);
final Response response = Response
.status(Status.BAD_REQUEST)
.entity(message)
.build();
throw new WebApplicationException(response);
}
return request;
}
};
}
@Override
public ContainerResponseFilter getResponseFilter() {
return null;
}
}
}
そして、ResourceFilterFactory
はweb.xml
のサーブレットの初期パラメータとしてジャージーに登録されています。
<init-param>
<param-name>com.Sun.jersey.spi.container.ResourceFilters</param-name>
<param-value>my.package.name.ValidationFilterFactory</param-value>
</init-param>
起動時に、ValidationFilterFactory.create
は、ジャージーによって検出された各リソースメソッドに対して呼び出されます。
クレジットは私を正しい軌道に乗せるためにこの投稿に行きます: Jersey ContainerResponseFilterでリソースアノテーションを取得するにはどうすればよいですか
実際には、カスタムリクエストフィルターにResourceInfo
を挿入する必要があります。 RESTEasyで試してみましたが、そこで機能します。利点は、Jersey実装ではなく、JSRインターフェースに対してコーディングすることです。
public class MyFilter implements ContainerRequestFilter
{
@Context
private ResourceInfo resourceInfo;
@Override
public void filter(ContainerRequestContext requestContext)
throws IOException
{
Method theMethod = resourceInfo.getResourceMethod();
return;
}
}
私はあなたがジャージーのみのソリューションを探していることを知っていますが、これは物事を機能させるはずのGuiceアプローチです:
public class Config extends GuiceServletContextListener {
@Override
protected Injector getInjector() {
return Guice.createInjector(
new JerseyServletModule() {
@Override
protected void configureServlets() {
bindInterceptor(Matchers.inSubpackage("org.example"), Matchers.any(), new ValidationInterceptor());
bind(Service.class);
Map<String, String> params = Maps.newHashMap();
params.put(PackagesResourceConfig.PROPERTY_PACKAGES, "org.example");
serve("/*").with(GuiceContainer.class, params);
}
});
}
public static class ValidationInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation method) throws Throwable {
System.out.println("Validating: " + method.getMethod());
return method.proceed();
}
}
}
@Path("/")
public class Service {
@GET
@Path("service")
@Produces({MediaType.TEXT_PLAIN})
public String service(@QueryParam("name") String name) {
return "Service " + name;
}
}
編集:パフォーマンス比較:
public class AopPerformanceTest {
@Test
public void testAopPerformance() {
Service service = Guice.createInjector(
new AbstractModule() {
@Override
protected void configure() { bindInterceptor(Matchers.inSubpackage("org.example"), Matchers.any(), new ValidationInterceptor()); }
}).getInstance(Service.class);
System.out.println("Total time with AOP: " + timeService(service) + "ns");
}
@Test
public void testNonAopPerformance() {
System.out.println("Total time without AOP: " + timeService(new Service()) + "ns");
}
public long timeService(Service service) {
long sum = 0L;
long iterations = 1000000L;
for (int i = 0; i < iterations; i++) {
long start = System.nanoTime();
service.service(null);
sum += (System.nanoTime() - start);
}
return sum / iterations;
}
}
Resteasy-jaxrs-3.0.5では、ResourceMethodInvoker
内のContainerRequestContext.getProperty()
から、一致するリソースメソッドを表すContainerRequestFilter
を取得できます。
import org.jboss.resteasy.core.ResourceMethodInvoker;
public class MyRequestFilter implements ContainerRequestFilter
{
public void filter(ContainerRequestContext request) throws IOException
{
String propName = "org.jboss.resteasy.core.ResourceMethodInvoker";
ResourceMethodInvoker invoker = (ResourceMethodInvoker)request.getProperty();
invoker.getMethod().getParameterTypes()....
}
}