java SPI机制(转)

本文介绍如何利用JDK服务实现一个简单的模块化插件系统,通过使用ServiceLoader加载服务提供者,无需依赖任何类即可注册新的服务实现。文章详细解释了如何定义服务接口、工厂类及服务实现类,并提供了实例代码,演示如何实现不同类型的输入转换为字符串数组。同时,介绍了如何打包项目并将其服务实现放入类路径中,以及如何在主程序中调用服务。此外,还讨论了如何为不同的输入类型创建服务实现,以及如何通过META-INF服务文件引用服务实现类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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.

  1. public interface Decode {  
  2.   
  3.  boolean isEncodingNameSupported(String encodingName);  
  4.  String[] getContent(String data);  
  5.    
  6. }  
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[].

DecodeFactory class is responsible for finding an implementation service that supports required encoding. In fact, this class encapsulates java.util.ServiceLoader calls. ServiceLoader class is in charge of load registered services.

 

  1. public class DecodeFactory {  
  2.   
  3.  private static ServiceLoader decodeSetLoader = ServiceLoader.load(Decode.class);  
  4.    
  5.  public static Decode getDecoder(String encodingName) throws UnsupportedEncodingException {  
  6.     
  7.   for (Decode decode : decodeSetLoader) {  
  8.    if(decode.isEncodingNameSupported(encodingName)) {  
  9.     return decode;  
  10.    }  
  11.   }  
  12.   
  13.   throw new UnsupportedEncodingException();  
  14.  }  
  15. }  
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.

And finally main class.

 

  1. public class App {  
  2.    
  3.  public static void main(String[] args) throws UnsupportedEncodingException {  
  4.     
  5.   String encodeName = args[0];  
  6.   String data = args[1];  
  7.     
  8.   Decode decoder = DecodeFactory.getDecoder(encodeName);  
  9.   System.out.println(Arrays.toString(decoder.getContent(data)));  
  10.     
  11.  }  
  12. }  
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", anUnsupportedEncodingException will be thrown. Now we are going to implement our first service. Note that reader project will not be modified nor recompiled.

First service we are going to implement is one that can support comma-separated values encoding. Only one class and one file are important.

CSV class is an implementation of Decode interface and transforms comma-separated values.

 

  1. public class CSVDecoder implements Decode {  
  2.   
  3.  private static final String DELIMITER = Character.toString(DecimalFormatSymbols.getInstance().getPatternSeparator());  
  4.    
  5.  public boolean isEncodingNameSupported(String encodingName) {  
  6.   return "text/csv".equalsIgnoreCase(encodingName.trim());  
  7.  }  
  8.   
  9.  public String[] getContent(String data) {  
  10.     
  11.   List values = new LinkedList();  
  12.     
  13.   StringTokenizer parser = new StringTokenizer(data, DELIMITER);  
  14.     
  15.   while(parser.hasMoreTokens()) {  
  16.    values.add(parser.nextToken());  
  17.   }  
  18.     
  19.   return values.toArray(new String[values.size()]);  
  20.     
  21.  }  
  22.   
  23. }  
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 (;).

And next important file is a file that is placed into META-INF. This file contains a pointer to service implementation class.

This file should be in META-INF/services and should be called as interface full qualified name. In this case org.alexsotob.reader.Decode. And its content should be service implementation full qualified name.

 

  1. org.alexsotob.reader.csv.CSVDecoder #Comma-separated value decode.  
org.alexsotob.reader.csv.CSVDecoder #Comma-separated value decode.

 

And now you can package this project, and you can reexecute  reader project but with generated  jar( csv.jar) into  classpath. And now output will be an array with  a, b, c and d characters instead of unsupported exception.

See that reader project has not been modified and its behaviour has been changed. Now you can develop new implementations for decoding new inputs, and you should only take care of copying them into classpath.

Only take care that all services should have a default constructor with no arguments.

And for those who use  Spring FrameworkServices are also supported through three different FactoryBeansServiceFactoryBeanServiceListFactoryBeanServiceLoaderFactoryBean.
 
As I have noted at start of this post,  JDK services is a simple (yet powerful) solution if you need to create a simple plugins system. In my case it has been enough with  JDK services, and I have never required more complex structure; but in case you are thinking about a complete plugin solution you can use  JPF that offers a solution like  Eclipse plugins, or even  OSGI.
 
I wish you have found this post useful and now you know (if you didn't know yet), an easy solution to develop modules that are plugged and play.
 
 


原文链接:http://alexsotob.blogspot.com/2011/11/en-ti-puedo-ver-la-libertad-tu-me-haces.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值