I'd like to know what is considered the best practices or patterns for decoupling application code from framework code, specifically regarding OSGi.
I'm going to use the example from the Felix SCR pages
The开发者_如何学Go example service is a Comparator
package sample.service;
import java.util.Comparator;
public class SampleComparator implements Comparator
{
public int compare( Object o1, Object o2 )
{
return o1.equals( o2 ) ? 0 : -1;
}
}
The code above contains no framework plumbing, it's focused and concise. Making this available to the application, when using OSGi, involves registering it with a service registry. One way, as described on the Felix pages linked, is by using the Service Component Runtime.
// OSGI-INF/sample.xml
<?xml version="1.0" encoding="UTF-8"?>
<component name="sample.component" immediate="true">
<implementation class="sample.service.SampleComparator" />
<property name="service.description" value="Sample Comparator Service" />
<property name="service.vendor" value="Apache Software Foundation" />
<service>
<provide interface="java.util.Comparator" />
</service>
</component>
and
Service-Component: OSGI-INF/sample.xml
All nice and lovely, my service implementation has no coupling at all to OSGI.
Now I want to use the service...
package sample.consumer;
import java.util.Comparator;
public class Consumer {
public void doCompare(Object o1, Object o2) {
Comparator c = ...;
}
}
Using SCR lookup strategy I need to add framework-only methods:
protected void activate(ComponentContext context) {
Comparator c = ( Comparator ) context.locateService( "sample.component" );
}
Using SCR event strategy I also need to add framework-only methods:
protected void bindComparator(Comparator c) {
this.c = c;
}
protected void unbindComparator(Comparator c) {
this.c = null;
}
Neither are terribly onerous, though I think it's probable you'd end up with a fair amount of this type of code duplicated in classes, which makes it more noise to filter.
One possible solution I can see would be to use an OSGi specific class to mediate between the consumer, via more traditional means, and the framework.
package sample.internal;
public class OsgiDependencyInjector {
private Consumer consumer;
protected void bindComparator(Comparator c) {
this.consumer.setComparator(c);
}
protected void unbindComparator(Comparator c) {
this.consumer.setComparator(null);
}
}
Though I'm not sure how you'd arrange this in the SCR configuration.
There is also org.apache.felix.scr.annotations, though that means it'll all only work if you're building with the maven-scr-plugin. Not so bad really and, AFAICT, they impose no runtime implications.
So, now you've read all that, what do you suggest is the best way of consuming OSGi provided services without 'polluting' application code with framework code?
1) I do not think the bind methods are polluting your code, they are just bean setters (you can also call them setXXX to be more traditional). You will need those for unit testing as well.
2) If you use bnd (which is in maven, ant, bndtools, eclipse plugin, etc) then you can also use the bnd annotations. bnd will then automatically create the (always horrible) xml for you.
package sample.service;
import java.util.Comparator;
import aQute.bnd.annotations.component.*;
@Component
public class SampleComparator implements Comparator {
public int compare( Object o1, Object o2 ) {
return o1.equals( o2 ) ? 0 : -1;
}
}
@Component
class Consumer {
Comparator comparator;
public void doCompare( Object o1, Object o2 ) {
if ( comparator.compare(o1,o2) )
....
}
@Reference
protected setComparator( Comparator c ) {
comparator = c;
}
}
In your manifest, just add:
Service-Component: *
This will be picked up by bnd. So no OSGi code in your domain code. You might be puzzled there is no unset method but the default for bnd is static binding. So the set method is called before you're activated and you're deactivated before the unset would be called. As long as your Consumer object would be a µservice too, you're safe. Look at bndtools, the bnd home page, and my blogs for more info about µservices.
PS. Your sample is invalid code because o1 will answer the both greater than and lesser than o2 if o1 != o2, this is not allowed by the Comparator contract and will make sorts unstable.
I'll write you how we do it in my project. As an OSGi container we are using Fuse ESB, although somewhere inside Apache Karaf can be found. To not pollute our code we use Spring DM (http://www.springsource.org/osgi), which greatly facilitates the interaction with OSGi container. It is tested "against Equinox 3.2.x, Felix 1.0.3+, and Knopflerfish 2.1.x as part of our continuous integration process" (the newest release).
Advantages of this approach:
- all "osgi" configuration in xml files - code not polluted
- ability to work with different implementations of OSGi container
How it looks?
- publishing service in OSGi registry:
< osgi:service id="some-id" ref="bean-implementing-service-to-expose" interface="interface-of-your-service" />
- importing service from OSGi registry:
< osgi:reference id="bean-id" interface="interface-of-exposed-service"/>
Moreover, to create valid OSGi bundles we use maven-bundle-plugin.
The advantage of the felix annotations compared to the ones in aQute.bnd.annotations.component seems to be that bind and unbind methods are automatically created by the felix scr plugin (you can annotate a private field). The disadvantage of the felix plugin is that it acts on the Java sources and so doesn't work for class files created in other languages (such as scala).
精彩评论