What scope do JAX-RS 2 filters have?

2019-04-14 09:16发布

问题:

I am using RestEasy 3.0.2 which is one of the first JAX-RS 2 implementations and run my application within a Tomcat 7. I also make use of injection in my application via WELD which is integrated with RestEasy via its CDI adaptor. Everything works fine so far.

Now, I wrote an implementation of a ContainerRequestFilter to perform authentication of incoming requests before they hit a resource. The JAX-RS standard says that injection is possible for every resource and every other JAX-RS component that is annotated with a @Provider annotation.

Here is a simplified version of my filter implementation:

@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

    @Inject
    AuthenticationProvider authenticationProvider;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        authenticationProvider.authenticate(requestContext);
    }
}

Note: AuthenticationProvider is @RequestScoped.

In general, this solution works. The components are being injected and requests are being processed as expected.

But I am still in doubt in which scope the filter is living. If it was application-scoped then this would obviously lead to "funny" concurrency issues that cannot be found with deterministic tests.

I have looked into various documentation, guides and examples but I have found none that uses injection with filters or says something about the filter scope.

回答1:

For RestEasy, the answer is given in the RestEasy documentation concerning CDI integration:

A CDI bean that does not explicitly define a scope is @Dependent scoped by default. This pseudo scope means that the bean adapts to the lifecycle of the bean it is injected into. Normal scopes (request, session, application) are more suitable for JAX-RS components as they designate component's lifecycle boundaries explicitly. Therefore, the resteasy-cdi module alters the default scoping in the following way:

If a JAX-RS root resource does not define a scope explicitly, it is bound to the Request scope.

If a JAX-RS Provider or javax.ws.rs.Application subclass does not define a scope explicitly, it is bound to the Application scope.

Therefor JAX-RS filters annotated with @Provider are @ApplicationScoped.

The documentation also says that a JAX-RS provider can be associated with any scope by putting the proper annotation to it. So in general, the scope of a JAX-RS filter can be customized.

It is important to notice that it is safe to inject @RequestScoped objects into an @ApplicationScoped filter. This is, because CDI does not inject a reference to the actual object but to a proxy. When a method is invoked on the proxy, a separate instance of the object will be used for each request behind the scenes.

Here the according WELD documentation:

4.9. Client proxies

Clients of an injected bean do not usually hold a direct reference to a bean instance, unless the bean is a dependent object (scope @Dependent).

Imagine that a bean bound to the application scope held a direct reference to a bean bound to the request scope. The application-scoped bean is shared between many different requests. However, each request should see a different instance of the request scoped bean—the current one!

...

Therefore, unless a bean has the default scope @Dependent, the container must indirect all injected references to the bean through a proxy object. This client proxy is responsible for ensuring that the bean instance that receives a method invocation is the instance that is associated with the current context. The client proxy also allows beans bound to contexts such as the session context to be serialized to disk without recursively serializing other injected beans.

I used the following code to validate this (assume that the entityManager is produced as @RequestScoped in the example):

@Provider
public class OtherTestFilter implements ContainerRequestFilter {

    @Inject
    EntityManager entityManager;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        Session session =  (Session) entityManager.getDelegate();
        System.out.println(session.hashCode());
    }
} 

This gives a different hash value of session for every request processed by the filter. So theory and practice match here.



回答2:

The way it's defined, it looks like it is DependentScoped. The only other possibility would be a silent RequestScoped, but that isn't very likely.



回答3:

What scope do JAX-RS 2 filters have?

Providers such as filters are singleton by default.


What the documentation says

From the Application documentation:

The default life-cycle for resource class instances is per-request. The default life-cycle for providers (registered directly or via a feature) is singleton.

From the JAX-RS specification:

By default a single instance of each provider class is instantiated for each JAX-RS application. First the constructor is called, then any requested dependencies are injected, then the appropriate provider methods may be called multiple times (simultaneously), and finally the object is made available for garbage collection.

And the JAX-RS specification also mentions the following regarding CDI integration:

Providers and Application subclasses MUST be singletons or use application scope.