blob: 53a5a645f5123199581741cfe3ed08ca050a6c85 [file] [log] [blame]
<?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 &jee6.javax.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 &jee6.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.jee6.servlet.HttpSession;-scoped JAX-RS resources.
</para>
<section>
<title>Implementing Custom Injection Provider</title>
<para>
Jersey implementation allows you to directly inject &jee6.servlet.HttpServletRequest; instance into
your JAX-RS components.
It is quite straight forward to get the appropriate &lit.jee6.servlet.HttpSession; instance out of the
injected request instance.
Let say, you want to get &lit.jee6.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.jee6.servlet.HttpSession; out of given &lit.jee6.servlet.HttpServletRequest;.
<programlisting language="java">import org.glassfish.hk2.api.Factory;
...
public class HttpSessionFactory implements Factory&lt;HttpSession&gt; {
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.jee6.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.jee6.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 &jee6.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.jee6.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 javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpSession;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
...
public class SessionInjectResolver implements InjectionResolver&lt;SessionInject&gt; {
@Inject
@Named(InjectionResolver.SYSTEM_RESOLVER_NAME)
InjectionResolver&lt;Inject&gt; systemInjectionResolver;
@Override
public Object resolve(Injectee injectee, ServiceHandle&lt;?&gt; 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 &jee6.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 javax.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&lt;InjectionResolver&lt;SessionInject&gt;&gt;(){})
.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 &jee6.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.jee6.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.jee6.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 javax.inject.Inject;
import javax.inject.Provider;
import javax.servlet.http.HttpServletRequest;
import javax.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;
@javax.ws.rs.ext.Provider
public class PerSessionComponentProvider implements ComponentProvider {
private ServiceLocator locator;
static class PerSessionFactory implements Factory&lt;PerSessionResource&gt;{
static ConcurrentHashMap&lt;String, PerSessionResource&gt; perSessionMap
= new ConcurrentHashMap&lt;String, PerSessionResource&gt;();
private final Provider&lt;HttpServletRequest&gt; requestProvider;
private final ServiceLocator locator;
@Inject
public PerSessionFactory(
Provider&lt;HttpServletRequest&gt; 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&lt;?&gt; component, Set&lt;Class&lt;?&gt;&gt; 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>