-
Notifications
You must be signed in to change notification settings - Fork 0
Service discovery
OSGi and SPI are two technologies that can be used for discovering implementations at runtime. Projects should support both technologies if possible. See below on how to accomplish that.
First a very brief introduction to these technologies.
The SPI is part of the Java API and it is easy to use. It does however have some inherent problems, which is one of the reasons why OSGi came to be in the first place.
Implementations that are to be provided as Java services must be registered in the META-INF/services
folder. That folder should contain one file for each SPI interface. The file name must be equal to the interface's canonical name. The contents of each file should be a list of canonical names of implementing classes.
Note that Java 9 uses the module-info file to describe services.
To use OSGi services, the application must run in an OSGi container.
All projects here use OSGi-annotations to define service implementations. Hence, only this approach is described below.
The implementation class (which implements a service interface) is annotated with @Component
. Dependencies on other OSGi services can be obtained by adding the @Reference
annotation on a method with the dependency's interface as argument. This method can be bound automatically with a corresponding method to unregister the implementation (should it become unavailable for some reason). This method's name follows a pattern suggested by the method used to register the implementation, that is to say the method having the @Reference
annotation.
The annotations are used to generate xml-files (a.k.a. "declarative services") at build time. These files are placed in the jar's OSGI-INF
folder. The files are named after the implementations and specify in them the interfaces they implement and the services they rely on.
Supporting both SPI and OSGi is not exactly straight forward, but it can be done reliably using the pattern described below.
Both SPI and OSGi rely on implementations having a public constructor that takes no arguments. The instance created will have no references to other discoverable objects. These must subsequently be set, see below.
Include a method called setCreatedWithSPI()
on interfaces which may be discovered by both SPI and OSGi.
If the object is created in an OSGi context, its references (or dependencies) are set using the methods annotated on the implementation. For example:
/**
* Adds a factory (intended for use by the OSGi framework)
* @param factory the factory to add
*/
@Reference(cardinality=ReferenceCardinality.MULTIPLE, policy=ReferencePolicy.DYNAMIC)
public void addFactory(EmbosserProvider factory) {
providers.add(factory);
}
/**
* Removes a factory (intended for use by the OSGi framework)
* @param factory the factory to remove
*/
// Unbind reference added automatically from the annotation on addFactory
public void removeFactory(EmbosserProvider factory) {
providers.remove(factory);
}
In an SPI context it's a bit more complicated (if the goal is to also support OSGi, that is). The methods above are not, and cannot be, in the API. Therefore, they cannot be used in an SPI context since instances created with SPI are only aware of methods provided by the interface. The caller cannot be expected to know what references every possible implementation needs anyway. Fortunately, the caller can know that it is in SPI context (typically by the use of ServiceLoader.load(class)
). Therefore, when creating objects using for example ServiceLoader.load(class)
, the method setCreatedWithSPI()
can be called on the created instance to inform it that it itself can use calls to ServiceLoader.load(class)
to set up its references (or dependencies) and so on. Note that the instance typically calls newInstance()
on concrete classes rather than calling ServiceLoader
directly. For example:
public static EmbosserCatalog newInstance() {
EmbosserCatalog ret = new EmbosserCatalog();
Iterator<EmbosserProvider> i = ServiceLoader.load(EmbosserProvider.class).iterator();
while (i.hasNext()) {
EmbosserProvider ep = i.next();
// setCreatedWithSPI() allows the implementation to set its references using SPI service discovery,
// including calls like SomeFactory.newInstance();
ep.setCreatedWithSPI();
ret.addFactory(ep);
}
return ret;
}
While it would be possible to create a more dynamic solution to this problem, it is important to remember that this pattern only exists in order to support both OSGi and SPI. Therefore, hacking the discovery mechanism for SPI contexts doesn't solve any problem, aside from, perhaps, one of aesthetics.
See also the Java modules plan.
The structure and content of this wiki is inspired by the blog post "Agile software architecture documentation".
Architecture Documentation
- Context
- Functional Overview
- Constraints
- Quality Attributes
- Principles
- Software Architecture
- Code
- Development Environment
- Data
- Deployment
- Decision Log