| <?xml version="1.0" encoding="UTF-8"?> |
| <!-- |
| |
| Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. |
| |
| This program and the accompanying materials are made available under the |
| terms of the Eclipse Public License v. 2.0, which is available at |
| http://www.eclipse.org/legal/epl-2.0. |
| |
| This Source Code may also be made available under the following Secondary |
| Licenses when the conditions for such availability set forth in the |
| Eclipse Public License v. 2.0 are satisfied: GNU General Public License, |
| version 2 with the GNU Classpath Exception, which is available at |
| https://www.gnu.org/software/classpath/license.html. |
| |
| SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 |
| |
| --> |
| |
| <!DOCTYPE chapter [<!ENTITY % ents SYSTEM "jersey.ent" > %ents;]> |
| <chapter xmlns="http://docbook.org/ns/docbook" |
| version="5.0" |
| xml:lang="en" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xmlns:xi="http://www.w3.org/2001/XInclude" |
| xmlns:xlink="http://www.w3.org/1999/xlink" |
| xsi:schemaLocation="http://docbook.org/ns/docbook http://docbook.org/xml/5.0/xsd/docbook.xsd |
| http://www.w3.org/1999/xlink http://www.w3.org/1999/xlink.xsd" |
| xml:id="ioc"> |
| <title>Custom Injection and Lifecycle Management</title> |
| |
| <para> |
| Since version 2.0, Jersey uses &hk2.link; 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. |
| </para> |
| |
| <para> |
| 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 &hk2.link; 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. |
| </para> |
| |
| <para> |
| There are typically three main use cases, where your application may consider dealing with |
| HK2 APIs exposed in Jersey: |
| |
| <itemizedlist> |
| <listitem><simpara>Implementing a custom injection provider that allows an application to define |
| additional types to be injectable into Jersey-managed JAX-RS components.</simpara></listitem> |
| <listitem><simpara>Defining a custom injection annotation (other than &jee9.jakarta.inject.Inject; |
| or &jaxrs.core.Context;) to mark application injection points.</simpara></listitem> |
| <listitem><simpara>Specifying a custom component life cycle management for your application |
| components.</simpara></listitem> |
| </itemizedlist> |
| </para> |
| |
| <para> |
| 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 &jee9.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 &lit.jee9.servlet.HttpSession;-scoped JAX-RS resources. |
| </para> |
| |
| <section> |
| <title>Implementing Custom Injection Provider</title> |
| |
| <para> |
| Jersey implementation allows you to directly inject &jee9.servlet.HttpServletRequest; instance into |
| your JAX-RS components. |
| It is quite straight forward to get the appropriate &lit.jee9.servlet.HttpSession; instance out of the |
| injected request instance. |
| Let say, you want to get &lit.jee9.servlet.HttpSession; instance directly injected into your JAX-RS |
| types like in the code snippet below. |
| |
| <programlisting language="java">@Path("di-resource") |
| public class MyDiResource { |
| |
| @Inject HttpSession httpSession; |
| |
| ... |
| |
| }</programlisting> |
| |
| To make the above injection work, you will need to define an additional HK2 binding in your |
| application &jersey.server.ResourceConfig;. |
| Let's start with a custom HK2 &hk2.Factory; implementation that knows how to extract |
| &lit.jee9.servlet.HttpSession; out of given &lit.jee9.servlet.HttpServletRequest;. |
| |
| <programlisting language="java">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) { |
| } |
| }</programlisting> |
| |
| Please note that the factory implementation itself relies on having the actual |
| &lit.jee9.servlet.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 &lit.hk2.Factory; implementation |
| is responsible for implementing the <literal>provide()</literal> 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. |
| </para> |
| |
| <para> |
| Once implemented, the factory can be used in a custom HK2 &lit.hk2.Binder; to define the |
| new injection binding for &lit.jee9.servlet.HttpSession;. Finally, the implemented binder |
| can be registered in your &jersey.server.ResourceConfig;: |
| |
| <programlisting language="java">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); |
| } |
| }); |
| } |
| }</programlisting> |
| |
| Note that we did not define any explicit injection scope for the new injection binding. |
| By default, HK2 factories are bound in a HK2 &hk2.PerLookup; scope, which is in most |
| cases a good choice and it is suitable also in our example. |
| </para> |
| |
| <para> |
| To summarize the approach described above, here is a list of steps to follow |
| when implementing custom injection provider in your Jersey application : |
| |
| <itemizedlist> |
| <listitem><simpara>Implement your own HK2 &lit.hk2.Factory; to provide the |
| injectable instances.</simpara></listitem> |
| <listitem><simpara>Use the HK2 &lit.hk2.Factory; to define an injection |
| binding for the injected instance via custom HK2 &lit.hk2.Binder;.</simpara></listitem> |
| <listitem><simpara>Register the custom HK2 &lit.hk2.Binder; in your application |
| &lit.jersey.server.ResourceConfig;.</simpara></listitem> |
| </itemizedlist> |
| </para> |
| |
| <para> |
| While the &lit.hk2.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> |
| <para> |
| 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 &hk2.Binder; |
| implementation in your application &jersey.server.ResourceConfig;! |
| </para> |
| </important> |
| </para> |
| </section> |
| |
| <section> |
| <title>Defining Custom Injection Annotation</title> |
| |
| <para> |
| 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. |
| </para> |
| |
| <para> |
| In the following example, we will describe how a custom injection annotation can be supported. |
| Let's start with defining a new custom <literal>SessionInject</literal> injection annotation |
| that we will specifically use to inject instances of &jee9.servlet.HttpSession; |
| (similarly to the previous example): |
| |
| <programlisting language="java">@Retention(RetentionPolicy.RUNTIME) |
| @Target(ElementType.FIELD) |
| public @interface SessionInject { }</programlisting> |
| |
| The above <literal>@SessionInject</literal> annotation should be then used as follows: |
| |
| <programlisting language="java">@Path("di-resource") |
| public class MyDiResource { |
| |
| @SessionInject HttpSession httpSession; |
| |
| ... |
| |
| }</programlisting> |
| |
| 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 |
| <literal>MyDiResource</literal> instance. This time however, you expect that the |
| <literal>httpSession</literal> field to be injected must be annotated with |
| a custom <literal>@SessionInject</literal> 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. |
| </para> |
| |
| <para> |
| 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 &hk2.Factory;) as well as define the |
| injection binding for the &lit.jee9.servlet.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 <literal>@SessionInject</literal> |
| annotation type that we want to support as a new injection point marker annotation. To do that, |
| we need to implement our own HK2 &hk2.InjectionResolver; for the annotation as demonstrated |
| in the following listing: |
| |
| <programlisting language="java">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; |
| } |
| }</programlisting> |
| |
| The <literal>SessionInjectResolver</literal> above just delegates to the default |
| HK2 system injection resolver to do the actual work. |
| </para> |
| |
| <para> |
| 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 &lit.jersey.server.ResourceConfig;. |
| Note that in this case we're explicitly binding the <literal>SessionInjectResolver</literal> |
| to a &jee9.inject.Singleton; scope to avoid the unnecessary proliferation of |
| <literal>SessionInjectResolver</literal> instances in the application: |
| |
| <programlisting language="java">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); |
| } |
| }); |
| } |
| }</programlisting> |
| </para> |
| </section> |
| |
| <section> |
| <title>Custom Life Cycle Management</title> |
| |
| <para> |
| 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 &jee9.servlet.HttpSession; is established. |
| (As with previous examples in the chapter, this example assumes the deployment of your application |
| to a Servlet container.) |
| </para> |
| |
| <para> |
| Following is an example of such a resource class that builds on the support for |
| &lit.jee9.servlet.HttpSession; injection from the earlier examples described in this chapter. |
| The <literal>PerSessionResource</literal> 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 &lit.http.GET; method call: |
| |
| <programlisting language="java">@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(); |
| } |
| }</programlisting> |
| |
| 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 <literal>PerSessionResource</literal> class would get created with a fresh |
| instance <literal>counter</literal> field set to 0. |
| The value of this field would get incremented to 1 in the the <literal>getSessionRequestCount</literal> |
| 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 <literal>PerSessionResource</literal> class to &lit.jee9.servlet.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. |
| </para> |
| |
| <para> |
| To get better control over your Jersey component instantiation and life cycle, |
| you need to implement a custom Jersey &jersey.server.spi.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. |
| </para> |
| |
| <para> |
| Following example shows a simple &lit.jersey.server.spi.ComponentProvider; implementation, |
| for our use case. Some comments on the code follow. |
| |
| <programlisting language="java">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() { |
| } |
| }</programlisting> |
| </para> |
| |
| <para> |
| The first and very important aspect of writing your own &lit.jersey.server.spi.ComponentProvider; |
| in Jersey is to store the actual HK2 &hk2.ServiceLocator; instance that will be passed to you as |
| the only argument of the provider <literal>initialize</literal> 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 &lit.hk2.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 &lit.hk2.ServiceLocator;. |
| </para> |
| |
| <para> |
| Now let's examine the <literal>bind</literal> 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 <literal>bind</literal> 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 (<literal>PerSessionResource</literal> class), |
| so the logic in our <literal>bind</literal> method is quite straightforward. If we see our |
| <literal>PerSessionResource</literal> 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 &hk2.link; documentation for the details of the concrete HK2 APIs used in |
| the <literal>bind</literal> method implementation above. The main idea behind the code is that |
| we register a new HK2 &hk2.Factory; (<literal>PerSessionFactory</literal>), to provide |
| the <literal>PerSessionResource</literal> instances to HK2. |
| </para> |
| |
| <para> |
| The implementation of the <literal>PerSessionFactory</literal> 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). |
| </para> |
| |
| <para> |
| 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. |
| </para> |
| </section> |
| </chapter> |