设计目标
面向对象的设计里,模块与模块之间是通过接口通信的,模块之间不对实现类进行硬编码。一旦代码里涉及到了具体的实现类,就违反了可插拔的原则,如果需要替换一种实现,就需要修改代码。为了在实现模块装配的时候,不在模块里写死代码,需要一种服务的发现机制。java spi就是提供这样的一个机制:为了某个接口寻找服务实现的机制。有点像IOC的思想,就是讲装配的控制权移到代码之外。
具体约定
当服务的提供者(provider),提供了一个接口多种实现时,一般会在jar包的META-INF/services/目录下,创建改接口的同名文件(接口的全名),文件的内容为改接口的具体的实现类的名称。而当外部加载这个模块的时候,就通过jar包META-INF/services/里的配置文件得到具体的实现类名,并加载实例化,完成模块的装载。
DEMO
定义接口
package com.dodoi.jdk.spi;
public interface Command {
public void execute();
}
实现类一
package com.dodoi.jdk.spi;
public class StartCommand implements Command{
@Override
public void execute() {
System.out.println("command start");
}
}
实现类二
package com.dodoi.jdk.spi;
public class ShutdownCommand implements Command{
@Override
public void execute() {
System.out.println("command shutdown");
}
}
META-INF/service/ com.dodoi.jdk.spi.Command
com.dodoi.jdk.spi.StartCommand
com.dodoi.jdk.spi.ShutdownCommand
测试
package com.dodoi.jdk.spi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
/**
* Executes the App
*/
public App() {
ServiceLoader<Command> serviceLoader = ServiceLoader.load(Command.class);
for(Command command : serviceLoader){
command.execute();
}
}
/**
* Program entry point
*
* @param args command line args
*/
public static void main(String[] args) {
new App();
}
}
输出
command start
command shutdown
JDK SPI缺点
jdk会一次性加载所有的实现类
- 启动耗时
- 浪费资源