Getting Started with OSGi: Declarative Services and Dependencies

Welcome back to the EclipseZone "Getting Started with OSGi" tutorial series. Before getting into today's lesson I'd like to remind you that you can find links to all the previous parts of the tutorial on the contents page  on my personal blog.

Last time we took our first look at Declarative Services. This time we will look at the service consumer side of Declarative Services. Remember that previously we registered a service under the java.lang.Runnable interface; now we will create a component that depends on that service.

As discussed, the Declarative Services specification is all about letting you focus on the application logic of your code, rather than the OSGi "glue" code that had to be written in previous lessons. With that in mind, we're simply going to dive into the code, but before we do that we need to create a project. Follow the same steps as in the last lesson, but use the project name "SampleImporter".

Now copy the following code from your browser and paste it into the src folder inside the newly created Eclipse project:
package org.example.ds;
 
import org.eclipse.osgi.framework.console.CommandInterpreter;
import org.eclipse.osgi.framework.console.CommandProvider;
 
public class SampleCommandProvider1 implements CommandProvider {
	
	private Runnable runnable;
	
	public synchronized void setRunnable(Runnable r) {
		runnable = r;
	}
	public synchronized void unsetRunnable(Runnable r) {
		runnable = null;
	}
	public synchronized void _run(CommandInterpreter ci) {
		if(runnable != null) {
			runnable.run();
		} else {
			ci.println("Error, no Runnable available");
		}
	}
	public String getHelp() {
		return "\trun - execute a Runnable service";
	}
}



This class implements the CommandProvider interface, which is used to extend the set of commands available at the "osgi>" prompt when you run Equinox. The reason for writing a CommandProvider is that it provides a convenient way to test our code interactively. There is a more detailed discussion of command providers in Chris Aniszczyk's article on IBM developerWorks.

Notice that we don't call any OSGi APIs in this class, in fact we don't even need to import anything from the org.osgi.* packages. The service that we depend on – in this case, an instance of java.lang.Runnable – is provided to us through the setRunnable method, and it is taken away using the unsetRunnable method. We can consider this a form of dependency injection .

The other two methods, getHelp and _run are the implementation methods for the command provider. Yes, "_run" is a funny name with its leading underscore, but that is just an odd feature of the Equinox console API, and nothing to do with OSGi or Declarative Services. Methods using the leading-underscore pattern become commands on the Equinox console, so by providing a method called _run we have added a "run" command. Another thing to notice about this class is that we're being careful to ensure that the runnable field is updated and accessed in a thread-safe way. Thread safety is particularly important in OSGi since it is intrinsically multithreaded, but frankly we should always write our code to be thread-safe.

As before, we have to provide an XML file containing the DS declarations to get this to work. Copy the following into OSGI-INF/commandprovider1.xml in your plug-in project:
<?xml version="1.0"?>
<component name="commandprovider1">
	<implementation class="org.example.ds.SampleCommandProvider1"/>
	<service>
		<provide interface="org.eclipse.osgi.framework.console.CommandProvider"/>
	</service>
	<reference name="RUNNABLE"
		interface="java.lang.Runnable"
		bind="setRunnable"
		unbind="unsetRunnable"
		cardinality="0..1"
		policy="dynamic"/>
</component>



There's another important step which I neglected to mention last time. (Thanks to Seamus Venasse for pointing it out .) You need to edit the build.properties file in your plug-in project, and check the box next to the OSGI-INF folder. This is necessary to ensure the folder is included when the bundle is exported using Eclipse's export wizard, or built using PDE Build.
Also we need to add the following line to the bundle manifest:

Service-Component: OSGI-INF/commandprovider1.xml



This declaration has two of the same elements that we saw before, the implementation and service nodes. The implementation node provides the name of the class which implements the component, and the service node tells DS to register the component as a service. In this case we are registering under the CommandProvider interface, which is how we let the Equinox console know about the existence of our command provider.

The next element called reference is something we haven't seen before – it declares to DS that our component has a dependency on a service. The name attribute is simply an arbitrary string which names the dependency (we don't need to worry yet about what this is used for) and the interface attribute specifies the name of the interface we depend on. The bind attribute is the name of a method in the implementation class that will be called by DS when a service becomes available, or in other words, when a Runnable service is registered with the Service Registry, DS will obtain a reference to the new service object and supply it to our component using the specified method. Likewise the unbind attribute is the name of a method that will be called by DS when a service we were using becomes unavailable.

The cardinality attribute reveals the real power of DS. This attribute controls whether the dependency is optional or mandatory, and whether it is singular or multiple. The possible values are:

    * 0..1: optional and singular, "zero or one"
    * 1..1: mandatory and singular, "exactly one"
    * 0..n: optional and multiple, "zero to many"
    * 1..n: mandatory and multiple, "one to many" or "at least one"



