程式扎記: [ OSGi In Action ] Introducing OSGi : Delving deeper into modularity (1)

標籤

2011年7月25日 星期一

[ OSGi In Action ] Introducing OSGi : Delving deeper into modularity (1)

Preface : 
This chapter covers 

■ Exploring advanced aspects of exporting packages
■ Importing optional or unknown packages
■ Requiring bundles instead of packages
■ Splitting bundles into fragments
■ Dealing with platform dependencies and native code

In this chapter, we’ll investigate more aspects of OSGi’s module layer. We’ll look into simple features, such as making imported packages a little more flexible, and into more complicated ones, such as splitting Java packages across multiple bundles or breaking a single bundle into pieces. You probably won’t need to use most of the features described in this chapter as often as the preceding ones; if you do, you should review your design, because it may not be sufficiently modular. With that said, it’s worthwhile to be aware of these advanced features of the OSGi module layer and the circumstances under which they’re useful. To assist in this endeavor, we’ll introduce use cases and examples to help you understand when and how to apply them. 

Managing your exports : 
From what you’ve learned so far, exporting a package from a bundle is fairly simple: include it in the Export-Package header, and potentially include some attributes. This doesn’t cover all the details of exporting packages. In the following subsections, we’ll discuss other aspects, like importing exported packages, implicit attributes, mandatory attributes, class filtering, and duplicate exports. 

Importing your exports : 
In chapter 2, you learned how Export-Package exposes internal bundle classes and how Import-Package makes external classes visible to internal bundle classes. This seems to be a nice division of labor between the two. You may even assume the two are mutually exclusive. In other words, you may assume a bundle exporting a given package can’t import it also and vice versa. In many module systems, this would be a reasonable assumption, but for OSGi it’s incorrect. 

Imagine that bundle A wants to use an instance of class javax.servlet.Servlet from bundle B. As you now understand, in their respective metadata, bundle A will import package javax.servlet, and bundle B will export it. Makes sense. Now imagine that bundle C also wants to share an instance of classjavax.servlet.Servlet with bundle A. It has two choices at this point : 

■ Don’t include a copy of package javax.servlet in its bundle JAR file, and import it instead.
■ Include a copy of package javax.servlet in its bundle JAR file, and also export it.

If the approach in option 1 is taken (see figure 5.1), bundle C can’t be used unless bundle B is present, because it has a dependency on packagejavax.servlet and only bundle B provides the package (that is, bundle C isn’t self-contained). 
 
Figure 5.1 If bundle C imports from B, both can share servlet instances with A because there’s only one copy of the Servlet class; but this creates a dependency for C on B. 

On the other hand, if the approach in option 2 is taken (see figure 5.2), bundle C is self-contained, and both B and C can be used independently. But what happens if you want bundle A to interact with the javax.servlet.Servlet objects from bundles B and C at the same time? 

Figure 5.2 If B and C each have a copy of the Servlet class, A can only share Servlet instances with either B or C because it can only see one definition of a class. 
 

The answer is technical, so we’ll only briefly explain it. To use a class, Java must load it into the JVM using a class loader. The identity of a class at execution time is not only associated with its fully qualified class name, it’s also scoped by the class loader that loaded it. The exact same class loaded by two different class loaders is loaded twice by the Java VM and is treated as two different and incompatible types. This means if you try to cast an instance of one to the other, you receive a ClassCast-Exception. Combine this knowledge with the fact that the OSGi specification requires each bundle to have its own class loader for loading its classes, and you begin to understand the issue with the second option we described. 

If bundles B and C both include and export a copy of the javax.servlet package, then there are two copies of the javax.servlet.Servlet class. Bundle A can’t use both instances, because they come from different class loaders and are incompatible. Due to this incompatibility, the OSGi framework only allows bundle A to see one copy, which means A can’t collaborate with both B and C at the same time. 

It’s not important for you to completely understand these arcane details of Java class loaders, especially because OSGi technology tries to relieve you from having to worry about them in the first place. The important point is to understand the issues surrounding the two options: option 1 results in bundle C requiring B to be present, whereas option 2 results in bundle A not being able to see the shared object instances from bundles B and C at the same time. This gets us to the crux of OSGi’s special case for allowing a bundle to import a package it exports. 

