Links: Table of Contents | Single HTML

Chapter 25. Custom Injection and Lifecycle Management

Since version 2.0, Jersey uses Glassfish-HK2 library for component life cycle management and dependency injection. Rather than spending a lot of effort in maintaining Jersey specific API (as it used to be before Jersey 2.0 version), Jersey defines several extension points where end-user application can directly manipulate Jersey HK2 bindings using the HK2 public API to customize life cycle management and dependency injection of application components.

Jersey user guide can by no means supply an exhaustive documentation of HK2 API in its entire scope. This chapter only points out the most common scenarios related to dependency injection in Jersey and suggests possible options to implement these scenarios. It is highly recommended to check out the Glassfish-HK2 website and read HK2 documentation in order to get better understanding of suggested approaches. HK2 documentation should also help in resolving use cases that are not discussed in this writing.

There are typically three main use cases, where your application may consider dealing with HK2 APIs exposed in Jersey:

  • Implementing a custom injection provider that allows an application to define additional types to be injectable into Jersey-managed JAX-RS components.
  • Defining a custom injection annotation (other than @Inject or @Context) to mark application injection points.
  • Specifying a custom component life cycle management for your application components.

Since Jersey 2.26, the injection has been abstracted, so that Glassfish-HK2 can be eventually replaced by the CDI or any other injection framework. In the next chapters, we document possibilities provided directly by Glassfish-HK2 and by Jersey abstraction components.

25.1. InjectionManager

Since Jersey 2.26, Jersey comes with the main abstraction interface to communicate with the DI container, the InjectionManager. What is ServiceLocator for Glassfish-HK2, or BeanManager for CDI, that's InjectionManager for Jersey.

InjectionManager can be injected into the user provided classes instantiated by Jersey. It can also be obtained programmatically by InjectionManagerClientProvider and InjectionManagerProvider from Jakarta REST components, such as FeatureContext, or MessageBodyReader<T> and MessageBodyReader<T>.

Customers used to the ServiceLocator can still use it directly; the ServiceLocator can be obtained either directly by injection, or programmatically as InjectionManager.getInstance(ServiceLocator.class).

25.2. Implementing Custom Injection Provider

Relying on Servlet HTTP session concept is not very RESTful. It turns the originally state-less HTTP communication schema into a state-full manner. However, it could serve as a good example that will help me demonstrate implementation of the use cases described above. The following examples should work on top of Jersey Servlet integration module. The approach that will be demonstrated could be further generalized. Below we will show how to make actual Servlet HttpSession injectable into JAX-RS components and how to make this injection work with a custom inject annotation type. Finally, we will demonstrate how you can write HttpSession-scoped JAX-RS resources.

Jersey implementation allows you to directly inject HttpServletRequest instance into your JAX-RS components. It is quite straight forward to get the appropriate HttpSession instance out of the injected request instance. Let say, you want to get HttpSession instance directly injected into your JAX-RS types like in the code snippet below.

@Path("di-resource")
public class MyDiResource {

    @Inject HttpSession httpSession;

    ...

}

25.2.1. Using HK2 classes

To make the above injection work, you will need to define an additional HK2 binding in your application ResourceConfig. Let's start with a custom HK2 Factory implementation that knows how to extract HttpSession out of given HttpServletRequest.

import org.glassfish.hk2.api.Factory;
    ...

    public class HttpSessionFactory implements Factory<HttpSession> {

    private final HttpServletRequest request;

    @Inject
    public HttpSessionFactory(HttpServletRequest request) {
        this.request = request;
    }

    @Override
    public HttpSession provide() {
       return request.getSession();
    }

    @Override
    public void dispose(HttpSession t) {
    }
}

Please note that the factory implementation itself relies on having the actual HttpServletRequest instance injected. In your implementation, you can of course depend on other types (and inject them conveniently) as long as these other types are bound to the actual HK2 service locator by Jersey or by your application. The key notion to remember here is that your HK2 Factory implementation is responsible for implementing the provide() method that is used by HK2 runtime to retrieve the injected instance. Those of you who worked with Guice binding API in the past will most likely find this concept very familiar.

Once implemented, the factory can be used in a custom HK2 Binder to define the new injection binding for HttpSession. Finally, the implemented binder can be registered in your ResourceConfig:

import org.glassfish.hk2.utilities.binding.AbstractBinder;
...

public class MyApplication extends ResourceConfig {

    public MyApplication() {

        ...

        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bindFactory(HttpSessionFactory.class).to(HttpSession.class)
                   .proxy(true).proxyForSameScope(false).in(RequestScoped.class);
            }
        });
    }
}

Note that if we did not define any explicit injection scope for the new injection binding, By default, HK2 factories are bound in a HK2 PerLookup scope, which is in most cases a good choice, and it is suitable also in our example.

