简体   繁体   中英

How do I intercept selective methods and classes in JAX-RS in Java EE 7 compliant container?

I want to intercept any class or methods annotated with @Foo

Class level interception:

@Foo
@path("/foo")
public class Attack {...}

Method level interception:

@path("/bar")
public class defend {

@Foo
@GET
public String myMethod(){....}

I want to intercept any class or methods annotated with @Foo but not other methods or classes. I want to print out the entire path or URI before proceeding to the method execution. One the method call is finished, I want to print out "executed sucessfully"

That is something of this kind:

 system.out.println(path) // this is the path the request is made. something like /api/2/imp/foo
   method call happens
   method call finishes
   System.out.println("executed successfully")

My scenario is different but this is the fundamental problem I have. I do not want to be implementation specific. Java EE 7 specification has a way to do this using @Postconstruct, @AroundInvoke etc. But I am really having difficulty assembling this.

This post is definitely a great approach to this problem. but it is implementation specific (RESTeasy) and the AcceptByMethod it uses is deprecated.

Thanks

Skimming through the Java EE Tutorial for JAX-RS , it seems they fail to mention anything about the concept of Filters and Interceptors from the jsr339-jaxrs-2.0-final-spec . You should probably download a copy for complete information.

Filters and entity interceptors can be registered for execution at well-defined extension points in JAX-RS implementations. They are used to extend an implementation in order to provide capabilities such as logging, confidentiality, authentication, entity compression

Entity interceptors wrap around a method invocation at a specific extension point. Filters execute code at an extension point but without wrapping a method invocation.

Basically the last paragraph is saying that interceptors occur in the same execution stack as the method call, whereas filters don't. That doesn't mean we can't use filters for your logging case. The filter contexts passed to the filter interface method actually have a lot more information that you can use.

The ContainerRequestFilter and ContainerResponseFilter get passed ContainerRequestContext and ContainerResponseContext , respectively, of which we can obtain things like the UriInfo to get the path from.

public interface ContainerResponseFilter {
    void filter(ContainerRequestContext requestContext, 
           ContainerResponseContext responseContext)
}

public interface ContainerRequestFilter {
    void filter(ContainerRequestContext requestContext)
}

Here's a simple example of a logging filter. There a few different ways to bind the filter, but with this example I'll use dynamic binding where I instantiate the filter explicitly, Therefore I don't have a container managed state, and pass the class and method names to the filter

public class LoggingFilter implements ContainerRequestFilter,
                                      ContainerResponseFilter {

    private static final Logger logger
            = Logger.getLogger(LoggingFilter.class.getName());

    protected String className;
    protected String methodName;

    public NewLoggingFilter(String className, String methodName) {
        this.className = className;
        this.methodName = methodName;
    }

    @Override
    public void filter(ContainerRequestContext requestContext) 
                                                      throws IOException {
        logger.log(Level.INFO, "Request path: {0}",
                requestContext.getUriInfo().getAbsolutePath().toString());
        logger.log(Level.INFO, "Starting Method: {0}.{1}",
                new Object[]{className, methodName});
    }

    @Override
    public void filter(ContainerRequestContext requestContext,
                       ContainerResponseContext responseContext)
                                                       throws IOException {

        logger.log(Level.INFO, "Finished Method: {0}.{1}",
                                       new Object[]{className, methodName});
    }
}

Here's how I bind the methods to the filter. Every resource method goes through this binder. If it or it's class is annotation with our custom annotation, it will be binded to out LoggingFilter . We also pass the LogginFilter the class and method names of the resource method. We will use those names for our logging

@Provider
public class LoggingBinder implements DynamicFeature {

    @Override
    public void configure(ResourceInfo ri, FeatureContext fc) {
        Class<?> clazz = ri.getResourceClass();
        Method method = ri.getResourceMethod();
        if (method.isAnnotationPresent(Logged.class) 
                || clazz.isAnnotationPresent(Logged.class)) {
            fc.register(new LoggingFilter(clazz.getName(), method.getName()));
        }
    }  
}

It checks the method or class to see if it has the annotation @Logged (which is a custom annotation - you can just as easily call it @Foo )

@NameBinding
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface Logged {
}

Using this resource class

@Path("/log")
public class LogResource {
    @GET
    @Logged
    public Response getLoggingResourceMethod() {
        return Response.ok("Hello Logging Response").build();
    }
}

We get the following result in our log

Oct 25, 2014 4:36:05 PM jaxrs.stackoverflow.filter.NewLoggingFilter filter
INFO: Request path: http://localhost:8081/rest/log
Oct 25, 2014 4:36:05 PM jaxrs.stackoverflow.filter.NewLoggingFilter filter
INFO: Starting Method: jaxrs.stackoverflow.filter.LogResource.getLoggingResourceMethod
Oct 25, 2014 4:36:05 PM jaxrs.stackoverflow.filter.NewLoggingFilter filter
INFO: Finished Method: jaxrs.stackoverflow.filter.LogResource.getLoggingResourceMethod
Oct 25, 2014 4:36:05 PM jaxrs.stackoverflow.filter.NewLoggingFilter filter
INFO: Method successful.

Don't forget to download the spec for more details.

An Interceptor is really simple:

@Foo @Interceptor
public class FooInterceptor
{
    @AroundInvoke
    public Object handleFoo(InvocationContext joinPoint) throws Exception
    {
        Method m = joinPoint.getMethod();

        // you can access all annotations on your @Foo-annotated method,
        // not just the @Foo annotation.
        Annotation[] as = m.getDeclaredAnnotations();

        // do stuff before the method call
        ...

        try
        {
            // here you call the actual method
            return joinPoint.proceed();
        }
        finally
        {
            // do stuff after the method call
            ...
        }
    }
}

This is how the annotation would look:

@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface Foo
{
    @Nonbinding
    ... // you could add parameters for your annotation, e.g. @Foo(value)
}

And this is how you would use it:

@Stateless
public class MyService
{
    @Foo("bar")
    public String myWrappedMethod()
    {
        ...
    }
}

The code inside myWrappedMethod would be "wrapped" by the code in the FooInterceptor. Note that the interceptor is only invoked if the method call to myWrappedMethod() is managed by the container, ie you invoke it on a managed instance of MyService (eg via @Inject)

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