Neither of the previous two options is satisfactory. The solution devised by the OSGi specification is to allow a bundle to both import and export the same package (see figure 5.3). In this case, the bundle contains the given package and its associated classes, but it may not end up using its copy. A bundle importing and exporting the same package is offering the OSGi framework a choice; it allows the framework to treat it as either an importer or an exporter of the package, whichever seems appropriate at the time the framework makes the decision. Here’s another way to think about this: it defines a substitutable export, where the framework is free to substitute the bundle’s export with an imported package from another bundle. Returning to the example, both bundles B and C can include a copy of package javax.servlet, with both importing and exporting it, knowing they’ll work independently or together. 
 
Figure 5.3 B and C can both export and import the Servlet package, which makes it possible for the framework to choose to substitute packages so all bundles use a single class definition. 

Admittedly, this may seem odd; but as the discussion here illustrates, to simplify the OSGi vision of a collaborative environment, it’s necessary to make sure bundles use the same class definitions. Up until the OSGi R4 specification, Export-Package implicitly meant Import-Package, too. The R4 specification removed this implicitness, making it a requirement to have a separate Import-Package to get a substitutable export; but this didn’t lessen the importance of doing so in cases where collaboration is desired. An interesting side effect of this is the possibility of metadata like this : 

Export-Package: javax.servlet; version="2.4.0"
Import-Package: javax.servlet; version="2.3.0"

This isn’t a mistake. A bundle may include a copy of a given package at a specific version but may work with a lower range. This can make the bundle useful in a wider range of scenarios, because it can still work in an environment where an older version of the package must be used. 

When to import your exports
Importing and exporting a package is also useful when you’re using an interfacebased development approach. In interface-based programming, which is the foundation of the OSGi service approach, you assume there are potentially multiple implementations of well-known interfaces. You may want to package the interfaces into their implementations to keep them self-contained. In this case, to ensure interoperability, the bundles should import and export the interface packages. Because the whole point of OSGi services is to foster collaboration among bundles, the choice between importing only or exporting and importing interface packages is fairly common.

You do have an alternative approach: to always package your collaborative interface packages into a separate bundle. By never packaging your interfaces in a bundle that provides implementations, you can be sure all implementations can be used together, because all implementations will import the shared packages. If you follow this approach, none of your implementations will be self-contained, because they’ll all have external dependencies on the shared packages. The trade-off is deciding whether you want more bundles with dependencies among them or fewer self-contained bundles with some content overlap. It’s all about balancing coupling and cohesion.

Implicit export attributes : 
Generally speaking, OSGi regards the same package exported by multiple bundles as being completely equivalent if the package versions are the same. This is beneficial when it comes to dependency resolution, because it’s possible for the framework to satisfy an import for a given package from any available matching exporter. In certain situations, you may not wish to have your bundle’s imports satisfied by an arbitrary bundle; instead, you may want to import from a specific bundle. For example, perhaps you patched a bug in a common open source library, and you don’t want to risk using a nonpatched version. OSGi supports this through implicit export attributes. 

Consider the following bundle manifest snippet : 

Bundle-ManifestVersion: 2
Bundle-SymbolicName: my.javax.servlet
Bundle-Version: 1.2.0
Export-Package: javax.servlet; javax.servlet.http; version="2.4.0"

This metadata exports the packages javax.servlet and javax.servlet.http with a version attribute of the specified value. Additionally, the framework implicitly attaches the bundle’s symbolic name and version to all packages exported by a bundle. Therefore, the previous metadata conceptually looks like this (also shown in figure 5.4): 

Bundle-ManifestVersion: 2
Bundle-SymbolicName: my.javax.servlet
Bundle-Version: 1.2.0
Export-Package: javax.servlet; javax.servlet.http; version="2.4.0";

bundle-symbolic-name="my.javax.servlet"; bundle-version="1.2.0"

 
Figure 5.4 a) Your metadata declares explicit attributes that are attached to your bundle’s exported packages, but b) the framework also implicitly attaches attributes explicitly identifying from which bundle the exports come. 

Although this is conceptually what is happening, don’t try to explicitly specify the bundle-symbolic-name and bundle-version attributes on your exports. These attributes can only be specified by the framework; explicitly specifying them results in an installation exception. With these implicit attributes, it’s possible for you limit the framework’s resolution of an imported package to specific bundles. For example, an importing bundle may contain the following snippet of metadata : 

Import-Package: javax.servlet; bundle-symbolic-name="my.javax.servlet";bundle-version="[1.2.0,1.2.0]"

