Chapter 6. Lookup: Let's Talk to Other Modules!

Lookup is a concept that is as important as it is simple. Used in many places within NetBeans Platform applications, it allows modules to communicate with each other. This chapter shows how the concept works.

Functionality

Lookup is a central component and a commonly used concept in the NetBeans Platform for the management of object instances. Simplified, Lookup is a Map, with Class objects as keys and instances of those Class objects as values.

The main idea behind Lookup is decoupling components. It lets modules communicate with each other, which plays an important role in component-based systems, such as applications based on the NetBeans Platform. Modules provide objects via Lookup, as well as searching for or using objects.

The advantage of Lookup is its type safety, achieved by using Class objects instead of strings as keys. With this, the key defines the type of retrieved instance. So, it is impossible to request an instance whose type is unknown in the module. This pattern results in a more robust application, since errors like ClassCastException do not occur. Lookup is also used to retrieve and manage multiple instances for one key—i.e., of one type. This central management of specific instances is used for different purposes. Lookup is used to discover service providers, for which declarative adding and lazy-loading of instances is supported. In addition to this, you may pass instances via Lookup from one module to another, without the modules knowing each other. Intermodule communication is established. Even context-sensitive actions can be realized using the Lookup component.

To clear a common misconception, within a single application it's possible to have more than one Lookup. The most commonly used Lookup is global, provided by default in the NetBeans Platform. In addition, there are components, such as TopComponent, that have their own Lookup. These are local Lookups. As described in the "Intermodule Communication" section, it is possible to create your own Lookups and equip your components with a Lookup.

The Lookup concept is simple, efficient, and convenient. Once familiar with this pattern, it applies to many different areas. In the following sections, the usage of Lookup in its main use cases is shown.

Services and Extension Points

A main application of Lookup is the discovery and provision of services. The role of Lookup in this scenario is a function of a dynamic service locator, allowing separation of the service interface and the service provider. A module makes use of functionality without knowing anything about implementation. With this, loose coupling is achieved between modules.

By means of Lookup and a service interface, it is simple to define extension points for graphic components. A good example is the NetBeans status bar, defining the interface StatusLineElementProvider. With this interface and a service provider registration, the status bar is extended with user-defined components (an example for this is described in the "Status Bar" section of Chapter 5), without the status bar knowing about or having a dependency on those components.

For a dynamic and flexible provision and exchange of services, these are added declaratively to the Lookup, rather than programmed in the source code. This is achieved by either of two methods: adding the implementation of a service using a Service Provider Configuration file in the META-INF/services directory, or using the layer file of your module. Both are shown in the "Registering Service Providers" section later in the chapter.

The NetBeans Platform provides a global Lookup, which is retrieved using the static method Lookup.getDefault(). This global Lookup is used to discover services, added by using one of the available declarative registrations. Use this approach to register more than one implementation for a single service. The declarative registration allows instantiation of implementations on the first request. This pattern is known as lazy-loading.

To achieve a better understanding of this pattern for providing and requesting services, and to get a more practical perspective, we illustrate the creation of a search list for an MP3 file.

Defining the Service Interface

Module A is a module providing a user interface allowing the user to search for MP3 files by special search criteria. The search results are shown in a list. To remain independent of the search algorithm and ensure the dynamic use of multiple search variants (which may be switched at runtime), we specify the service interface Mp3Finder in module A. This service defines the search interface for MP3 files. The actual search algorithm is implemented in a separate module, B, provided via declarative registration.

Loose Service Provisioning

Module B is a service provider for implementation of the interface Mp3Finder. In this example, assume the module is searching for MP3 files in a database. This allows multiple implementations of the service provider to be registered. All implementations can be in either one or separate modules. To create an Mp3DatabaseFinder implementation of the interface Mp3Finder from module A, module B must define a dependency on module A. However, module A, the search list user interface, needs no dependency on module B. This is because Lookup provides the service based on the interface (living in module A as well) rather than the implementation (residing in module B). Thus, module A is completely independent of the implementation of the service (see Figure 6-1) and can use it transparently.

