HHH-8607 - Start Topical Guide - Service Registries

This commit is contained in:
Steve Ebersole 2013-10-15 15:54:33 -05:00
parent b5f5288708
commit 67ee00a422
2 changed files with 361 additions and 22 deletions

View File

@ -3,21 +3,20 @@
:toc:
Services and Registries are new *as a formalized concept* starting in 4.0. But the functionality provided by
the different Services have actually been around in Hibernate much, much longer. What is new is the managing them,
their lifecycles and dependencies through a lightweight, dedicated container we call a ServiceRegistry.
This guide aims to describe the design and purpose of these Services and Registries. Where appropriate it will
look at details of their implementations. It will also delve into the ways third-party integrators and applications
can leverage and customize Services and Registries.
the different Services have actually been around in Hibernate much, much longer. What is new is managing them,
their lifecycles and dependencies through a lightweight, dedicated container we call a ServiceRegistry. The
goal of this guide is to describe the design and purpose of these Services and Registries, as well as to look at
details of their implementations where appropriate. It will also delve into the ways third-party integrators and
applications can leverage and customize Services and Registries.
== What is a Service?
Services provide various types of functionality, in a pluggable manner. Specifically they are implementations
of certain service contract interfaces. The interface is known as the service role; the implementation class is
known as the service implementation. The pluggability comes from the fact that the service implementation adheres
to contract defined by the interface of the service role and that consumers of the service program to the service
role, not the implementation.
Services provide various types of functionality, in a pluggable manner. Specifically they are interfaces defining
certain functionality and then implementations of those service contract interfaces. The interface is known as the
service role; the implementation class is known as the service implementation. The pluggability comes from the fact
that the service implementation adheres to contract defined by the interface of the service role and that consumers
of the service program to the service role, not the implementation.
NOTE: All Services are expected to implement the +org.hibernate.service.Service+ "marker" interface. Hibernate uses
this internally for some basic type safety; it defines no methods (at the moment).
@ -34,9 +33,10 @@ service contract, varying in how they actually manage the Connections:
Internally Hibernate always references +org.hibernate.engine.jdbc.connections.spi.ConnectionProvider+ rather than
specific implementations in consuming the service (we will get to producing the service later when we talk about
registries). Because of that fact, other ConnectionProvider service implementations could be plugged in. There is
nothing revolutionary here; programming to interfaces is generally accepted as good programming practice. What's
interesting is the ServiceRegistry and the pluggable swapping of the different implementors.
registries). Because of that fact, other ConnectionProvider service implementations could be plugged in.
There is nothing revolutionary here; programming to interfaces is generally accepted as good programming practice.
What's interesting is the ServiceRegistry and the pluggable swapping of the different implementors.
== What is a ServiceRegistry?
@ -50,7 +50,7 @@ produced (choose using one implementation over another). The ServiceRegistry fu
In a concise definition, the ServiceRegistry acts as a inversion-of-control (IoC) container.
NOTE: Despite some recent revisionist history, Spring did not invent IoC and dependency injection nor were they even
NOTE: Despite some recent revisionist history, Spring did not invent IoC nor dependency injection nor were they even
the first to bring it into Java. Projects like JBoss MicroContainer and Apache Avalon pre-date Spring
by many years and each did IoC and dependency injection. The concepts in ServiceRegistry are actually very similar
to Apache Avalon.
@ -173,6 +173,7 @@ powerful. For more information on this aspect, see:
* +BootstrapServiceRegistryBuilder#withStrategySelector+
* +BootstrapServiceRegistryBuilder#withStrategySelectors+
* +org.hibernate.boot.registry.selector.StrategyRegistrationProvider+ (via +ServiceLoader+ discovery)
* 'StrategySelector#registerStrategyImplementor` / 'StrategySelector#unRegisterStrategyImplementor`
The service role for this service is +org.hibernate.boot.registry.selector.spi.StrategySelector+.
@ -226,7 +227,8 @@ if one is found its reported +JtaPlatform+ is used (first wins).
==== RegionFactory
This is the second level cache service.
This is the second level cache service in terms of starting the underlying cache provider
==== SessionFactoryServiceRegistryFactory
@ -244,6 +246,7 @@ which need access to the SessionFactory. Currently that is just 3 Services.
NOTE: Integrators, as it stands in 4.x, operate on the SessionFactoryServiceRegistry...
==== EventListenerRegistry
+org.hibernate.event.service.spi.EventListenerRegistry+ is the big Service managed in the +SessionFactoryServiceRegistry+.
@ -265,28 +268,347 @@ collector portion, if you will.
== Service lifecycle
Managing the lifecycle of services is the big role of a ServiceRegistry as a container for those services. The overall
lifecycle of a Service is:
. <<service-initiation,initiation>>
. (optional) <<service-configuration,configuration>>
. (optional) <<service-starting,starting>>
. in use - until registry closed
. (optional) <<service-stopping,stopping>>
[[service-initiation]]
=== Initiation (creation)
A Service needs to be initiated/created. We'll explore the details a little more when we discuss
<<serviceregistry-building>>. But generally speaking, either
* a Service can be instantiated directly and handed to the ServiceRegistry
* A `ServiceInitiator` can be handed to the ServiceRegistry to initiate the Service on-demand.
[[service-configuration]]
=== Configuration
=== Starting/Stopping
A Service can optionally implement the `org.hibernate.service.spi.Configurable` interface to be handed the
`java.util.Map` of configuration settings handed to Hibernate during initial bootstrapping. `Configurable#configure`
is called after initiation but before usage
== Building ServiceRegistry
[[service-starting]]
=== Starting
A Service can optionally implement `org.hibernate.service.spi.Startable` to receive a callback just prior to
going into "in use". Reflexively speaking, it is generally good practice for a Service needing `Startable` to also
need `Stoppable` (<<service-stopping,stopping>>).
[[service-stopping]]
=== Stopping
A Service can optionally implement `org.hibernate.service.spi.Stoppable` to receive a callback as the Service
is taken out of "in use" as part ServiceRegistry shutdown.
=== Manageable (JMX)
A Service can optionally implement `org.hibernate.service.spi.Manageable` to be made available to JMX.
NOTE: This particular feature is still under design/development
== Service Dependencies
Services sometimes depend on other services. For example, the DataSourceConnectionProvider service implementation
usually needs access to the JndiService to perform JNDI lookups. This has 2 implications. First, it means that
DataSourceConnectionProvider needs access to JndiService. Secondly it means that the JndiService musty be fully
"in use" prior to its usage from DataSourceConnectionProvider.
There are 2 ways to obtain access to dependent Services:
. Have the Service implement `org.hibernate.service.spi.ServiceRegistryAwareService`, which will inject the
ServiceRegistry into your Service. You can then look up any Services you need access to. The returned Services
you lookup will be fully ready for use.
. Injecting specific Services using `@org.hibernate.service.spi.InjectService`.
.. The Service role to inject is generally inferred by the type of parameter of the method to which the annotation
is attached. If the parameter type is different from the Service role, use `InjectService#serviceRole` to name the
role explicitly.
.. By default the Service to inject is considered required (an exception will be thrown if it is not found). If the
service to be injected is optional, use `InjectService#required=false`.
== Management (JMX)
[[serviceregistry-building]]
== Building ServiceRegistry
Once built, a ServiceRegistry is generally considered immutable. The Services themselves might accept
re-configuration, but immutability here means adding/replacing services. So all the services hosted in a particular
ServiceRegistry must be known up-front. To this end, building a ServiceRegistry usually employees a
http://en.wikipedia.org/wiki/Builder_pattern[builder^].
=== Building BootstrapServiceRegistry
Building the `BootstrapServiceRegistry` is normally done via the 'org.hibernate.boot.registry.BootstrapServiceRegistryBuilder`
class which exposes methods for defining `ClassLoaders` to use, non-discoverable `Integrators` to incorporate, etc.
By default Hibernate will use the Thread-context ClassLoader (TCCL), if one, as well as the ClassLoader of its classes
as the ClassLoaders it will consult when asked to load classes or resources or to perform ServiceLoader resolutions.
You can tell Hibernate to consider any additional ClassLoaders via the overloaded
`BootstrapServiceRegistryBuilder#with(ClassLoader)` method:
[source,java]
----
BootstrapServiceRegistry bootstrapServiceRegistry = new BootstrapServiceRegistryBuilder()
.with( anAdditionalClassLoader )
.with( anotherAdditionalClassLoader )
.build();
----
NOTE: you can also tell Hibernate to use a completely different ClassLoaderService implementation using
`BootstrapServiceRegistryBuilder#with(ClassLoaderService)`.
Integrators are normally discovered via the JDK `ServiceLoader` mechanism. To tell Hibernate about an Integrator
that will not be discovered (for whatever reason) you would use the `BootstrapServiceRegistryBuilder#with(Integrator)`
method:
[source,java]
----
BootstrapServiceRegistry bootstrapServiceRegistry = new BootstrapServiceRegistryBuilder()
.with( new MyCustomIntegrator() )
.build();
----
`BootstrapServiceRegistryBuilder` also exposes methods to add extra strategy selections. Let's say we developed
a custom CORBA-based TransactionFactory named CORBATransactionFactory and that we'd like to make this available via
short-naming. One option would be to explicitly set up the short name during BootstrapServiceRegistry building:
[source,java]
----
BootstrapServiceRegistry bootstrapServiceRegistry = new BootstrapServiceRegistryBuilder()
.withStrategySelector( TransactionFactory.class, "corba", CORBATransactionFactory.class )
.build();
----
If we were going to distribute our CORBATransactionFactory, we might develop a
`org.hibernate.boot.registry.selector.StrategyRegistrationProvider`:
[source,java]
----
public class CORBATransactionFactoryStrategyRegistrationProvider implements StrategyRegistrationProvider {
public Iterable<StrategyRegistration> getStrategyRegistrations() {
return Collections.singletonList(
(StrategyRegistration) new SimpleStrategyRegistrationImpl<ConnectionProvider>(
ConnectionProvider.class,
CORBATransactionFactory.class,
"corba"
)
);
}
}
----
which we could register explicitly:
[source,java]
----
BootstrapServiceRegistry bootstrapServiceRegistry = new BootstrapServiceRegistryBuilder()
.withStrategySelectors( new CORBATransactionFactoryStrategyRegistrationProvider() )
.build();
----
or define for discovery by adding a `META-INF/services/org.hibernate.boot.registry.selector.StrategyRegistrationProvider`
file to our artifact naming `CORBATransactionFactoryStrategyRegistrationProvider`.
We might combine several of these at once:
[source,java]
----
BootstrapServiceRegistry bootstrapServiceRegistry = new BootstrapServiceRegistryBuilder()
.with( anAdditionalClassLoader )
.with( anotherAdditionalClassLoader )
.with( new MyCustomIntegrator() )
.withStrategySelector( ConnectionProvider.class, "custom", MyCustomConnectionProvider.class )
.withStrategySelectors( new CORBATransactionFactoryStrategyRegistrationProvider() )
.build();
----
=== Building StandardServiceRegistry
Building the `StandardServiceRegistry` is normally done via the
'org.hibernate.boot.registry.StandardServiceRegistryBuilder` which exposes methods for managing settings and
controlling the services hosted by the built StandardServiceRegistry.
Managing settings can be as simple as telling the builder about one or more settings directly:
[source,java]
----
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.applySetting( "hibernate.hbm2ddl.auto", true )
.applySettings( Collections.singletonMap( "hibernate.transaction.factory_class", "jdbc" ) )
.build();
----
Or we can tell it to load settings from various files:
[source,java]
----
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.configure() <1>
.configure( "com/acme/hibernate.cfg.xml" ) <2>
.loadProperties( "com/acme/hibernate.properties" ) <3>
.build();
----
<1> loads settings from an XML file (conforming to the Hibernate cfg.xml DTD) via a ClassLoader resource lookup for hibernate.cfg.xml
<2> loads settings from an XML file (conforming to the Hibernate cfg.xml DTD) via a ClassLoader resource lookup for com/acme/hibernate.cfg.xml
<3> loads settings from Properties via a ClassLoader resource lookup for com/acme/hibernate.properties
The other methods of interest on `StandardServiceRegistryBuilder` relate to customizing the Services to use. We can
either pass in a Service instance to use or the ServiceInitiator to use as already discussed. There are 2 distinct
ways to customize the Services to use:
==== Building StandardServiceRegistry - Overriding
== Customization
Here the intent is to override or replace a service impl. Many of the standard ServiceInitiators look through the
settings to determine the appropriate service to use. Going back to an example we have used multiple times:
=== Extending
[source]
----
hibernate.transaction.factory_class=jdbc
----
=== Expanding
The standard `TransactionFactoryInitiator` looks for this setting and determines what `TransactionFactory` service
implementation to use. Let's say for whatever reason we always want it to use JdbcTransactionFactory:
[source,java]
----
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.addService( TransactionFactory.class, new JdbcTransactionFactory() )
.build();
----
Or say we want to resolve the service implementation to use differently:
[source,java]
----
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.addInitiator( new MyCustomTransactionFactoryInitiator() )
.build();
----
==== Building StandardServiceRegistry - Expanding
Here the intent is to have the ServiceRegistry host custom services (completely new Service roles). As an example,
let's say our application publishes Hibernate events to a JMS Topic and that we want to leverage the Hibernate
ServiceRegistry to host a Service representing our TopicPublisher. So we will expand the ServiceRegistry to host this
completely new Service role:
[source,java]
----
/**
* The service role
*/
public interface EventPublishingService extends Service {
public void publish(Event theEvent);
}
/**
* A disabled (no-op) impl
*/
public class DisabledEventPublishingServiceImpl implements EventPublishingService {
public static DisabledEventPublishingServiceImpl INSTANCE = new DisabledEventPublishingServiceImpl();
private DisabledEventPublishingServiceImpl() {
}
@Override
public void publish(Event theEvent) {
// nothing to do...
}
}
/**
* A standard impl
*/
public class EventPublishingServiceImpl
implements EventPublishingService, Configurable, Startable, Stoppable, ServiceRegistryAwareService {
private ServiceRegistryImplementor serviceRegistry;
private String jmsConnectionFactoryName;
private String destinationName;
private Connection jmsConnection;
private Session jmsSession;
private MessageProducer publisher;
@Override
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
public void configure(Map configurationValues) {
this.jmsConnectionFactoryName = configurationValues.get( JMS_CONNECTION_FACTORY_NAME_SETTING );
this.destinationName = configurationValues.get( JMS_DESTINATION_NAME_SETTING );
}
@Override
public void start() {
final JndiService jndiService = serviceRegistry.getService( JndiService.class );
final ConnectionFactory jmsConnectionFactory = jndiService.locate( jmsConnectionFactoryName );
this.jmsConnection = jmsConnectionFactory.createConnection();
this.jmsSession = jmsConnection.createSession( true, Session.AUTO_ACKNOWLEDGE );
final Destination destination = jndiService.locate( destinationName );
this.publisher = jmsSession.createProducer( destination );
}
@Override
public void publish(Event theEvent) {
publisher.send( theEvent );
}
@Override
public void stop() {
publisher.close();
jmsSession.close();
jmsConnection.close();
}
}
public class EventPublishingServiceInitiator implements StandardServiceInitiator<EventPublishingService> {
public static EventPublishingServiceInitiator INSTANCE = new EventPublishingServiceInitiator();
public static final String ENABLE_PUBLISHING_SETTING = "com.acme.EventPublishingService.enabled";
public Class<R> getServiceInitiated() {
return EventPublishingService.class;
}
@Override
public R initiateService(Map configurationValues, ServiceRegistryImplementor registry) {
final boolean enabled = extractBoolean( configurationValues, ENABLE_PUBLISHING_SETTING );
if ( enabled ) {
return new EventPublishingServiceImpl();
}
else {
return DisabledEventPublishingServiceImpl.INSTANCE;
}
}
}
----
Now, lets tell Hibernate about this custom Service:
[source,java]
----
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.addInitiator( EventPublishingServiceInitiator.INSTANCE )
...
.build();
----
== Conclusion
Blah, blah, blah...