To summarize the approach described above, here is a list of steps to follow when implementing custom injection provider in your Jersey application :

  • Implement your own HK2 Factory to provide the injectable instances.
  • Use the HK2 Factory to define an injection binding for the injected instance via custom HK2 Binder.
  • Register the custom HK2 Binder in your application ResourceConfig.

While the Factory-based approach is quite straight-forward and should help you to quickly prototype or even implement final solutions, you should bear in mind, that your implementation does not need to be based on factories. You can for instance bind your own types directly, while still taking advantage of HK2 provided dependency injection. Also, in your implementation you may want to pay more attention to defining or managing injection binding scopes for the sake of performance or correctness of your custom injection extension.

Important

While the individual injection binding implementations vary and depend on your use case, to enable your custom injection extension in Jersey, you must register your custom HK2 Binder implementation in your application ResourceConfig!

25.2.2. Injection Provider Using Jersey API

To make the HttpSession injection work without using HK2 API, we will need to create a custom supplier that knows how to extract HttpSession out of given HttpServletRequest.

import java.util.function.Supplier
    ...

public class HttpSessionSupplier implements Supplier<HttpSession> {

    private final HttpServletRequest request;

    @Inject
    public HttpSessionSupplier(HttpServletRequest request) {
        this.request = request;
    }

    @Override
    public HttpSession get() {
        return request.getSession();
    }

}

Once implemented, the supplier can be used in a custom Jersey AbstractBinder to define the new injection binding for HttpSession. Finally, the implemented binder can be registered in your ResourceConfig:

import org.glassfish.jersey.internal.inject.AbstractBinder;
    ...
public class MyApplication extends ResourceConfig {

    public MyApplication() {

        ...

        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bindFactory(HttpSessionSupplier.class).to(HttpSession.class)
                    .proxy(true).proxyForSameScope(false).in(RequestScoped.class);
            }
        });
    }
}

The default scope for Jersey binder is similarly as for the HK2, the PerLookup.

To summarize the approach described above, here is a list of steps to follow when implementing custom injection provider in your Jersey application :

  • Implement your own Supplier to provide the injectable instances.
  • Use the Supplier to define an injection binding for the injected instance via custom AbstractBinder.
  • Register the custom AbstractBinder in your application ResourceConfig.

Important

Similarly to the HK2, to enable your custom injection extension in Jersey, you must register your custom AbstractBinder implementation in your application ResourceConfig!

25.3. Defining Custom Injection Annotation

Java annotations are a convenient way for attaching metadata to various elements of Java code. Sometimes you may even decide to combine the metadata with additional functionality, such as ability to automatically inject the instances based on the annotation-provided metadata. The described scenario is one of the use cases where having means of defining a custom injection annotation in your Jersey application may prove to be useful. Obviously, this use case applies also to re-used existing, 3rd-party annotation types.

In the following example, we will describe how a custom injection annotation can be supported. Let's start with defining a new custom SessionInject injection annotation that we will specifically use to inject instances of HttpSession (similarly to the previous example):

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SessionInject { }

The above @SessionInject annotation should be then used as follows:

@Path("di-resource")
public class MyDiResource {

    @SessionInject HttpSession httpSession;

    ...

}

Again, the semantics remains the same as in the example described in the previous section. You want to have the actual HTTP Servlet session instance injected into your MyDiResource instance. This time however, you expect that the httpSession field to be injected must be annotated with a custom @SessionInject annotation. Obviously, in this simplistic case the use of a custom injection annotation is an overkill, however, the simplicity of the use case will help us to avoid use case specific distractions and allow us better focus on the important aspects of the job of defining a custom injection annotation.

25.3.1. Custom Injection Annotation using HK2

If you remember from the previous section, to make the injection in the code snippet above work, you first need to implement the injection provider (HK2 Factory) as well as define the injection binding for the HttpSession type. That part we have already done in the previous section. We will now focus on what needs to be done to inform the HK2 runtime about our @SessionInject annotation type that we want to support as a new injection point marker annotation. To do that, we need to implement our own HK2 InjectionResolver for the annotation as demonstrated in the following listing:

import jakarta.inject.Inject;
import jakarta.inject.Named;

import jakarta.servlet.http.HttpSession;

import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;

...

public class SessionInjectResolver implements InjectionResolver<SessionInject> {

    @Inject
    @Named(InjectionResolver.SYSTEM_RESOLVER_NAME)
    InjectionResolver<Inject> systemInjectionResolver;

    @Override
    public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
        if (HttpSession.class == injectee.getRequiredType()) {
            return systemInjectionResolver.resolve(injectee, handle);
        }

        return null;
    }

    @Override
    public boolean isConstructorParameterIndicator() {
        return false;
    }

    @Override
    public boolean isMethodParameterIndicator() {
        return false;
    }
}