In this case, the importer limits its dependency resolution to a specific bundle by specifying its symbolic name with a precise version range. As you can imagine, this makes the dependency a lot more brittle, but under certain circumstances this may be desired. 

You may be thinking that implicit export attributes aren’t completely necessary to control how import dependencies are resolved. You’re correct. You can also use good old arbitrary attributes to achieve the same effect—just make sure your attribute name and/or value are sufficiently unique. For example, you can modify your exporting manifest like this : 

Bundle-ManifestVersion: 2
Bundle-SymbolicName: javax.servlet
Bundle-Version: 1.2.0
Export-Package: javax.servlet; javax.servlet.http; version="2.4.0"; my-provider-attribute="my.value.scheme"

In this case, the importer needs to specify the corresponding attribute name and value on its Import-Package declaration. There’s an advantage to using this approach if you’re in a situation where you must have brittle dependencies: it’s not as brittle as implicit attributes. You’re able to refactor your exporting bundle without impacting importing bundles, because these attribute values aren’t tied to the containing bundle. On the downside, arbitrary attributes are easier for other bundles to imitate, even though there are no guarantees either way. 

In short, it’s best to avoid brittle dependencies, but at least now you understand how both implicit and arbitrary export attributes allow importing bundles to have a say in how their dependencies are resolved. Thinking about the flip side, it may also occasionally be necessary for exporting bundles to have some control over how importing bundles are resolved. Mandatory attributes can help you here. 

Mandatory export attributes : 
The OSGi framework promotes arbitrary package sharing among bundles. As we discussed in the last subsection, in some situations this isn’t desired. Up until now, the importing bundle appears to be completely in control of this situation, because it declares the matching constraints for dependency resolution. For example, consider the following metadata snippet for importing a package : 

Import-Package: javax.servlet; version="[2.4.0,2.5.0)"

Such an import declaration matches any provider of javax.servlet as long as it’s in the specified version range. Now consider the following metadata snippet for exporting a package in another bundle : 

Export-Package: javax.servlet; version="2.4.1"; private="true"

Will the imported package match this exported package? Yes, it will, as shown in figure 5.5. The name of the attribute, private, may have tricked you into thinking otherwise, but it’s just an arbitrary attribute and has no meaning (if it did have meaning to the framework, it would likely be a directive, not an attribute). When it comes to matching an import to an export, only the attributes mentioned on the import declaration are compared against the attributes on the export declaration. In this case, the import mentions the package name and version range, which match the exported package’s name and version. The private attribute isn’t even considered. 
 
Figure 5.5 Only attributes mentioned in the imported package declaration impact dependency resolution matching. Any attributes mentioned only in the exported package declaration are ignored. 

In some situations, you may wish to have a little more control in your exporting bundle. For example, maybe you’re exposing a package containing a nonpublic API, or you’ve modified a common open source library in an incompatible way, and you don’t want unaware bundles to inadvertently match your exported packages. The OSGi specification provides this capability using mandatory export attributes, which you declare using the mandatory export package directive of the Export-Package manifest header. 

MANDATORY DIRECTIVE The mandatory export package directive specifies a comma-delimited list of attribute names that any importer must match in order to be wired to the exported package.

To see how mandatory attributes work, let’s modify the previous snippet to export its package, like this : 

Export-Package: javax.servlet; version="2.4.1"; private="true"; mandatory:="private"

You add the mandatory directive to the exported package to declare the private attribute as mandatory. Any export attribute declared as mandatory places a constraint on importers. If the importers don’t specify a matching value for the attribute, then they don’t match. The export attribute can’t be ignored, as shown in figure 5.6. The need for mandatory attributes doesn’t arise often; you’ll see some other use cases in the coming sections. Until then, let’s look into another more fine-grained mechanism that bundles can use to control what is exposed from their exported packages. 
 
Figure 5.6 If an export attribute is declared as mandatory, importing bundles must declare the attribute and matching value; otherwise, it won’t match when the framework resolves dependencies. 

Export filtering : 
In chapter 1, we discussed the limitations of Java’s rules for code visibility. There’s no way to declare module visibility, so you must use public visibility for classes accessed across packages. This isn’t necessarily problematic if you can keep your public and private APIs in separate packages, because bundles have the ability to hide packages by not exporting them. Unfortunately, this isn’t always possible, and in some cases you end up with a public implementation class inside a package exported by the bundle which contains private APIs. To cope with this situation, OSGi provides include and excludeexport filtering directives for the Export-Package manifest header to enable fine-grained control over the classes exposed from your bundle’s exported packages. 

