这篇关于SPI的文章是在blogspot上面,被ZF给强奸了,大多数人访问不了,就贴在这里,比较好懂。
From ServiceLoader javadoc: A service is a well-known set of interfaces and classes. A service provider is a specific implementation of a service. The classes in a provider typically implement the interfaces and subclass the classes defined in the service itself.
Since JDK 6, a simple service-provider loading facility is implemented. As is suggested it is simple, you cannot understand that implementation as a complex system for implementing plugins for your application, but can help you in many situations where you want implementations of a service could be discovered by other module automatically.
What I really like about using JDK Services is that your services do not have any dependency to any class. Moreover for registering a new implementation of a service, you just have to put jar file into classpath and nothing more.
Now I will explain the service we are going to implement, and then I will show you how to code it.
We want to implement a system that depending on kind of structured input (comma-separated value, tab-separated value, ...) returns a String[] of each value; so for example you can receive input a,b,c,dor 1<tab>2<tab>3<tab>4 and the system should return an array with [a, b, c, d] or [1, 2, 3, 4].
So our system will have three Java projects.
One defining service contract (an interface) and, because of teaching purpose, a main class where internet media type, for example text/csv, is received with input data. Then using a factory class that I have created, it will ask which registered service can transform input to String[].
And two projects each one implementing a service following defined contract, one for comma-separated values and another one for tab-separated values.
Let's see the code:
Main project (reader) is composed by an interface, a main class and a factory class.
The most important part is Decode interface which defines service contract.
public interface Decode { boolean isEncodingNameSupported(String encodingName); String[] getContent(String data); }Two operations are defined, one that returns if service supports given input, and another that transforms data to String[] .
public class DecodeFactory { private static ServiceLoader decodeSetLoader = ServiceLoader.load(Decode.class); public static Decode getDecoder(String encodingName) throws UnsupportedEncodingException { for (Decode decode : decodeSetLoader) { if(decode.isEncodingNameSupported(encodingName)) { return decode; } } throw new UnsupportedEncodingException(); } }At line 3 we are loading all services that are registered in classpath. At line 7 we only iterate through all services asking if given encoding name is supported.
public class App { public static void main(String[] args) throws UnsupportedEncodingException { String encodeName = args[0]; String data = args[1]; Decode decoder = DecodeFactory.getDecoder(encodeName); System.out.println(Arrays.toString(decoder.getContent(data))); } }And now if you run this class with java -jar reader.jar "text/cvs" "a, b, c, d" , an UnsupportedEncodingException will be thrown. Now we are going to implement our first service. Note that reader project will not be modified nor recompiled.
public class CSVDecoder implements Decode { private static final String DELIMITER = Character.toString(DecimalFormatSymbols.getInstance().getPatternSeparator()); public boolean isEncodingNameSupported(String encodingName) { return "text/csv".equalsIgnoreCase(encodingName.trim()); } public String[] getContent(String data) { List values = new LinkedList(); StringTokenizer parser = new StringTokenizer(data, DELIMITER); while(parser.hasMoreTokens()) { values.add(parser.nextToken()); } return values.toArray(new String[values.size()]); } }As you can see a simple StringTokenizer class. Only take care that this class is Locale sensitive, countries where comma (,) is used as decimal delimiter, separation character is semicolon (;).
org.alexsotob.reader.csv.CSVDecoder #Comma-separated value decode.