The OSGi Enterprise Release 7 specification targeted for release in the coming months contains a brand new specification:
CDI Integration. This specification brings the exciting features and capabilities of the
Contexts and Dependency Injection (CDI) specification to OSGi.
CDI itself is a vast specification and with that in mind several goals were established to guide the development of the integration:
- Do not reinvent the wheel, follow established approaches such as leveraging the CDI Service Provider Interface model
- Make code look and feel as natural to CDI developers as possible using CDI designs and best practices where applicable and generally adopt CDI form and function
- Provide uncompromising support for key OSGi features such as services, configuration and the dynamics these entail, while not over-complicating or over-engineering the design
- Enable modularity for CDI Portable Extensions such that an ecosystem of portable extensions may emerge
Beans
The most basic interaction a developer has with CDI comes from the
"Contexts" portion of the spec and the creation of
beans, which stems from defining in which context a bean's instances reside. Generally this is accomplished by applying a
scope annotation to a POJO.
@ApplicationScoped
public class ShopImpl implements Shop {
public List<Product> getProducts() { ... }
}
This POJO is a
bean whose scope is defined as
@ApplicationScoped whose
context defines its instance as visible to the entire application.
Injection
The next interaction with CDI comes from the
"Dependency Injection" portion of the spec. This is accomplished by applying the
@Inject annotation to a field, method or constructor of a POJO.
@ApplicationScoped
public class ShopImpl implements Shop {
@Inject
Logger logger;
public List<Product> getProducts() { ... }
}
This POJO will now have a
Logger instance injected into it when the instance is created.
OSGi Services
With a basic understanding of
beans and
dependency injection let's move on to the OSGi CDI Integration features. The most important feature provided by the CDI integration is the ability to register services with or obtain services from the OSGi service registry.
Registering a service can be as simple as applying the
@Service annotation to a bean.
@ApplicationScoped
@Service
public class ShopImpl implements Shop {
...
}
This POJO is registered into the service registry with the service type
Shop.
Adding service properties is accomplished using annotations that are meta-annotated with the
@BeanPropertyType annotation. A couple of examples are the
@ServiceDescription and
@ServiceRanking annotations defined by this specification.
@ApplicationScoped
@Service
@ServiceDescription("This is the primary implementation of the Shop service.")
@ServiceRanking(1000)
public class ShopImpl implements Shop {
...
}
Obtaining services is accomplished by using the
@Reference annotation in conjunction with
@Inject.
@ApplicationScoped
@Service
public class ShopImpl implements Shop {
@Inject
@Reference
ProductStore productStore;
public List<Product> getProducts() {
return productStore.all();
}
}
This POJO is injected with a service of type
ProductStore.
Filtering services is accomplished by specifying a target filter from the
@Reference annotation and/or by adding one or more annotations meta-annotated with
@BeanPropertyType to the injection point. The following example filters services having the
service.vendor service property equal to
Acme, Inc.
@ApplicationScoped
@Service
public class ShopImpl implements Shop {
@Inject
@Reference
@ServiceVendor("Acme, Inc.")
ProductStore productStore;
public List<Product> getProducts() {
return productStore.all();
}
}
Optionality, Cardinality and Dynamics
In OSGi services may need to be expressed in terms of their optionality (if a service is required at all), cardinality (how many services are required) and their dynamics (if the service(s) may change during the lifetime of the bean's context). These concerns are handled elegantly using the Java type system.
The previous example demonstrated a
mandatory,
unary cardinality reference.
Optional references are expressed using Java's
Optional type.
@ApplicationScoped
@Service
public class ShopImpl implements Shop {
@Inject
@Reference
Optional<ProductStore> productStore;
public List<Product> getProducts() {
return productStore.map(s -> s.all()).orElse(Collections.emptyList());
}
}
Multi-cardinality references are expressed using a
Collection or
List types. The default cardinality in this scenario is 0 (which is to say that the services are optional by default). A minimum cardinality can optionally be expressed in conjunction with multi-cardinality using the
@MinimumCardinality annotation.
@ApplicationScoped
@Service
public class ShopImpl implements Shop {
@Inject
@Reference
@MinimumCardinality(1)
List<ProductStore> productStores;
public List<Product> getProducts() {
return productStores.stream().flatMap(
s -> s.all().stream()
).collect(Collectors.toList());
}
}
Dynamic references are expressed using the
Provider type.
@ApplicationScoped
@Service
public class ShopImpl implements Shop {
@Inject
@Reference
Provider<ProductStore> productStore;
public List<Product> getProducts() {
return productStore.get().all();
}
}
Greediness
One of the key distinctions between
Declarative Services and CDI Integration with respect to references to services is greediness. Where the greediness of references in Declarative Services is reluctant by default, the greediness of references in CDI Integration is greedy by default. This means that CDI Integration references will always reflect the
best service(s) available. This also means that CDI Integration beans may have a more volatile life cycle depending on their references and how often matching services come and go.
Defining a reference to be reluctant is accomplished using the
@Reluctant annotation. (This means that once an adequate service is bound it is unlikely to be replaced with a
better service in the future.)
@ApplicationScoped
@Service
public class ShopImpl implements Shop {
@Inject
@Reference
@Reluctant
ProductStore productStore;
public List<Product> getProducts() {
return productStore.all();
}
}
Beans vs. Components
In a traditional CDI application, all beans make up the application and form a single cohesive unit; the CDI container. Modeling external dependencies such as (non-dynamic) references to services and configurations without complexities like proxies or byte code instrumentation means the CDI container has to be treated as a single unit, resulting that whenever any dependency changes in a significant way the entire CDI container must be destroyed and recreated. This is a fundamental difference to the model defined in Declarative Services which permits individual components to exist independently from each other. It is also rather limiting. In order to address this limitation the CDI Integration defines the concept of
components. Components are cohesive collections of beans which have a consistent, related life cycle which may operate individually from one another.
The CDI Integration defines 3 component types:
- Container Component - All traditional beans (@ApplicationScoped, @RequestScoped, @SessionScoped, @Dependent, custom scopes, etc.) are part of the container component (in fact, any bean which is not @ComponentScoped is part of the container component)
- Factory Components - are collections of @ComponentScoped beans rooted by a bean having the stereotype @FactoryComponent (such components are driven by factory configuration)
- Single Components - are collections of @ComponentScoped beans rooted by a bean with the stereotype @SingleComponent
These components are arranged in two levels where the
container component exists as the first level and any number of factory and/or single component children exist in the second level.
|
Type (relation to CDI Container) |
1 |
Container Component (1..1) |
2 |
Factory Component (0..n) |
Single Component (0..n) |
Factory and single components exist and react to change independently from each other, just like Declarative Services components, while also depending on the container component. If the container component needs to be recreated then all factory and single components must also be recreated.
With this model, it's possible to replicate the Declarative Services component model, while also supporting the traditional monolithic CDI approach (with the additional capability to share beans between container, factory and single components provided they are
@ApplicationScoped or
@Dependent and also to use other CDI features like Decorators, Interceptors and CDI Portable Extensions.)
Let's see an example:
@ApplicationScoped
@Service
public class ShopImpl implements Shop {
@Inject
@Reference
Provider<List<ProductStore>> productStores;
public List<Product> getProducts() {
return productStores.get().stream().flatMap(
s -> s.all().stream()
).collect(Collectors.toList());
}
}
@BeanPropertyType
public @interface StoreConfig {
String vendor_name();
String data_file();
}
@FactoryComponent("product.store")
@Service
public class ProductStoreImpl implements ProductStore {
@Inject
@ComponentProperties
StoreConfig storeConfig;
public List<Product> all() {
return read(storeConfig);
}
}
This application provides a
Shop service that is dynamically tracking a number of
ProductStore services.
ProductStore instances are created by adding new factory configuration instances using the factory PID
product.store. Each
ProductStore instance is injected with its component properties which are coerced into a typesafe, user-defined
StoreConfig for easy processing.
Conclusion
The CDI Integration specification bridges the powerful features of CDI and OSGi in a clean and concise way which should empower developers. There are many other aspects of the specification that don't fit into a single blog post such as modularity for CDI Portable Extensions, further discussion about
BeanPropertyType, configuration dependencies, tracking service events, the relationship with Decorators and Interceptors, etc. So don't forget to read the
latest draft of the spec. The CDI Integration specification is an important step forward in OSGi dependency injection story that hopefully opens the OSGi door to a wider audience already familiar with CDI.
Want to find out more about OSGi R7?