The SessionInjectResolver above just delegates to the default HK2 system injection resolver to do the actual work.

You again need to register your injection resolver with your Jersey application, and you can do it the same was as in the previous case. Following listing includes HK2 binder that registers both, the injection provider from the previous step as well as the new HK2 inject resolver with Jersey application ResourceConfig. Note that in this case we're explicitly binding the SessionInjectResolver to a @Singleton scope to avoid the unnecessary proliferation of SessionInjectResolver instances in the application:

import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;

import jakarta.inject.Singleton;

...

public class MyApplication extends ResourceConfig {

    public MyApplication() {

        ...

        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bindFactory(HttpSessionFactory.class).to(HttpSession.class);

                bind(SessionInjectResolver.class)
                    .to(new TypeLiteral<InjectionResolver<SessionInject>>(){})
                    .in(Singleton.class);
            }
        });
    }
}

25.3.2. Custom Injection Annotation using Jersey InjectionResolver

Jersey also comes with its InjectionResolver used to translate into the HK2 InjectionResolver during runtime. The abstraction is important for allowing to support the custom injection annotation in various DI containers. For instance, the abstraction is used when supporting injection using @Context in the CDI container (jersey-cdi2-se module).

The SessionInjectResolver then looks as follows:

import jakarta.inject.Inject;

import jakarta.servlet.http.HttpSession;

import org.glassfish.jersey.internal.inject.InjectionResolver;

...

public class SessionInjectResolver implements InjectionResolver<SessionInject> {

    private final InjectionManger injectionManager;

    public SessionInjectResolver(InjectionManager) {
        this.injectionManager = injectionManager;
    }

    @Override
    public Object resolve(Injectee injectee) {
        if (HttpSession.class == injectee.getRequiredType()) {
            return injectionManager.getInstance(HttpServletRequest.class).getSession();
        }
        return null;
    }

    @Override
    public boolean isConstructorParameterIndicator() {
        return false;
    }

    @Override
    public boolean isMethodParameterIndicator() {
        return false;
    }

    @Override
    public Class<SessionInject> getAnnotation() {
        return SessionInject.class;
    }
}

The SessionInjectResolver uses InjectionManager described in Section 25.1, “InjectionManager”.

Unlike with Glassfish-HK2, Jersey InjectionResolver can only be bound as instance in the AbstractBinder. That is why the InjectionManager is used in the InjectionResolver to resolve the HttpSession instance.

The InjectionResolver can be registered in the with Jersey application ResourceConfig as follows:

import jakarta.ws.rs.core.Feature;

import org.glassfish.jersey.InjectionManagerProvider;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.internal.inject.InjectionManager;


import jakarta.inject.Singleton;

...

public class MyApplication extends ResourceConfig {

public MyApplication() {

    ...

    register(new Feature() {
        @Override
        public boolean configure(FeatureContext context) {
            final InjectionManager injectionManager = InjectionManagerProvider.getInjectionManager(context);
            context.register(new AbstractBinder() {
                @Override
                protected void configure() {
                    bind(new SessionInjectResolver(injectionManager)).to(HttpSession.class).in(Singleton.class);
                }
            });
            return true;
        }
    });
}

25.4. Custom Life Cycle Management

The last use case discussed in this chapter will cover managing custom-scoped components within a Jersey application. If not configured otherwise, then all JAX-RS resources are by default managed on a per-request basis. A new instance of given resource class will be created for each incoming request that should be handled by that resource class. Let say you want to have your resource class managed in a per-session manner. It means a new instance of your resource class should be created only when a new Servlet HttpSession is established. (As with previous examples in the chapter, this example assumes the deployment of your application to a Servlet container.)

Following is an example of such a resource class that builds on the support for HttpSession injection from the earlier examples described in this chapter. The PerSessionResource class allows you to count the number of requests made within a single client session and provides you a handy sub-resource method to obtain the number via a HTTP GET method call:

@Path("session")
public class PerSessionResource {

    @SessionInject HttpSession httpSession;

    AtomicInteger counter = new AtomicInteger();

    @GET
    @Path("id")
    public String getSession() {
        counter.incrementAndGet();
        return httpSession.getId();
    }

