Overview
Jini is a distributed computing technology developed by Sun Microsystems. It is now being continued under the auspices of the Apache River project.
Jini enables highly scalable and resilient distributed applications to run on an infrastructure consisting of unreliable hardware, networks, and operating systems; in other words, real world environments. The underlying protocols that enable this type of autonomic computing can, however, be a bit . . . fiddly, to use a highly technical term.
This page describes a set of utility classes that encapsulate the complexity of service lookup in Jini.
Jini Services and Proxies
Jini systems consist of many interacting services distributed across a network. These services make themselves available by registering with a lookup service (which is also a Jini service).
When a Jini service starts, it finds any available lookup services and registers a proxy with each of them. When a client starts, it finds the available lookup services in exactly the same way, then requests a proxy for each service it needs to use. Service lookup is done by interface — the client doesn't need to know which particular implementation sits behind the proxy, or even the implementation of the proxy itself.
There are three types of proxies in a typical Jini system. Standalone proxies contain all of the behaviors exposed by the service. Once a client has retrieved a copy of a standalone proxy from the lookup service, it simply uses it directly.
Thin or remote proxies pass all method invocations to a remote implementation. The communication protocol between the proxy and the remote service is not constrained by Jini. Often RMI is used, but Jini's protocol independence has resulted in systems using raw sockets, message-oriented middleware, and even CICS.
Finally, hybrid proxies combine aspects of standalone and thin proxies, implementing some methods locally and calling a remote service for others.
If a particular proxy becomes unusable, due to the remote service no longer being available, for example, the client simply requests another proxy for the same service interface from the lookup service.
Jini Service Discovery
Jini provides a very flexible set of options for discovering lookup services, partitioning services, and detecting service startup and shutdown. That flexibility means that, while service discovery in Jini is straightforward, it does require detailed configuration.
Multicast and Unicast
The lookup service can be found by unicast to a particular host machine and port number or by broadcast on a multicast address and port. Broadcast discovery is more flexible because it completely decouples clients from any knowledge of the location of the lookup service, but may not be available in some environments.
Lookup services, like any other Jini service, may start and stop at any time. When multicast lookup is used, these events are easily monitored. When unicast lookup is required, Jini supports the specification of multiple host and port pairs to provide resiliency.
Groups
Services, including the lookup service, may be members of one or more groups. Groups provide a mechanism for partitioning sets of services. A common use for groups is to allow development, quality assurance, pre-production, and production services to run on the same network simultaneously.
The Service Discovery Protocol
In order to discover and use Jini services, a client must:
- Register to receive notifications of available lookup services
- Notify existing lookup services that it is interested in the services they have registered
- Filter the available services to those of interest
- Manage lookup service start and stop events
- Manage service start and stop events
The ServiceCache Class
ServiceClass.java
is an implementation of a class that encapsulates the details of
finding available lookup service and maintaining a cache of lookup
services and the services registered with them.
ServiceCache implements two interfaces,
DiscoveryListener and
ServiceDiscoveryListener. DiscoveryListener
defines two methods, discovered and
discarded, that are called when a lookup service starts
up or shuts down, respectively. ServiceDiscoveryListener
exposes serviceAdded, serviceChanged, and
serviceRemoved, which are called when a service of
interest is added to, changed in, or removed from a known lookup
service.
Initializing The ServiceCache
The ServiceCache constructor initializes all of the Jini
components required to ensure that the DiscoveryListener
and ServiceDiscoveryListener methods are invoked when
appropriate. The core of the constructor consists of three method
calls:
lookupDiscoveryManager_
= new LookupDiscoveryManager(groups,locators,this);
serviceDiscoveryManager_
= new ServiceDiscoveryManager(lookupDiscoveryManager_,
leaseRenewalManager_);
lookupCache_
= serviceDiscoveryManager_.createLookupCache(template,filter,this);
LookupDiscoveryManager is a utility class that registers
a DiscoveryManager, the current ServiceCache
object in this case, to receive events when lookup services start and
shut down. It accepts both an array of Jini groups, for multicast
lookup, and an array of locators, for unicast lookup.
The ServiceDiscoveryManager is a utility class similar to
LookupDiscoveryManager but for services registered with
lookup services rather than lookup services themselves. It uses the
LookupDiscoveryManager created previously to find lookup
services. The second parameter to the
ServiceDiscoveryManager constructor is a
LeaseRenewalManager. All Jini resources are
leased to prevent resource accretion. The
LeaseRenewalManager maintains the leases granted to the
ServiceDiscoveryManager by the lookup services. If the
ServiceDiscoveryManager terminates for any reason, the
leases will not be renewed and the lookup services will stop sending
events to it.
The LookupCache is simply a cache of services maintained
by the ServiceDiscoveryManager. This cache is queried
when service proxies are requested from the ServiceCache.
The ServiceCache Constructor
The signature for the full ServiceCache constructor is:
This allows the specification of multicast groups, unicast lookup
locators, service templates, and filters. Most Jini clients won't
require this many options. Accordingly,
public ServiceCache(String[] groups,
LookupLocator[] locators,
ServiceTemplate template,
ServiceItemFilter filter)
ServiceCache
provides some utility constructors:
The default constructor uses the public multicast group and listens
for all Jini services. The most frequently used constructor is the
one that takes an array of multicast groups and the interface class of
interest.
public ServiceCache(ServiceTemplate template)
public ServiceCache(String[] groups,Class serviceClass)
public ServiceCache(Class serviceClass)
public ServiceCache()
The ServiceCache Interface
ServiceCache currently exposes only three methods for
accessing service proxies:
The first of these returns a proxy from the
public Object service(ServiceItemFilter filter)
public Object service(final Class serviceClass)
public Object service()
LookupCache
matching the specified filter, if any. The second is a convenience
method that creates a filter from the specified class and attempts to
find a matching proxy. The third returns any service from the cache.
This is only useful if the ServiceCache has been
configured to cache only proxies for services of one particular type.
JavaSpaceCache and TransactionManagerCache
The ServiceCache class encapsulates much of the
complexity of Jini service discovery, but even simpler utilities can
be built on top of it. The most common requirement for a client in a
Jini system is to find a service of a particular type registered with
a lookup service for a particular group.
In a language with real support for generic programming, it would be
possible to create a class called something like
ServiceFinder, parameterized on the service of interest,
to provide exactly this capability. Because the Java designers chose
to emasculate the Java generics implementation with
type erasure,
thereby
significantly increasing the complexity of the language
with no correspondingly signficant benefits, this is not possible.
(Yes, I know the arguments in favor of type erasure. They're wrong. Generics should have been done right or not done at all.)
What I have done instead is implement convenience classes for two of
the most often used Jini services,
JavaSpaces
and
transaction managers.
The convenience classes are named JavaSpaceCache and
TransactionManagerCache respectively. The design of both is
nearly identical — instances of the class are constructed with
one or more lookup groups specified, the public group being the
default, and when the service proxy is requested the method blocks
until an instance is found.
Example
The main methods of both convenience classes demonstrate
how to use them. Taking JavaSpaceCache as an example:
Finding a remote service isn't going to get much simpler than that.
JavaSpaceCache cache = new JavaSpaceCache();
JavaSpace space = cache.javaSpace();
Source Code
The full source is available as separate files:
or as a tarball.In order to build and run the example, you will need the Jini 2.1 Starter Kit and J2SE 5.0 installed.
Please contact me by email if you have any questions, comments, or suggestions.