程式扎記: [ OSGi In Action ] Introducing OSGi : OSGi revealed (2)

標籤

2011年7月7日 星期四

[ OSGi In Action ] Introducing OSGi : OSGi revealed (2)

Preface : 
From previous discussion, we know OSGi functionality is divided over the three layers mentioned previously (modularity, lifecycle, and service), we’ll show you three different “Hello, world!” examples that illustrate each of these layers. 

Module layer example : 
The module layer isn’t related to code creation as such; rather, it’s related to the packaging of your code into bundles. You need to be aware of certain code-related issues when developing, but by and large you prepare code for the module layer by adding packaging metadata to your project’s generated JAR files. For example, suppose you want to share the following class : 

Listing 1.2 Basic greeting implementation
  1. package org.foo.hello;  
  2.   
  3. public class Greeting {  
  4.     final String m_name;  
  5.     public Greeting(String name) {  
  6.         m_name = name;  
  7.     }  
  8.     public void sayHello() {  
  9.         System.out.println("Hello, " + m_name + "!");  
  10.     }  
  11. }  

During the build process, you compile the source code and put the generated class file into a JAR file. To use the OSGi module layer, you must add some metadata into your JAR file’s META-INF/MANIFEST.MF file, such as the following : 

Bundle-ManifestVersion: 2
Bundle-Name: Greeting API
Bundle-SymbolicName: org.foo.hello
Bundle-Version: 1.0
Export-Package: org.foo.hello;version="1.0"

The first line indicates the OSGi metadata syntax version. Next is the human-readable name, which isn’t strictly necessary. This is followed by the symbolic name and version bundle identifier. The last line shares packages with other bundles. 

In this example, the bulk of the metadata is related to bundle identification. The important part is the Export-Package statement, because it extends the functionality of a typical JAR file with the ability for you to explicitly declare which packages contained in the JAR are visible to its users. In this example, only the contents of the org.foo.hello package are externally visible; if the example included other packages, 
they wouldn’t be externally visible. This means that when you run your application, other modules won’t be able to accidentally (or intentionally) depend on packages your module doesn’t explicitly expose. 

To use this shared code in another module, you again add metadata. This time, you use the Import-Package statement to explicitly declare which external packages are required by the code contained in the client JAR. The following snippet illustrates : 

Bundle-ManifestVersion: 2
Bundle-Name: Greeting Client
Bundle-SymbolicName: org.foo.hello.client
Bundle-Version: 1.0
Import-Package: org.foo.hello;version="[1.0,2.0)"

In this case, the last line specifies a dependency on an external package. In addition, there are tools that can help you create your bundle metadata, which we’ll discuss in appendix A; but in reality, no special tools are required to create a bundle other than what you normally use to create a JAR file. Chapter 2 will go into all the juicy details of OSGi modularity. 

Lifecycle layer example : 
In the last subsection, you saw that it’s possible to take advantage of OSGi functionality in a non-invasive way by adding metadata to your existing JAR files. Such a simple approach is sufficient for most reusable libraries, but sometimes you need or want to go further to meet specific requirements or to use additional OSGi features. The lifecycle layer moves you deeper into the OSGi world. 

Perhaps you want to create a module that performs some initialization task, such as starting a background thread or initializing a driver; the lifecycle layer makes this possible. Bundles may declare a given class as an activator, which is the bundle’s hook into its own lifecycle management. We’ll discuss the full lifecycle of a bundle in chapter 3, but first let’s look at a simple example to give you an idea of what we’re talking about. The following listing extends the previous Greeting class to provide a singleton instance. 

Listing 1.3 Extended greeting implementation
  1. package org.foo.hello;  
  2.   
  3. public class Greeting {  
  4.     static Greeting instance;  
  5.     final String m_name;  
  6.     Greeting(String name) {  
  7.         m_name = name;  
  8.     }  
  9.     public static Greeting get() {  
  10.         return instance;  // Client must use singleton  
  11.     }  
  12.     public void sayHello() {  
  13.         System.out.println("Hello, " + m_name + "!");  
  14.     }  
  15. }  

Listing 1.4 implements a bundle activator to initialize the Greeting class singleton when the bundle is started and clear it when it’s stopped. The client can now use the preconfigured singleton instead of creating its own instance : 

Listing 1.4 OSGi bundle activator for our greeting implementation
  1. package org.foo.hello;  
  2.   
  3. import org.osgi.framework.BundleActivator;  
  4. import org.osgi.framework.BundleContext;  
  5.   
  6. public class Activator implements BundleActivator {  
  7.     public void start(BundleContext ctx) {  
  8.         Greeting.instance = new Greeting("lifecycle");   
  9.     }  
  10.     public void stop(BundleContext ctx) {  
  11.         Greeting.instance = null;  
  12.     }  
  13. }  

A bundle activator must implement a simple OSGi interface, which in this case is composed of the two methods start() and stop(). At execution time, the framework constructs an instance of this class and invokes the start() method when the bundle is started and the stop() method when the bundle is stopped. (What we mean by starting and stopping a bundle will become clearer in chapter 3.) Because the framework uses the same activator instance while the bundle is active, you can share member variables between the start() and stop() methods. 

You may wonder what the single parameter of type BundleContext in the start() and stop() methods is all about. This is how the bundle gets access to the OSGi framework in which it’s executing. From this context object, the module has access to all the OSGi functionality for modularity, lifecycle, and services. In short, it’s a fairly important object for most bundles, but we’ll defer a detailed introduction of it until later when we discuss the lifecycle layer. The important point to take away from this example is that bundles have a simple way to hook into their lifecycle and gain access to the underlying OSGi framework. 

