|
The Java Specialists' Newsletter
Issue 139 2007-02-10
Category:
Tips and Tricks
Java version: JDK 1.6 Mustang ServiceLoaderby Dr. Heinz M. KabutzAbstract: 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!
Would you like to really understand Java concurrency? Join us for an
in-depth study of how threading works in Java. During the course,
you will learn how to write correct and fast multi-threaded Java code.
Please
click here if you would like to learn more. 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
Tips and Tricks Articles
Related Java Course
Discuss at The Java Specialist Club
|