EXCLUDE/INCLUDE DIRECTIVES The exclude and include export package directives specify a comma-delimited list of class names to exclude or include from the exported package, respectively.

To see how you can use these directives, consider a hypothetical bundle containing a package (org.foo.service) with a service interface (public class Service), an implementation of the service (package private class ServiceImpl), and a utility class (public class Util). In this hypothetical example, the utility class is part of the private API. It’s included in this package due to dependencies on the service implementation and is public because it’s used by other packages in the bundle. You need to export the 
org.foo.service package, but you don’t want to expose the Util class. In general, you should avoid such scenarios, but the following metadata snippet illustrates how you cando this with export filtering : 

Export-Package: org.foo.service; version="1.0.0"; exclude:="Util"

This exported package behaves like any normal exported package as far as dependency resolution is concerned; but at execution time, the framework filters the Util class from the package so importers can’t access it, as shown in figure 5.7. A bundle attempting to load the Util class receives a "class not found" exception. The value of the directive specifies only class names; the package name must not be specified, nor should the .class portion of the class file name. The * character is also supported as a wildcard, so it’s possible to exclude all classes with names matching *Impl, for example. 
 

In some cases, it may be easier to specify which classes are allowed instead of which ones are disallowed. For those situations, the include directive is available. It specifies which classes the exported package should expose. The default value for the include directive is *, and the default value for the excludedirective is an empty string. You can also specify both the include and exclude directive for a given exported package. A class is visible only if it’s matched by an entry in the include directive and not matched by any entry in the exclude directive. 

You should definitely strive to separate your public and private APIs into different packages so these mechanisms aren’t needed, but they’re here to get you out of a tough spot when you need them. Next, we’ll move on to another mechanism to help you manage your APIs. 

Duplicate exports : 
A given bundle can see only one version of a given class while it executes. In view of this, it’s not surprising to learn that bundles aren’t allowed to import the same package more than once. What you may find surprising is that OSGi allows a bundle to export the same package more than once. For example, the following snippet of metadata is perfectly valid : 

Export-Package: javax.servlet; version="2.3.0", javax.servlet; version="2.4.0"

How is it possible, you ask? The trick is that the bundle doesn’t contain two separate sets of classes for the two exported packages. The bundle is masquerading the same set of classes as different packages. Why would it do this? Expounding on the previous snippet, perhaps you have unmodifiable third-party bundles with explicit dependencies on javax.servlet version 2.3.0 in your application. Because version 2.4.0 is backward compatible with version 2.3.0, you can use duplicate exports to allow your bundle to stake a backward compatibility claim. In the end, all bundles requiring either version ofjavax.servlet can resolve, but they’ll all use the same set of classes at execution time, as shown in figure 5.8 : 
 

As with export filtering, this is another mechanism to manage your APIs. You can take this further and combine it with some of the other mechanisms you’ve learned about in this section for additional API management techniques. For example, you generally don’t want to expose your bundle’s implementation details to everyone, but sometimes you want to expose implementation details to select bundles; this is similar to the friend concept in C++. A friend is allowed to see implementation details, but nonfriends aren’t. To achieve something like this in OSGi, you need to combine mandatory export attributes, export filtering, and duplicate exports. 

To illustrate, let’s return to the example from export filtering : 

Export-Package: org.foo.service; version="1.0.0"; exclude:="Util"

In this example, you excluded the Util class, because it was an implementation detail. This is the exported package your nonfriends should use. For friends, you need to export the package without filtering : 

Export-Package: org.foo.service; version="1.0.0"; exclude:="Util", org.foo.service; version="1.0.0"

Now you have one export hiding the Util class and one exposing it. How do you control who gets access to what? That’s right: mandatory export attributes. The following complete export metadata gives you what you need : 

Export-Package: org.foo.service; version="1.0.0"; exclude:="Util", org.foo.service; version="1.0.0"; friend="true"; mandatory:="friend"

Only bundles that explicitly import your package with the friend attribute and matching value see the Util class. Clearly, this isn’t a strong sense of friendship, because any bundle can specify the friend attribute; but at least it requires an opt-in strategy for using implementation details. Best practice dictates avoiding the friendship concept, because it weakens modularity. If an API is valuable enough to export, you should consider making it a public API. 

