Java ServiceLoader(SPI)学习

1. 几个不错的关于ServiceLoader的文章,大家可以先参考一下

1) http://www.myexception.cn/program/1355384.html 这篇的后面的问题分析不错

2.实例代码

直接上代码吧
1) 接口
package com.ctrip.lzyan.test.element.serviceloader.sample1;

public interface Command {
    
    public void execute(); 
    
}

2) 实现类

package com.ctrip.lzyan.test.element.serviceloader.sample1;

public class StartCommand implements Command {

    public void execute() {  
        System.out.println("start....");  
    }
    
}
package com.ctrip.lzyan.test.element.serviceloader.sample1;

public class ShutdownCommand implements Command {

    @Override
    public void execute() {
        System.out.println("shutdown....");
    }

}

3) 配置文件

由于是使用maven构建的项目,所以就在resources下面新建目录META-INF/services,在该目录下新建文件com.ctrip.lzyan.test.element.serviceloader.sample1.Command, 即完整的接口名
文件内容如下:
com.ctrip.lzyan.test.element.serviceloader.sample1.StartCommand
com.ctrip.lzyan.test.element.serviceloader.sample1.ShutdownCommand

4) 测试类

package com.ctrip.lzyan.test.element.serviceloader.sample1;

import java.util.ServiceLoader;

public class CommandTester {
    
    public static void main(String[] args) {
        ServiceLoader<Command> serviceLoader = ServiceLoader.load(Command.class);  
        for(Command command:serviceLoader){  
            command.execute();
        }
    }
    
}

5) 编译执行

mvn clean install -Dmaven.test.skip=true  
mvn exec:java -Dexec.mainClass=com.ctrip.lzyan.test.element.serviceloader.sample1.CommandTester

3.activemq中的使用实例

第一次接触到ServiceLoader就是在activemq的启动过程中,所以学习了一下
activemq broker启动时,调用的是org.apache.activemq.console.command.ShellCommand 类,ShellCommand.main调用runTask,runTask会调用getCommands方法,下面看一下getCommands的代 码:
    ArrayList<Command> getCommands() {  
        ServiceLoader<Command> loader = ServiceLoader.load(Command.class);  
        Iterator<Command> iterator = loader.iterator();  
        ArrayList<Command> rc = new ArrayList<Command>();  
        boolean done = false;  
        while (!done) {  
            try {  
                if( iterator.hasNext() ) {  
                    rc.add(iterator.next());  
                } else {  
                    done = true;  
                }  
            } catch (ServiceConfigurationError e) {  
                // it's ok, some commands may not load if their dependencies  
                // are not available.  
            }  
        }  
        return rc;  
    }  

ServiceLoader会读取META-INF/services/org.apache.activemq.console.command.Command 配置的类并在迭代时将其实例化。

回头看一下runTask方法:
    protected void runTask(List<String> tokens) throws Exception {  
      
        // Process task token  
        if (tokens.size() > 0) {  
            Command command=null;  
            String taskToken = (String)tokens.remove(0);  
      
      
            for( Command c: getCommands() ) {  
                if( taskToken.equals(c.getName()) ) {  
                    command = c;  
                    break;  
                }  
            }  
            if( command == null ) {  
                if (taskToken.equals("help")) {  
                    printHelp();  
                } else {  
                    printHelp();  
                }  
            }  
      
            if( command!=null ) {  
                command.setCommandContext(context);  
                command.execute(tokens);  
            }  
        } else {  
            printHelp();  
        }  
      
    }  

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


问题:

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


转自:

http://blog.csdn.net/unei66/article/details/47051017

原文地址:https://www.cnblogs.com/ylz8401/p/7359061.html