Of course, it isn’t sufficient to just create this bundle activator implementation; you have to tell the framework about it. Luckily, this is simple. If you have an existing JAR file you’re converting to be a module, you must add the activator implementation to the existing project so the class is included in the resulting JAR file. If you’re creating a bundle from scratch, you need to compile the class and put the result in a JAR file. You must also tell the OSGi framework about the bundle activator by adding another piece of metadata to the JAR file manifest. For this section’s example, you add the following metadata to the JAR manifest : 

Bundle-Activator: org.foo.hello.Activator
Import-Package: org.osgi.framework

Notice that you also need to import the org.osgi.framework package, because the bundle activator has a dependency on it. We’ve now introduced how to create OSGi bundles out of existing JAR files using the module layer and how to make your bundles lifecycle aware so they can use framework functionality. The last example in this section demonstrates the service-oriented programming approach promoted by OSGi. 

Service layer example : 
If you follow an interfaced-based approach in your development, the OSGi service approach will feel natural to you. To illustrate, consider the followingGreeting interface : 

  1. package org.foo.hello;  
  2.   
  3. public interface Greeting {  
  4.     void sayHello();  
  5. }  
For any given implementation of the Greeting interface, when the sayHello() method is invoked, a greeting will be displayed. In general, a service represents a contract between a provider and prospective clients; the semantics of the contract are typically described in a separate, human-readable document, like a specification. The previous service interface represents the syntactic contract of all Greeting implementations. The notion of a contract is necessary so that clients can be assured of getting the functionality they expect when using a Greeting service. 

The precise details of how any given Greeting implementation performs its task aren’t known to the client. For example, one implementation may print its greeting textually, whereas another may display its greeting in a GUI dialog box. The following code depicts a simple text-based implementation. 

Listing 1.5 Implementation of the Greeting interface
  1. package org.foo.hello.impl;  
  2.   
  3. import org.foo.hello.Greeting;  
  4.   
  5. public class GreetingImpl implements Greeting {  
  6.     final String m_name;  
  7.     GreetingImpl(String name) {  
  8.         m_name = name;  
  9.     }  
  10.     public void sayHello() {  
  11.         System.out.println("Hello, " + m_name + "!");  
  12.     }  
  13. }  

That’s what makes the OSGi’s service approach so natural if you’re already following an interface-based approach; your code will largely stay the same. Your development will be a little different in two places: how you make a service instance available to the rest of your application, and how the rest of your application discovers the available service. 

All service implementations are ultimately packaged into a bundle, and that bundle must be lifecycle aware in order to register the service. This means you need to create a bundle activator for the example service, as shown next. 

Listing 1.6 OSGi bundle activator with service registration
  1. package org.foo.hello.impl;  
  2.   
  3. import org.foo.hello.Greeting;  
  4. import org.osgi.framework.BundleActivator;  
  5. import org.osgi.framework.BundleContext;  
  6.   
  7. public class Activator implements BundleActivator {  
  8.     public void start(BundleContext ctx) {  
  9.         ctx.registerService(Greeting.class.getName(),  
  10.        new GreetingImpl("service"), null);  
  11.     }  
  12.     public void stop(BundleContext ctx) {}  
  13. }  

This time, in the start() method, instead of storing the Greeting implementation as a singleton, you use the provided bundle context to register it as a service in the service registry. The first parameter you need to provide is the interface name(s), followed by the actual service instance, and finally the service properties. In the stop() method, you could unregister the service implementation before stopping the bundle; but in practice, you don’t need to do this. The OSGi framework automatically unregisters any registered services when a bundle stops. 

You’ve seen how to register a service, but what about discovering a service? The following listing shows a simplistic client that doesn’t handle missing services and that suffers from potential race conditions. We’ll discuss a more robust way to access services in chapter 4. 

Listing 1.7 OSGi bundle activator with service discovery
  1. package org.foo.hello.client;  
  2.   
  3. import org.foo.hello.Greeting;  
  4. import org.osgi.framework.*;  
  5.   
  6. public class Client implements BundleActivator {  
  7.     public void start(BundleContext ctx) {  
  8.         ServiceReference ref =  
  9.         ctx.getServiceReference(Greeting.class.getName());  // (1) Looks up service reference  
  10.         ((Greeting) ctx.getService(ref)).sayHello(); // (2) Retrieves and uses the service  
  11.     }  
  12.     public void stop(BundleContext ctx) {}  
  13. }  

Notice that accessing a service in OSGi is a two-step process. First, an indirect reference is retrieved from the service registry (1). Second, this indirect reference is used to access the service object instance (2). Both the service implementation and the client should be packaged into separate bundle JAR files. The metadata for each bundle declares its corresponding activator, but the service implementation exports the org.foo.hello package, whereas the client imports it. Note that the client bundle’s metadata only needs to declare an import for the Greeting interface package—it has no direct dependency on the service implementation. This makes it easy to swap service implementations dynamically without restarting the client bundle. 

Now that you’ve seen some examples, you can better understand how each layer of the OSGi framework builds on the previous one. Each layer gives you additional capabilities when building your application, but OSGi technology is flexible enough for you to adopt it according to your specific needs. If you only want better modularity in your project, use the module layer. If you want a way to initialize modules and interact with the module layer, use both the module and lifecycle layers. If you want a dynamic, interface-based development approach, use all three layers. The choice is yours. 

Supplement : 
* Karaf Tutorial Part 1 - Installation and First application

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!