Loosening your imports : 
Explicitly declared imports are great, because explicit requirements allow you to more easily reuse code and automate dependency resolution. This gives you the benefit of being able to detect misconfigurations early rather than receiving various class-loading and class-cast exceptions at execution time. On the other hand, explicitly declared imports are somewhat constraining, because the framework uses them to strictly control whether your code can be used; if an imported package can’t be satisfied, the framework doesn’t allow the associated bundle to transition into the RESOLVED state. Additionally, to import a package, you must know the name of a package in advance, but this isn’t always possible. 

What can you do in these situations? The OSGi framework provides two different mechanisms for dealing with such situations: optional and dynamic imports. Let’s look into how each of these can help, as well as compare and contrast them. 

Optional imports : 
Sometimes a given package imported by a bundle isn’t strictly necessary for it to function properly. Consider an imported package for a nonfunctional purpose, like logging. The code in a given bundle may have been programmed to function properly regardless of whether a logging facility is available. To express this, OSGi provides the resolution directive for the Import-Package manifest header to mark imported packages as optional. 

RESOLUTION DIRECTIVE The resolution import package directive can have a value of mandatory or optional to indicate whether the imported package is required to successfully resolve the bundle.

Consider the following metadata snippet : 

Import-Package: javax.servlet; version="2.4.0", org.osgi.service.log; version="1.3.0"; resolution:="optional"

This import statement declares dependencies on two packages, javax.servlet and org.osgi.service.log. The dependency on the logging package is optional, as indicated by the use of the resolution directive with the optional value. This means the bundle can be successfully resolved even if there isn’t anorg.osgi.service.log package available. Attempts by the bundle to use classes from this package at execution time result in ClassNotFoundExceptions. All imported packages have a resolution associated with them, but the default value is mandatory. We’ll look at how this is used in practice in section 5.2.4, but for now let’s consider the other tool in the box: dynamic imports. 

Dynamic imports : 
Certain Java programming practices make it difficult to know all the packages that a bundle may need at execution time. A prime example is locating a JDBC driver. Often the name of the class implementing the JDBC driver is a configuration property or is supplied by the user at execution time. Because your bundle can only see classes in packages it imports, how can it import an unknown package? This sort of situation arises when you deal with service provider interface (SPI) approaches, where a common interface is known in advance, but not the name of the class implementing it. To capture this, OSGi has a separate DynamicImport-Package manifest header. 

DYNAMICIMPORT-PACKAGE This header is a comma-separated list of packages needed at execution time by internal bundle code from other bundles, but not needed at resolve time. Because the package name may be unknown, you can use a * wildcard (matching all packages) or a trailing .* (matching subpackages recursively).

In the most general sense, a dynamic import is expressed in the bundle metadata like this : 

DynamicImport-Package: *

This snippet dynamically imports any package needed by the bundle. When you use the wildcard at the end of a package name (for example, org.foo.*), it matches all subpackages recursively but doesn’t match the specified root package. Given the open-ended nature of dynamic imports, it’s important to understand precisely when in the class search order of a bundle they’re employed. They appear in the class search order as follows : 

1. Requests for classes in java. packages are delegated to the parent class loader; searching stops with either a success or failure (section 2.5.4).
2. Requests for classes in an imported package are delegated to the exporting bundle; searching stops with either a success or failure (section 2.5.4).
3. The bundle class path is searched for the class; searching stops if found but continues to the next step with a failure (section 2.5.4).
4. If the package in question isn’t exported by the bundle, requests matching any dynamically imported package are delegated to an exporting bundle if one is found; searching stops with either a success or failure.

As you can see, dynamic imports are attempted only as a last resort. But when a dynamically imported package is resolved and associated with the importing bundle, it behaves just like a statically imported package. Future requests for classes in the same package are serviced in step 2. 

You can also use dynamically imported packages in a fashion more similar to optionally imported packages by specifying additional attributes like normal imported packages : 

DynamicImport-Package: javax.servlet.*; version="2.4.0"

or even : 

DynamicImport-Package: javax.servlet; javax.servlet.http; version="2.4.0"

In the first case, all subpackages of javax.servlet of version 2.4.0 are dynamically imported, whereas in the second only the explicitly mentioned packages are dynamically imported. More precise declarations such as these often make less sense when you’re using dynamic imports, because the general use case is for unknown package names. 

