Reputation: 714
I've created a service with multiple implementations, like so:
@ProviderType
public interface MyService {
public void printMessage();
}
@Component
public class Foo implements MyService {
public void printMessage() {
System.out.println("foo");
}
}
@Component
public class Bar implements MyService {
public void printMessage() {
System.out.println("bar");
}
}
The real implementations are obviously a bit more complex (I can't just make a single implementation where printMessage
takes an argument!). The consumers of the service need to be able to select an appropriate implementation based on a property - for simplicity, we can assume I'm using the name of the implementation.
The factory method used by the existing non-OSGi implementation is something like this:
public MyService getService(String property) {
switch (property) {
case "ham":
return new Foo();
case "spam":
return new Bar();
}
}
The real implementation is, again, more complicated (it's based on a substring match of the property
parameter). The simplest approach (for me) to convert this to OSGi would be to make this factory method into a static method of MyService
, returning only the necessary filter string, and to make consumers take care of the fiddly bits with getting a BundleContext, then getting and ungetting the service:
public class MyConsumer {
private BundleContext context;
@Activate
public void activate(BundleContext context) {
this.context = context;
}
public void doStuff() {
Collection<ServiceReference<MyService>> refs = context.getServiceReferences(MyService.class, MyService.getService("ham"));
ServiceReference<MyService> service = refs.iterator().next();
context.getService(service).printMessage();
context.ungetService(service);
}
Is there a better way to create a "factory" that returns "services", but doesn't require that the consumers deal with BundleContexts etc.?
I've found a couple approaches that won't quite work:
@Reference(target = "ham")
(I know that syntax is wrong) allows me to select an implementation based on a property, but only at compile-time.ServiceFactory
allows different bundles to get different instances of the service, but not to specify implementation.Upvotes: 1
Views: 990
Reputation: 15372
The idea of a service is that the consumer should NOT select because that makes the consumers very un-reusable. It generally makes brittle systems if you move that control into the consumer. The consumer should use whatever is registered.
One anti-pattern is that you try to hide the implementation of the service but then needing a specific implementation in your consumer. If this is your use cases, your implementation is public and just make it its own service type. It could still register as the common MyService type as well of course, you can register under multiple types.
If you have an outside selection criteria for which one to use, e.g. a printer and you want the user to decide, then you just get all and show them to the user. Using the service id you can use the right one.
If you need to configure which service should be used then you should use the @Reference(target="(selection.criterium=foo)")
. You can override this selection with Configuration Admin. This is explained in targets. You basically set a property with the name of the reference followed by .target
to the required filter.
Upvotes: 2