    @GET
    @Path("count")
    public int getSessionRequestCount() {
        return counter.incrementAndGet();
    }
}

Should the above resource be per-request scoped (default option), you would never be able to obtain any other number but 1 from it's getReqs sub-resource method, because then for each request a new instance of our PerSessionResource class would get created with a fresh instance counter field set to 0. The value of this field would get incremented to 1 in the the getSessionRequestCount method before this value is returned. In order to achieve what we want, we have to find a way how to bind the instances of our PerSessionResource class to HttpSession instances and then reuse those bound instances whenever new request bound to the same HTTP client session arrives. Let's see how to achieve this.

To get better control over your Jersey component instantiation and life cycle, you need to implement a custom Jersey ComponentProvider SPI, that would manage your custom components. Although it might seem quite complex to implement such a thing, the component provider concept in Jersey is in fact very simple. It allows you to define your own HK2 injection bindings for the types that you are interested in, while informing the Jersey runtime at the same time that it should back out and leave the component management to your provider in such a case. By default, if there is no custom component provider found for any given component type, Jersey runtime assumes the role of the default component provider and automatically defines the default HK2 binding for the component type.

Following example shows a simple ComponentProvider implementation, for our use case. Some comments on the code follow.

import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
...
import org.glassfish.hk2.api.DynamicConfiguration;
import org.glassfish.hk2.api.DynamicConfigurationService;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.binding.BindingBuilderFactory;
import org.glassfish.jersey.server.spi.ComponentProvider;

@jakarta.ws.rs.ext.Provider
public class PerSessionComponentProvider implements ComponentProvider {

    private ServiceLocator locator;

    static class PerSessionFactory implements Factory<PerSessionResource>{
        static ConcurrentHashMap<String, PerSessionResource> perSessionMap
                = new ConcurrentHashMap<String, PerSessionResource>();


        private final Provider<HttpServletRequest> requestProvider;
        private final ServiceLocator locator;

        @Inject
        public PerSessionFactory(
                Provider<HttpServletRequest> request,
                ServiceLocator locator) {

            this.requestProvider = request;
            this.locator = locator;
        }

        @Override
        @PerLookup
        public PerSessionResource provide() {
            final HttpSession session = requestProvider.get().getSession();

            if (session.isNew()) {
                PerSessionResource newInstance = createNewPerSessionResource();
                perSessionMap.put(session.getId(), newInstance);

                return newInstance;
            } else {
                return perSessionMap.get(session.getId());
            }
        }

        @Override
        public void dispose(PerSessionResource r) {
        }

        private PerSessionResource createNewPerSessionResource() {
            final PerSessionResource perSessionResource = new PerSessionResource();
            locator.inject(perSessionResource);
            return perSessionResource;
        }
    }

    @Override
    public void initialize(ServiceLocator locator) {
        this.locator = locator;
    }

    @Override
    public boolean bind(Class<?> component, Set<Class<?>> providerContracts) {
        if (component == PerSessionResource.class) {

            final DynamicConfigurationService dynamicConfigService =
                locator.getService(DynamicConfigurationService.class);
            final DynamicConfiguration dynamicConfiguration =
                dynamicConfigService.createDynamicConfiguration();

            BindingBuilderFactory
                .addBinding(BindingBuilderFactory.newFactoryBinder(PerSessionFactory.class)
                .to(PerSessionResource.class), dynamicConfiguration);

            dynamicConfiguration.commit();

            return true;
        }
        return false;
    }

    @Override
    public void done() {
    }
}

The first and very important aspect of writing your own ComponentProvider in Jersey is to store the actual HK2 ServiceLocator instance that will be passed to you as the only argument of the provider initialize method. Your component provider instance will not get injected at all so this is more or less your only chance to get access to the HK2 runtime of your application. Please bear in mind, that at the time when your component provider methods get invoked, the ServiceLocator is not fully configured yet. This limitation applies to all component provider methods, as the main goal of any component provider is to take part in configuring the application's ServiceLocator.

Now let's examine the bind method, which is where your provider tells the HK2 how to bind your component. Jersey will invoke this method multiple times, once for each type that is registered with the actual application. Every time the bind method is invoked, your component provider needs to decide if it is taking control over the component or not. In our case we know exactly which Java type we are interested in (PerSessionResource class), so the logic in our bind method is quite straightforward. If we see our PerSessionResource class it is our turn to provide our custom binding for the class, otherwise we just return false to make Jersey poll other providers and, if no provider kicks in, eventually provide the default HK2 binding for the component. Please, refer to the Glassfish-HK2 documentation for the details of the concrete HK2 APIs used in the bind method implementation above. The main idea behind the code is that we register a new HK2 Factory (PerSessionFactory), to provide the PerSessionResource instances to HK2.

The implementation of the PerSessionFactory is also included above. Please note that as opposed to a component provider implementation that should never itself rely on an injection support, the factory bound by our component provider would get injected just fine, since it is only instantiated later, once the Jersey runtime for the application is fully initialized including the fully configured HK2 runtime. Whenever a new session is seen, the factory instantiates and injects a new PerSessionResource instance. The instance is then stored in the perSessionMap for later use (for future calls).

In a real life scenario, you would want to pay more attention to possible synchronization issues. Also, we do not consider a mechanism that would clean-up any obsolete resources for closed, expired or otherwise invalidated HTTP client sessions. We have omitted those considerations here for the sake of brevity of our example.