Service Lookup pattern

Figure 6.1. Service Lookup pattern

In module A, the service interface Mp3Finder is specified and a user interface is implemented for search and display of MP3 files (see Listing 6-1). A service provider is retrieved by passing the Class object of the interface Mp3Finder to Lookup, returning an instance matching the requested type. The interface Mp3Finder is also known as an extension point of module A. Any module can register implementations for it.

Example 6.1. Module A: MP3 searcher

public interface Mp3Finder {
   public List<Mp3FileObject> find(String search);
}
public class Mp3SearchList {
   public void doSearch(String search) {
      Mp3Finder finder =
         Lookup.getDefault().lookup(Mp3Finder.class);
      List<Mp3FileObject> list = finder.find(search);
   }
}

Module B provides a service provider allowing the search of a database for MP3 files. This is done by implementing the interface Mp3Finder, specified by module A (see Listing 6-2). So, module B is an extension of module A at the extension point Mp3Finder.

Example 6.2. Module B: MP3 finder

public class Mp3DatabaseFinder implements Mp3Finder {
   public List<Mp3FileObject> find(String search) {
      // search in database for mp3 files
   }
}

The newly created service provider must be registered, so it can be discovered with Lookup. This is done via a new file in the META-INF/services directory, named after the interface (com.galileo.netbeans.modulea.Mp3Finder) and using its fully qualified name. To associate an implementation with the interface, add a line to the file containing the fully qualified name of the implementation:

com.galileo.netbeans.moduleb.Mp3DatabaseFinder

Providing Multiple Service Implementations

It is useful to be able to register multiple MP3 search implementations. This is easy. Simply create further implementations of the interface Mp3Finder —for example

public class Mp3FilesystemFinder implements Mp3Finder {
   public List<Mp3FileObject> find(String search) {
      // search in local filesystem for mp3 files
   }
}

The name of this service provider is added to the previously created Service Provider Configuration file in META-INF/services:

com.galileo.netbeans.moduleb.Mp3FilesystemFinder

To use all registered implementations of a service, discovery of the services using Lookup must be adopted. Rather than using the lookup() method to retrieve a single implementation, use lookupAll() to retrieve all registered implementations of the service. Call the find() method of all discovered services as follows:

public class Mp3SearchList {
   public void doSearch(String search) {
      Collection<? extends Mp3Finder> finder =
         Lookup.getDefault().lookupAll(Mp3Finder.class);
      List<Mp3FileObject> list = new ArrayList<Mp3FileObject>();
      for(Mp3Finder f : finder) {
         list.addAll(f.find(search));
      }
   }
}

Ensuring Service Availability

A search module is of no use to the user if no search service is available allowing a search for MP3 files. To enable module A, ensuring that at least one implementation of a service is available, the NetBeans module system provides two attributes: OpenIDE-Module-Provides and OpenIDE-Module-Requires, which allow definition in the manifest file of a module if a special service implementation is provided or required. These and further attributes of the manifest file are described in more detail in the "Module Manifest" section of Chapter 3.

Within the manifest file of module A, the existence of at least one provider of the Mp3Finder service is required, with the following entry:

OpenIDE-Module-Requires: com.galileo.netbeans.modulea.Mp3Finder

To inform the module system during loading of the modules that module B provides the service Mp3Finder, add the following entry to the manifest file of module B:

OpenIDE-Module-Provides: com.galileo.netbeans.modulea.Mp3Finder

If no module declares such an entry in its manifest file (i.e., there is no service provider available), the module system announces an error and does not load module A.

Global Services

Global services—i.e., services that can be used by multiple modules and that are only provided by one module—are typically implemented using abstract (singleton) classes. With this pattern, the services manage the implementation on their own and provide an additional trivial implementation (as an inner class) in case there is no other implementation registered in the system. This has the advantage that the user always gets a valid reference to a service and never a null value.