View File

@ -27,6 +27,23 @@ import org.hibernate.service.Service;
/**
* Service which acts as a registry for named strategy implementations.
* <p/>
* Strategies are more open ended than services, though a strategy managed here might very well also be a service. The
* strategy is any interface that has multiple, (possibly short) named implementations.
* <p/>
* StrategySelector manages resolution of particular implementation by (possibly short) name via the
* {@link #selectStrategyImplementor} method, which is the main contract here. As indicated in the docs of that
* method the given name might be either a short registered name or the implementation FQN. As an example, consider
* resolving the {@link org.hibernate.engine.transaction.spi.TransactionFactory} implementation to use. To use the
* JDBC-based TransactionFactory the passed name might be either {@code "jdbc"} or
* {@code "org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory"} (which is the FQN).
* <p/>
* Strategy implementations can be managed by {@link #registerStrategyImplementor} and
* {@link #unRegisterStrategyImplementor}. Originally designed to help the OSGi use case, though no longer used there.
* <p/>
* The service also exposes a general typing API via {@link #resolveStrategy} and {@link #resolveDefaultableStrategy}
* which accept implementation references rather than implementation names, allowing for a multitude of interpretations
* of said "implementation reference". See the docs for {@link #resolveDefaultableStrategy} for details.
*
* @author Steve Ebersole
*/