We apparently have two different ways to loosen bundle imports. Let’s compare and contrast them a little more closely. 

Optional vs. dynamic imports : 
There are intended use cases for both optional and dynamic imports, but the functionality they provide overlaps. To better understand each, we’ll look into their similarities and differences. Let’s start with the similarities. 

- SIMILARITIES 
Both are used to express dependencies on packages that may or may not be available at execution time. Although this is the specific use case for optional imports, it’s a byproduct of dynamic imports. Either way, this has the following impact : 

■ Optional/dynamic imports never cause a bundle to be unable to resolve.
■ Your bundle code must be prepared to catch ClassNotFoundExceptions for the optionally/dynamically imported packages.

Only normally imported packages (that is, mandatory imported packages) impact bundle dependency resolution. If a mandatory imported package can’t be satisfied, the bundle can’t be resolved or used. Neither optionally nor dynamically imported packages are required to be present when resolving dependencies. For optional imports, this is the whole point: they’re optional. For dynamic imports, they aren’t necessarily optional; but because they aren’t known in advance, it’s not possible for the framework to enforce that they exist. 

Because the packages may not exist in either case, the logical consequence is that the code in any bundle employing either mechanism must be prepared to catch ClassNotFoundExceptions when attempting to access classes in the optionally or dynamically imported packages. This is typically the sort of issue the OSGi framework tries to help you avoid with explicit dependencies; we shouldn’t be dealing with classloading issues as developers. 

- DIFFERENCES 
By now, you must be wondering what the difference is between optional and dynamic imports. It has to do with when the framework tries to resolve the dependencies. 

The framework attempts to resolve an optionally imported package once when the associated bundle is resolved. If the import is satisfied, the bundle has access to the package. If not, the bundle doesn’t and will never have access to the package unless it’s re-resolved. For a dynamically imported package, the framework attempts to resolve it at execution time when the bundle’s executing code tries to use a class from the package. 

Further, the framework keeps trying to resolve the dynamically imported package each time the bundle’s executing code tries to use classes from it until it’s successfully resolved. If a bundle providing the dynamically imported package is ever deployed into the executing framework, the framework eventually will be able to resolve it. After the resolve is successful, the bundle is wired to the provider of the package; it behaves like a normal import from that point forward. 

Logging example : 
The OSGi specification defines a simple logging service that you may want to use in your bundles, but you can’t be certain it will always be available. One way to deal with this uncertainty is to create a simple proxy logger that uses the logging service if available or prints to standard output if not. 

Our first example uses an optional import for the org.osgi.service.log package. The simple proxy logger code is shown here. 

- Listing 5.1 Simple proxy logger using optional import
  1. public class Logger {  
  2.     private final BundleContext m_context;  
  3.     private final ServiceTracker m_tracker;  
  4.   
  5.     public Logger(BundleContext context) {  
  6.         m_context = context;  
  7.         m_tracker = init(m_context);  
  8.     }  
  9.   
  10.     private ServiceTracker init(BundleContext context) {  
  11.         ServiceTracker tracker = null;  
  12.         try {  
  13.             // (1) Create ServiceTracer  
  14.             tracker = new ServiceTracker(context,  
  15.                     org.osgi.service.log.LogService.class.getName(), null);  
  16.             tracker.open();  
  17.         } catch (NoClassDefFoundError error) {  
  18.         }  
  19.         return tracker;  
  20.     }  
  21.   
  22.     public void close() {  
  23.         if (m_tracker != null) {  
  24.             m_tracker.close();  
  25.         }  
  26.     }  
  27.   
  28.     public void log(int level, String msg) {  
  29.         boolean logged = false;  
  30.         if (m_tracker != null) { // (2) Check for valid tracker  
  31.             LogService logger = (LogService) m_tracker.getService();  
  32.             if (logger != null) { // (3) Check for log service  
  33.                 logger.log(level, msg);  
  34.                 logged = true;  
  35.             }  
  36.         }  
  37.         if (!logged) {  
  38.             System.out.println("[" + level + "] " + msg);  
  39.         }  
  40.     }  
  41. }  