In this example we chose optional and singular, which means our command provider can cope with the dependent service being unavailable. Looking back at the code for the _run method, you can see it was necessary to do a null check to handle such a situation.

Let's see what happens if we run this bundle. If you still have your SampleExporter bundle from last time, then you should see the following response when you type the "run" command at the osgi> prompt:


    Hello from SampleRunnable



Great! This confirms that we have successfully imported the Runnable service that we wrote in the last lesson. Now try using the stop command to turn off the SampleExporter bundle. When you type the "run" command again you should see:


    Error, no Runnable available



Which means that DS noticed the Runnable service going away, and called our unsetRunnable method to let us know.

Looking again at the cardinality attribute, what do you think will happen if we change to "1..1", i.e. switch from an optional to a mandatory dependency? Try making that change and restarting Equinox. If the SampleExporter bundle is active when we type "run" then we will see the same message as before: "Hello from SampleRunnable". However, if SampleExporter is inactive then we will see a very different error message than before. In fact, we will see Equinox's console help message, which is its standard response to an unrecognized command. This indicates that our command provider itself has been unregistered by DS! Since the component has a mandatory dependency which cannot be satisfied, DS is forced to deactivate the component and unregister any services it was providing. That in turn means that the Equinox console forgets about the "run" command.

The ease with which we can make changes like this is one of the best reasons to use Declarative Services. Remember that with ServiceTracker we would have had to rewrite a fairly substantial piece of code.

You might be wondering about the policy attribute, since I haven't mentioned it yet. The value of this can be either "static" or "dynamic", and it states whether the component implementation is able to cope with having services dynamically switched. If it is not, then it is necessary for DS to deactivate the component and create a new instance each time the target service changes. As you might expect, this is quite a heavyweight approach, and it's best to code your component classes to support dynamic switching whenever possible. Unfortunately the default value is static, so you have to remember to set it to dynamic explicitly.

So, we've seen optional singular and mandatory singular, but what about multiple dependencies? There may be more than one Runnable registered in the Service Registry, and if we only bind to one of them then the choice of which one we get is arbitrary. Perhaps instead we would like to implement a "runall" command that runs all of the currently registered Runnables.

What would happen if we just changed the cardinality to "0..n"? In a way, that would almost work: instead of calling the setRunnable method just one time, DS will call setRunnable once for each instance of Runnable in the registry. The problem is that the class we wrote will get confused. Instead of setting a single Runnable field, we need to accumulate the Runnables into a collection. Here's the slightly modified version of the class, which you can again copy and paste directly into the src folder of your project:
package org.example.ds;
 
import java.util.*;
import org.eclipse.osgi.framework.console.CommandInterpreter;
import org.eclipse.osgi.framework.console.CommandProvider;
 
public class SampleCommandProvider2 implements CommandProvider {
 
	private List<Runnable> runnables =
		Collections.synchronizedList(new ArrayList<Runnable>());
	
	public void addRunnable(Runnable r) {
		runnables.add(r);
	}
	public void removeRunnable(Runnable r) {
		runnables.remove(r);
	}
	public void _runall(CommandInterpreter ci) {
		synchronized(runnables) {
			for(Runnable r : runnables) {
				r.run();
			}
		}
	}
	public String getHelp() {
		return "\trunall - Run all registered Runnables";
	}
}



Now create OSGI-INF/commandprovider2.xml and copy in the following contents:
<?xml version="1.0"?>
<component name="commandprovider2">
	<implementation class="org.example.ds.SampleCommandProvider2"/>
	<service>
		<provide interface="org.eclipse.osgi.framework.console.CommandProvider"/>
	</service>
	<reference name="RUNNABLE"
		interface="java.lang.Runnable"
		bind="addRunnable"
		unbind="removeRunnable"
		cardinality="0..n"
		policy="dynamic"/>
</component>



... and finally add this file to the Service-Component header of the manifest, so that it looks like this:

Service-Component: OSGI-INF/commandprovider1.xml,

  OSGI-INF/commandprovider2.xml



This declaration essentially the same as before, except we have renamed the bind and unbind methods, and changed the cardinality to "0..n". As an exercise, try registering a few additional Runnable services and check that the "runall" command executes all of them. Next, what do you think will happen if we change the cardinality to "1..n"? Try verifying it does what you expect.

That's all for this lesson. Remember that the OSGi Alliance Community Event is happening next week in Munich, Germany, and I believe it's still not too late to register . See you there!

你可能感兴趣的:(eclipse,thread,xml,IBM,osgi)