An example would be an MP3 player service (see Listing 6-3) used by different modules—e.g., a search list or playlist. The implementation of the player is exchangeable.

Example 6.3. MP3 player as a global service in the MP3 Services module

public abstract class Mp3Player {
   public abstract void play(Mp3FileObject mp3);
   public abstract void stop();
   public static Mp3Player getDefault() {
      Mp3Player player = Lookup.getDefault().lookup(Mp3Player.class);
      if(player == null) {
         player = new DefaultMp3Player();
      }
      return(player);
   }
private static class DefaultMp3Player extends Mp3Player {
      public void play(Mp3FileObject mp3) {
         // send file to an external player or
         // provide own player implementation or
         // show a message that no player is available
      }
      public void stop() {}
   }
}

This service, implemented as an abstract class, specifies its interface via the abstract methods, and at the same time provides access to the service via the static method getDefault(). The advantage of this pattern is that there is no need for users of the service to know anything about the Lookup API. This keeps the application logic lean, as well as independent from Lookup API.

The abstract class should normally be part of a module, which is, in turn, part of the standard distribution of the application (in the example, this would be the MP3 Services module). The service provider (i.e., the classes that contain the real code for playing MP3 files) can be encapsulated in a separate module (see Listing 6-4). In the example, this is the class MyMp3Player, for which we subsequently create a skeleton and add it to module C.

Example 6.4. MP3 player service provider in the MP3 Player module

public class MyMp3Player extends Mp3Player {
   public void play(Mp3FileObject mp3) {
      // play file
   }
   public void stop() {
      // stop player
   }
}

Now the MyMp3Player service provider must be registered. This is done—as shown in the previous section—via a Service Provider Configuration file in the name com.galileo. netbeans.mp3services.Mp3Player in the META-INF/services directory (see the "Registering Service Providers" section) with the following content:

com.galileo.netbeans.mp3player.MyMp3Player

The relationships and dependencies of the modules are shown in Figure 6-2.

Dependencies and relationships of global service, service provider, and application module

Figure 6.2. Dependencies and relationships of global service, service provider, and application module

Good examples for global services inside the NetBeans Platform are StatusDisplayer and IOProvider. The class IOProvider grants access to the Output window. The service provider actually writing the data to the Output window is in a separate class, NbIOProvider, in a separate module. If the module is available and the service provider registered, its implementation is retrieved via the static method IOProvider.getDefault(). If the module is not available, the default implementation is provided, which writes the output data to the default output (System.out and System.err).

Registering Service Providers

To allow a dynamic and flexible registration of service providers, even after delivering the application, and to ensure those are loaded only if needed, the registration is done declaratively, using configuration files.

Services available inside a NetBeans Platform-based application and accessible via Lookup, and can be registered using two different mechanisms. Both will be shown in detail in the following sections.

Service Provider Configuration File

The preferred method of registration of service providers is using a Service Provider Configuration file. This approach is part of the Java JAR File Specification. A file is named after its service and lists in its content all service providers. The file must be placed in the META-INF/services directory, which is part of the src/ directory of a module, or must be part of the classpath of a module.

src/META-INF/services/com.galileo.netbeans.module.Mp3Finder
com.galileo.netbeans.module.Mp3DatabaseFinder
com.galileo.netbeans.module.Mp3FilesystemFinder

In this example, two service providers are registered for the service (i.e., the interface of abstract class, Mp3Finder). The provided services and providers are discovered using the META-INF services browser of the NetBeans IDE. This feature is part of the project view for NetBeans module projects. The information is shown under Important Files

Service Provider Configuration File

The global Lookup (i.e., the standard Lookup) discovers the services in the META-INF/services directory and instantiates the providers. A successful service instantiation requires that each service provider have a default constructor so that creation from Lookup is possible.