The proxy logger has a constructor that takes the BundleContext object to track log services, an init() method to create a ServiceTracker for log services, aclose() method to stop tracking log services, and a log() method for logging messages. Looking more closely at the init() method, you try to use the logging package to create a ServiceTracker (1). Because you’re optionally importing the logging package, you surround it in a try-catch block. If an exception is thrown, you set your tracker to null; otherwise, you end up with a valid tracker. When a message is logged, you check if you have a valid tracker (2). If so, you try to log to a log service. Even if you have a valid tracker, that doesn’t mean you have a log service, which is why you verify it (3). If you have a log service, you use it; otherwise, you log to standard output. The important point is that you attempt to probe for the log package only once, with a single call to init() from the constructor, because an optional import will never be satisfied later if it’s not satisfied already. 

The bundle activator is shown in the following listing : 

- Listing 5.2 Bundle activator creating the proxy logger
  1. public class Activator implements BundleActivator {  
  2.     private volatile Logger m_logger = null;  
  3.   
  4.     public void start(BundleContext context) throws Exception {  
  5.         m_logger = new Logger(context);  
  6.         m_logger.log(4"Started");  
  7.         // …  
  8.     }  
  9.   
  10.     public void stop(BundleContext context) {  
  11.         m_logger.close();  
  12.     }  
  13. }  

When the bundle is started, you create an instance of your proxy logger that’s used throughout the bundle for logging. Although not shown here, the bundle passes a reference or somehow provides access to the logger instance to any internal code needing a logger at execution time. When the bundle is stopped, you invoke close() on the proxy logger, which stops its internal service tracker, if necessary. The manifest for your logging bundle is : 

Bundle-ManifestVersion: 2
Bundle-SymbolicName: example.logger
Bundle-Activator: example.logger.Activator
Import-Package: org.osgi.framework, org.osgi.util.tracker,
 org.osgi.service.log; resolution:=optional

How would this example change if you wanted to treat the logging package as a dynamic import? The impact to the Logger class is as follows : 

- Listing 5.3 Simple proxy logger using dynamic import
  1. public class Logger {  
  2.     private final BundleContext m_context;  
  3.     private ServiceTracker m_tracker;  
  4.   
  5.     public LoggerImpl(BundleContext context) {  
  6.         m_context = context;  
  7.     }  
  8.   
  9.     private ServiceTracker init(BundleContext context) {  
  10.         ServiceTracker tracker = null;  
  11.         try {  
  12.             tracker = new ServiceTracker(context,  
  13.                     org.osgi.service.log.LogService.class.getName(), null);  
  14.             tracker.open();  
  15.         } catch (NoClassDefFoundError error) {  
  16.         }  
  17.         return tracker;  
  18.     }  
  19.   
  20.     public synchronized void close() {  
  21.         if (m_tracker != null) {  
  22.             m_tracker.close();  
  23.         }  
  24.     }  
  25.   
  26.     public synchronized void log(int level, String msg) {  
  27.         boolean logged = false;  
  28.         if (m_tracker == null) {  
  29.             m_tracker = init(m_context);  
  30.         }  
  31.         if (m_tracker != null) {  
  32.             LogService logger = (LogService) m_tracker.getService();  
  33.             if (logger != null) {  
  34.                 logger.log(level, msg);  
  35.                 logged = true;  
  36.             }  
  37.         }  
  38.         if (!logged) {  
  39.             System.out.println("[" + level + "] " + msg);  
  40.         }  
  41.     }  
  42. }  

You can no longer make your ServiceTracker member variable final, because you don’t know when it will be created. To make your proxy logger thread safe and avoid creating more than one ServiceTracker instance, you need to synchronize your entry methods (functions close() and log()). Because the logging package can appear at any time during execution, you try to create the ServiceTracker instance each time you log a message until successful. As before, if all else fails, you log to standard output. The manifest metadata is pretty much the same as before, except you use DynamicImport-Package to import the logging package : 

Bundle-ManifestVersion: 2
Bundle-SymbolicName: example.logger
Bundle-Activator: example.logger.Activator
Import-Package: org.osgi.framework, org.osgi.util.tracker

DynamicImport-Package: org.osgi.service.log

These two examples illustrate the differences between these two mechanisms. As you can see, if you plan to take advantage of the full, dynamic nature of dynamically imported packages, there’s added complexity with respect to threading and concurrency. There’s also potential overhead associated with dynamic imports, not only because of the synchronization, but also because it can be costly for the framework to try to find a matching package at execution time. For logging, which happens frequently, this cost can be great. 
 

沒有留言:

張貼留言

網誌存檔

關於我自己

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