Java ServiceLoader(SPI)学习

来源:互联网 发布:淘宝买苹果主板靠谱吗 编辑:程序博客网 时间:2024/06/05 07:01

研究Neo4j代码过程中jar包中配置接口实现方法,通过JAVA SPI方式实现。


我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。

  

为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。


java spi的具体约定如下 :

当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 

基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。

jdk提供服务实现查找的一个工具类:java.util.ServiceLoader


2.实例代码

直接上代码吧
1)接口
[java] view plaincopy
  1. package com.unei.serviceloader;  
  2.   
  3. /** 
  4.  * Created by sun on 2015/7/25. 
  5.  */  
  6. public interface Command {  
  7.     public void execute();  
  8. }  

2)实现类
[java] view plaincopy
  1. package com.unei.serviceloader;  
  2.   
  3. /** 
  4.  * Created by sun on 2015/7/25. 
  5.  */  
  6. public class ShutdownCommand implements Command{  
  7.     public void execute() {  
  8.         System.out.println("shutdown....");  
  9.     }  
  10. }  

[java] view plaincopy
  1. package com.unei.serviceloader;  
  2.   
  3. /** 
  4.  * Created by sun on 2015/7/25. 
  5.  */  
  6. public class StartCommand implements Command{  
  7.     public void execute() {  
  8.         System.out.println("start....");  
  9.     }  
  10. }  

3)配置文件
由于是使用maven构建的项目,所以就在resources下面新建目录META-INF/services,在该目录下新建文件com.unei.serviceloader.Command,即完整的接口名
文件内容如下:
[plain] view plaincopy
  1. com.unei.serviceloader.ShutdownCommand  
  2. com.unei.serviceloader.StartCommand  

4)main方法
[java] view plaincopy
  1. package com.unei.serviceloader;  
  2.   
  3. import java.util.ServiceLoader;  
  4.   
  5. /** 
  6.  * Created by sun on 2015/7/25. 
  7.  */  
  8. public class Main {  
  9.     public static void main(String[] args) {  
  10.         ServiceLoader<Command> serviceLoader=ServiceLoader.load(Command.class);  
  11.         for(Command command:serviceLoader){  
  12.             command.execute();  
  13.         }  
  14.     }  
  15.   
  16. }  

5)编译执行
[plain] view plaincopy
  1. mvn clean install -Dmaven.test.skip=true  
  2. mvn exec:java -Dexec.mainClass=com.unei.serviceloader.Main  

3.activemq中的使用实例
第一次接触到ServiceLoader就是在activemq的启动过程中,所以学习了一下
activemq broker启动时,调用的是org.apache.activemq.console.command.ShellCommand类,ShellCommand.main调用runTask,runTask会调用getCommands方法,下面看一下getCommands的代码:
[java] view plaincopy
  1. ArrayList<Command> getCommands() {  
  2.     ServiceLoader<Command> loader = ServiceLoader.load(Command.class);  
  3.     Iterator<Command> iterator = loader.iterator();  
  4.     ArrayList<Command> rc = new ArrayList<Command>();  
  5.     boolean done = false;  
  6.     while (!done) {  
  7.         try {  
  8.             if( iterator.hasNext() ) {  
  9.                 rc.add(iterator.next());  
  10.             } else {  
  11.                 done = true;  
  12.             }  
  13.         } catch (ServiceConfigurationError e) {  
  14.             // it's ok, some commands may not load if their dependencies  
  15.             // are not available.  
  16.         }  
  17.     }  
  18.     return rc;  
  19. }  

ServiceLoader会读取META-INF/services/org.apache.activemq.console.command.Command 配置的类并在迭代时将其实例化。
回头看一下runTask方法:
[java] view plaincopy
  1. protected void runTask(List<String> tokens) throws Exception {  
  2.   
  3.     // Process task token  
  4.     if (tokens.size() > 0) {  
  5.         Command command=null;  
  6.         String taskToken = (String)tokens.remove(0);  
  7.   
  8.   
  9.         for( Command c: getCommands() ) {  
  10.             if( taskToken.equals(c.getName()) ) {  
  11.                 command = c;  
  12.                 break;  
  13.             }  
  14.         }  
  15.         if( command == null ) {  
  16.             if (taskToken.equals("help")) {  
  17.                 printHelp();  
  18.             } else {  
  19.                 printHelp();  
  20.             }  
  21.         }  
  22.   
  23.         if( command!=null ) {  
  24.             command.setCommandContext(context);  
  25.             command.execute(tokens);  
  26.         }  
  27.     } else {  
  28.         printHelp();  
  29.     }  
  30.   
  31. }  

该方法根据类名选择Command,并执行。

问题:

1.配置文件为什么要放在META-INF/services下面?
ServiceLoader.PREFIX定义如下:
private static final String PREFIX = "META-INF/services/";
JDK已经写死了。
但是如果ServiceLoader在load时提供Classloader,则可以从其他的目录读取。
2.ServiceLoader读取实现类是什么时候实例化的?
ServiceLoader.LazyIterator.nextService中实例化,即load的结果迭代时才会被实例化。

0 0
原创粉丝点击