Based on the original specification of the Service Provider Configuration file, the NetBeans Platform provides two add-ons, allowing the removal of existing service providers or changing the order of the registered providers. To make these additions comply with the original Java specification, the add-ons are prefixed with the comment sign (#). So, these lines are ignored by JDK implementations.

Removal of a Service Provider

It is possible to remove a service provider registered within another module. This feature can be used to substitute the standard implementation of a service of the NetBeans Platform with another implementation.

A service provider is removed by adding the following entry in your Service Provider Configuration file. At the same time, you can provide your own implementation.

# remove the other implementation (by prefixing the line with #-)
#-org.netbeans.core.ServiceImpl
# provide my own
com.galileo.netbeans.module.MyServiceImpl

Order of Service Providers

The order in which service providers are returned from Lookup is controlled using a position attribute for each provider entry.

For example, this is necessary to control the order of additional entries in the status bar (see the "Status Bar" section in Chapter 5) or to ensure that your own implementation is called before the NetBeans Platform implementation. Optionally, you can specify a negative value for the position attribute. The NetBeans Platform orders instances by ascending positions, so that instances with smaller numbers are returned before instances with larger numbers. For that purpose, the following entry is added to the Service Provider Configuration file:

com.galileo.netbeans.module.MyServiceImpl
#position=20
com.galileo.netbeans.module.MyImportantServiceImpl
#position=10

We recommend assigning position values in larger intervals, as shown in the example. This simplifies adding further implementations later on.

Services Folder

Another way to provide a service implementation is registration using the Services folder in the module layer file, as shown in Listing 6-5.

Example 6.5. Registration of service providers in a layer file

<folder name="Services">
  <folder name="Mp3Services">
    <file name="com-galileo-netbeans-module-Mp3DatabaseFinder.instance">
      <attr name="instanceOf" stringvalue="com.galileo.netbeans.module.Mp3Finder"/>
    </file>
  </folder>
</folder>

If a service is requested using the default Lookup, implementations are discovered by searching the Services folder and its subdirectories for instances, which can be assigned to the requested service interface. So, services can be grouped using arbitrary folders, as shown with the folder Mp3Services in our example.

In contrast to the registration using the Service Provider Configuration file, the service provider need not provide a default constructor if registered in the layer file. With the layer file, specifying a static method in the instanceCreate attribute is possible, creating an instance of the service provider. Let's assume the already created provider Mp3DatabaseFinder has a static method getDefault() that returns the instance. The declaration can be changed by adding the following attribute:

<attr name="instanceCreate"
        methodvalue="com.galileo.netbeans.module.Mp3DatabaseFinder.getDefault"/>

With this attribute declaration, the service provider instance is not created using the default constructor, but rather by calling the static method getDefault() (more detailed information regarding this attribute and the corresponding .instance files are described in Chapter 3).

Also, using the registration via the Services folder allows removing existing service providers and controlling the order of the providers. Both mechanisms are achieved using default features of the layer file. A service provider can be removed by adding the suffix _hidden to its name, as is done for menu entries (see the "Menu Bar" section in Chapter 5).

<file name="com-galileo-netbeans-module-ServImp.instance_hidden">

The order in which service providers are returned is controlled using the position attribute, which is the same strategy as used for other entries in the layer file (see Chapter 3).

<folder name="Services">
   <file name="com-galileo-netbeans-module-ServImp.instance">
      <attr name="position" intvalue="10"/>
   </file>
   <file name="com-galileo-netbeans-module-ServImp2.instance">
      <attr name="position" intvalue="20"/>
   </file>
</folder>

In this example, the position attributes ensure that the service provider ServImp will be returned before ServImp2.

Intermodule Communication

Beside the global Lookup, which is provided by the NetBeans Platform and allows access to all registered services, a local Lookup to your own components may be added. The Lookup API offers a factory to create Lookups and an opportunity to listen to changes in Lookups. Using the class ProxyLookup, a user can create a proxy combining multiple Lookups into one. Using this feature of the Lookup API and SPI, we create communication between components of different modules without making them interdependent.

A typical use case for the communication of loosely coupled modules is the visualization of detailed information for a selected object. The selection of objects and visualization of information is done in separate modules. As an example, imagine a list displaying the search results for MP3 files. Selecting an entry in the list provides the selected entry via Lookup, so other parts of the application can access the entry and display the required detailed information. This pattern is similar to the observer pattern. The module providing the objects—in this case the search list—is the subject, and the information display module is the observer. This allows multiple modules to display the data or detailed information in various ways. Again, the advantage is loose coupling of the modules: they are completely independent of each other. The only thing they have in common is the provided object (or to be more precise its interface), which is the source of the information to be processed. This loose coupling is achieved by using a proxy object, which acts as a substitute for the subject in the registration process of the observer. So, the observer is registered with the proxy component (in our case the Lookup), not the subject.

Figure 6-3 shows the example implemented in the following paragraphs. Both windows are in a separate module, each independent of the other (i.e., both can be exchanged or new ones can be added arbitrarily).

Typical application example of a data exchange between two modules, without interdependency

Figure 6.3. Typical application example of a data exchange between two modules, without interdependency

The structure of this concept is shown in Figure 6-4. The class Mp3SearchList in module A represents a list of search results. A search result entry is represented by the class Mp3FileObject, residing in a separate module, since this class is the most common denominator of all modules. If an entry is selected in the list, the Mp3FileObject instance is added to the local Lookup. A moderator (i.e., a proxy component depicted as the interface ContextGlobalProvider) is needed to decouple modules A and B. This proxy component provides the local Lookup of module A to module B, which contains the currently selected instance. To enable the centralized proxy component to access the local Lookup of the class Mp3SearchList, the Lookup API provides the interface Lookup.Provider. This interface must be implemented from the class Mp3SearchList.

Using the method getLookup(), the local Lookup can be obtained. The Lookup.Provider interface is already implemented by the class TopComponent, which is the superclass of all visible NetBeans window system components, as well as the Mp3SearchList. The NetBeans window system already provides an instance of the central proxy component, the class GlobalActionContextImpl. This class provides a proxy Lookup, which accesses the local Lookup of the focused TopComponent. This Lookup can be obtained by calling the static utility method Utilities. actionsGlobalContext(). So, there is no need to create our own ContextGlobalProvider instance, but we already have access to the global proxy Lookup. If you are interested in more details and want to know more about this concept, it may be worthwhile to investigate the sources for the mentioned classes and methods.

Structure of the intermodule communication concept using a local Lookup via a proxy component to decouple subject and observer

Figure 6.4. Structure of the intermodule communication concept using a local Lookup via a proxy component to decouple subject and observer

The class Mp3DetailsView gains access to the local Lookup of the Mp3SearchList by calling Utilities.actionsGlobalContext(). Based on the global Proxy Lookup, we create a Lookup. Result for the class Mp3FileObject. An instance of the class Lookup.Result provides a subset of a Lookup for a special class. The main advantage is that the user can listen for changes in this subset by using a LookupListener. So, the component will be notified as soon as another Mp3FileObject is selected in the Mp3SearchList, or if the window showing the Mp3SearchList loses focus. As an example, no detailed MP3 information will be displayed.

Following, you find the classes of this example application. Only the important parts of the classes are shown.

First, we have the class Mp3SearchList, which represents a window, and because of this, extends from the base class TopComponent. To enable listening to selection changes in the result list, we also implement the ListSelectionListener interface. As a private member, we have a data model that manages the data in the table. For demonstration purposes, a simple data model has been chosen, creating three example objects of the class Mp3FileObject in the constructor and adding them to the model. This data would normally be provided using the search algorithm. The second private member object is an instance of InstanceContent. This enables us to dynamically change the content of the Lookup. In the constructor of the Mp3SearchList, we can now create a local Lookup, using the class AbstractLookup and passing our InstanceContent object into its constructor. Using the method associateLookup(), our local Lookup is set as the Lookup of the TopComponent, so that it will be returned from the getLookup() method.

In the method valueChanged(), which gets called if a data set is selected in the table, we get the data set from the data model, wrap it into a collection, and pass it to our InstanceContent instance (see Listing 6-6), which is the data storage for the Lookup. So, the selected element is always part of the local Lookup.

Example 6.6. Mp3SearchList displays the search results in a table and adds the actual selected data set to the local Lookup.

public class Mp3SearchList extends TopComponent implements ListSelectionListener {
   private Mp3SearchListModel model = new Mp3SearchListModel();
   private InstanceContent content = new InstanceContent();
   private Mp3SearchList() {
      initComponents();
      searchResults.setModel(model);
      searchResults.getSelectionModel().addListSelectionListener(this);
      associateLookup(new AbstractLookup(content));
   }
   public void valueChanged(ListSelectionEvent event) {
      if(!event.getValueIsAdjusting()) {
         Mp3FileObject mp3 = model.getRow(searchResults.getSelectedRow());
         content.set(Collections.singleton(mp3), null);
      }
   }
}

Here, the data model Mp3SearchListModel of the table with the search results is just an example and is kept quite simple (see Listing 6-7). Three objects of the type Mp3FileObject are directly created in the constructor.

Example 6.7. Simplified data model managing and providing the data for the result list

public class Mp3SearchListModel extends AbstractTableModel {
   private String[] columns = {"Artist", "Title", "Year"};
   private Vector<Mp3FileObject> data = new Vector<Mp3FileObject>();
   public Mp3SearchListModel() {
      data.add(new Mp3FileObject("Gigi D'Agostino", "The rain", "2006"));
data.add(new Mp3FileObject("Marquess", "El temperamento", "2006"));
      data.add(new Mp3FileObject("Floorfilla", "Cyberdream", "2006"));
   }
   public Mp3FileObject getRow(int row) {
      return(data.get(row));
   }
   public Object getValueAt(int row, int col) {
      Mp3FileObject mp3 = data.get(row);
      switch(col) {
         case 0: return mp3.getArtist();
         case 1: return mp3.getTitle();
         case 2: return mp3.getYear();
      }
      return "";
   }
}

The class Mp3DetailsView is the window showing detailed information of the selected entry of the Mp3SearchList. To get notification of changes in the Lookup—e.g., in case of selection changes—the LookupListener interface is implemented. A Lookup.Result, which enables us to react to changes for a specific type (in our case Mp3FileObject), is used as a private member. Opening a window triggers the method componentOpened(). We use this callback to obtain the Lookup of the proxy component, using the method Utilities.actionsGlobalContext(), which returns a Lookup that always delegates to the local Lookup of the active TopComponent. Based on this Proxy Lookup, we now create a Lookup.Result for the type Mp3FileObject and register a LookupListener to listen to changes on this result. If a TopComponent now gains the focus, which has one or more instances of this type in a local Lookup, the method resultChanged() gets called. With this, we only need to retrieve the instances and display the information accordingly, as shown in Listing 6-8.

Example 6.8. The window Mp3DetailsView shows the information of the Mp3FileObject, which is selected in the Mp3SearchList.

public class Mp3DetailsView extends TopComponent implements LookupListener {
   private Lookup.Result<Mp3FileObject> result = null;
   private Mp3DetailsView() {
      initComponents();
   }
   public void componentOpened() {
      result = Utilities.actionsGlobalContext().lookupResult(Mp3FileObject.class);
      result.addLookupListener(this);
      resultChanged(null);
   }
   public void resultChanged(LookupEvent event) {
      Collection<? extends Mp3FileObject> mp3s = result.allInstances();
      if(!mp3s.isEmpty()) {
         Mp3FileObject mp3 = mp3s.iterator().next();
         artist.setText(mp3.getArtist());
         title.setText(mp3.getTitle());
         year.setText(mp3.getYear());
      }
}
}

The information provided via Mp3SearchList and displayed using Mp3DetailsView is part of the class Mp3FileObject (see Listing 6-9). This class should be implemented in a separate module to achieve the best possible encapsulation and reuse. In this example, it is module C. To grant modules A and B access to this class, they must declare a dependency on module C. If the class Mp3FileObject is provided only via module A, it is possible to move the class to module A.

Example 6.9. Mp3FileObject provides the data

public class Mp3FileObject {
   private String artist = new String();
   private String title  = new String();
   private String year   = new String();
   public Mp3FileObject(String artist, String title, String year) {
      this.artist = artist;
      this.title  = title;
      this.year   = year;
   }
   public String getArtist() {
      return this.artist;
   }
   public String getTitle() {
      return this.title;
   }
   public String getYear() {
      return this.year;
   }
}

As a proxy component, we use the global Proxy Lookup provided by the NetBeans Platform, which delegates to the local Lookup of the active TopComponent. In Figure 6-4, this is depicted with the interface ContextGlobalProvider. This global proxy Lookup is easily substituted by another implementation. This implementation has only to provide the local Lookup of the component containing the subject to the observer.

Java Service Loader

Since Java 6, an API is available similar to Lookup: ServiceLoader. This class loads service providers, which are registered over the META-INF/services directory. With this functionality, the API equals the NetBeans standard Lookup that can be obtained using Lookup.getDefault(). A ServiceLoader is created for a special type using the Class object of the service interface or the abstract service class. A static factory method is used for creating a ServiceLoader instance. Depending on the classloader used to load the service providers, three methods for creating service loaders are available.

By default, service providers are loaded using the context classloader of the current thread. Inside the NetBeans Platform, this is the system classloader (for more details on the NetBeans classloader system, see Chapter 2). This allows the user to load service providers from all modules. Such a service loader is created with the following call:

ServiecLoader<Mp3Finder> s = ServiceLoader.load(Mp3Finder.class);

You may want to use a special classloader to load service providers—e.g., a module classloader to restrict loading of service providers to classes from your own module. To obtain such a ServiceLoader, the classloader to be used is passed to the factory method:

ServiceLoader<Mp3Finder> s = ServiceLoader.load(
   Mp3Finder.class, this.getClass().getClassLoader());

In addition to this, it is possible to create a service loader that only returns installed service providers—e.g., a service provider from JAR archives located in the lib/ext directory or in the platform-specific extension directory. Other service providers found on the classpath are ignored. This service loader is created using the loadInstalled() method:

ServiceLoader<Mp3Finder> s = ServiceLoader.loadInstalled(Mp3Finder.class);

The service provider can be obtained using an iterator. The iterator triggers dynamic loading of the provider on first access. The loaded providers are stored in a local cache. The iterator returns the cached providers before loading the remaining previously unloaded providers. If necessary, the internal cache can be cleared using the method reload(). This ensures that all providers are reloaded.

Iterator<Mp3Finder> i = s.iterator();
if(i.hasNext()) {
   Mp3Finder finder = i.next();
}

Summary

In this chapter, you learned one of the most interesting and important concepts of the NetBeans Platform: Lookup. We examined functionality of Lookups and you became familiar with the service interfaces and service providers. You learned to create service interfaces and use them within service providers, as well as how service providers are discovered in a loosely coupled way. To that end, we began to use the various registration mechanisms.

However, Lookups do a lot more than simply discover services. In fact, they also function to enable intermodular communication. We looked at an example, showing how information is shared between windows without them knowing about each other. Finally, we broadened our exploration of this topic by relating it to the JDK 6 ServiceLoader class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.214.155