Running on Java 22-ea+27-2262 (Preview)
Home of The JavaSpecialists' Newsletter

139Mustang ServiceLoader

Author: Dr. Heinz M. KabutzDate: 2007-02-10Java Version: 6Category: Tips and Tricks
 

Abstract: Mustang introduced a ServiceLoader than can be used to load JDBC drivers (amongst others) simply by including a jar file in your classpath. In this newsletter, we look at how we can use this mechanism to define and load our own services.

 

Welcome to the 139th edition of The Java(tm) Specialists' Newsletter, sent to you from the beautiful island of Crete. In the last 8 years, I have flown over 100 times and have experienced more than 20 airports. Flying fills me with anxiety, not because of the actual flight, but because of the airports before and after. Airports are unpleasant, soulless places, where employees are paid to harass you (so it seems). The airport of Chania (CHQ) in Crete is a breath of fresh air. It is tiny, but has an enormous runway. I was told that this is the largest runway in Europe and GoogleEarth seems to agree. It is as long as Heathrow (imagine that - on an island!) but broader. It doubles as a military airport, so you are not allowed to take pictures. This also explains its size. I love the margin for error :-) The whole experience is completely pleasant. It being Crete, no one harasses you. You only have to walk short distances. The staff is cordial and polite. And so far, they have not bothered me due to overweight luggage. Definitely the best airport experience so far :-)

The Sun Developer Day in Athens was incredible! There were 380 attendees (they expected 100). The main room was packed, with people standing in the aisles. They had two overflow rooms as well. Thanks to Aris Pantazopoulos and the whole Sun Microsystems team for putting on this cracker event!

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

Mustang ServiceLoader

JDK 1.1 introduced a clever mechanism in the way that we could add new JDBC drivers by simply loading the correct class. You could thus load the JDBC-ODBC bridge driver with the command Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"). This would, in the static intialiser block, register the driver object with the DriverManager.

In Java 6 Mustang, this was changed to a more general concept with the java.util.ServiceLoader. We use the META-INF/services/ directory to store all the implementations of services for our system.

Let's take for instance the JDBC-ODBC bridge driver. Since Mustang, you do not need to use the Class.forName() class loading anymore. The JRE/lib/resources.jar contains a META-INF/services/java.sql.Driver file, with one line: sun.jdbc.odbc.JdbcOdbcDriver. The ServiceLoader is then queried for an instance of a java.sql.Driver. It will go through all the jar files in its classpath and find all possible database drivers.

Let's say we want to use the Derby database. All we need to do is include the derby.jar file in our classpath, and the driver will automatically be available to us. There is no more need to use Class.forName(). Code that still uses the old mechanism will be compatible with Mustang.

This mechanism can be used for any service, not just for the JDBC driver. For example, if you wanted to call JRuby scripts from Java, you would include the jruby-engine.jar file, which contains a META-INF/services/javax.script.ScriptEngineFactory file. This would provide the glue to load the correct scripting language into the VM and to then provide it to the user.

So now let us say that we would like to add our own services. How could we implement this? First off, we define an interface for the service we want to offer, for example:

package com.cretesoft.services;

import java.util.List;

public interface MusicService {
  List<String> getTitleList();
  void play(String title);
}

We then define an implementation of this service, in this case I just call it MusicServiceImpl:

package com.cretesoft.music;

import com.cretesoft.services.MusicService;

import java.util.Arrays;
import java.util.List;

public class MusicServiceImpl implements MusicService {
  private final String[] titles = {
      "Don't Worry Be Happy - Bobby Mcferrin",
      "I've just seen Jesus - Larnelle Harris",
      "When Praise Demands a Sacrifice - Larnelle Harris",
      "Sultans of Swing - Dire Straits"
  };

  public List<String> getTitleList() {
    return Arrays.asList(titles);
  }

  public void play(String title) {
    System.out.println("Playing: " + title);
  }
}

Now comes the slightly fiddly bit: We need a META-INF/services/com.cretesoft.services.MusicService file containing the text:

com.cretesoft.music.MusicServiceImpl

The META-INF/services directory needs to be included in a jar file. I also created a MANIFEST.MF file in the META-INF directory:

Manifest-Version: 1.0
Main-Class: MusicServiceTest

The test code can now use the standard ServiceLoader to discover any services that implement our MusicService interface.

import com.cretesoft.services.MusicService;

import java.util.List;
import java.util.ServiceLoader;

public class MusicServiceTest {
  public static void main(String[] args) {
    ServiceLoader<MusicService> musicServices =
        ServiceLoader.load(MusicService.class);
    for (MusicService musicService : musicServices) {
      System.out.println(musicService.getClass());
      List<String> titles = musicService.getTitleList();
      for (String title : titles) {
        musicService.play(title);
      }
    }
  }
}

To make it doubly easy for you to run this example, here is an Ant build script:

<?xml version="1.0"?>
<project name="music" default="compile">
  <target name="init">
    <tstamp/>
    <mkdir dir="build"/>
  </target>

  <target name="compile" depends="init">
    <javac srcdir="src" source="1.6" target="1.6"
           destdir="build"/>
    <copy todir="build/META-INF">
      <fileset dir="src/META-INF"/>
    </copy>
    <jar jarfile="music.jar" basedir="build"
         filesetmanifest="merge"/>
  </target>

  <target name="clean">
    <delete dir="build"/>
    <delete file="music.jar"/>
  </target>
</project>

If you managed to run Ant successfully, it should be possible to simply call java -jar music.jar which should then produce this output:

    class com.cretesoft.music.MusicServiceImpl
    Playing: Don't Worry Be Happy - Bobby Mcferrin
    Playing: I've just seen Jesus - Larnelle Harris
    Playing: When Praise Demands a Sacrifice - Larnelle Harris
    Playing: Sultans of Swing - Dire Straits

We could now add new types of Music Services at will, all based on the original MusicService interface.

Be Careful!

As I learned in Athens on Wednesday, you can now use wildcards for jar files in classpaths. This means that the order of the services is non-deterministic and can depend on the operating system.

Since you cannot rely on the order in which services are offered to you, a secondary mechanism is necessary to decide which service to use. For example, to get a scripting engine, you can specify either the scripting language used, the file extension or the MIME type. Similar approaches should be used for your services.

That's all for this week. I need to get mountain biking with my son Maxi now ... :)

Kind regards from the Island of Crete

Heinz

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...