Reputation: 31252
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
Upvotes: 4
Views: 5568
Reputation: 7321
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, i.e. you invoke it on a managed instance of MyService (e.g. via @Inject)
Upvotes: 2
Reputation: 208964
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.
Upvotes: 6