java中的SPI机制初探

SPI在java中是Service Provider Interface的缩写,服务提供者接口的意思,最早,这个方式是用来针对第三方提供的服务或者扩展的,也就是各个厂商或者插件。系统只提供抽象接口,并不实现,而由厂商提供实现,每一个厂商都有自己的实现,当我们需要调用这个接口服务,我们只需要指定接口,系统会动态加载各种实现并初始化。

SPI机制也需要遵循一个规范:厂商提供接口实现,最终打包的时候,需要指定这个实现所在的位置,以及实现类的全限定名,需要在类路径下的META-INF/services/目录下创建一个以接口名命名的文件,文件中指定实现类的全限定名即可。当外部程序需要装配和这个模块的时候,就能通过该jar包/META-INF/services/里的配置文件找到具体的实现类,并装载实例化,完成模块的注入。

寻找接口的实现类,即服务发现的过程,有点类似IOC,将装配的控制权移到程序之外,这也是模块化的设计思想。jdk提供了服务发现的工具类:java.util.ServiceLoader。通过该类,我们可以查找所有的实现类。

SPI实现机制如下图所示:

若要满足SPI的实现,需要构造这样的项目,不仅要实现接口,还需要在MATA-INF/services/目录下构建一个接口命名的文件:

SPI的机制有点类似策略模式,接口类提供抽象接口,不提供具体实现,实现类实现具体的接口,一个实现类就是一种方案。SPI实际上就是基于“接口的编程+策略模式+配置文件”实现的服务动态加载机制。

下面通过具体的实例来演示:

创建两个项目,一个是提供服务(即接口实现)的项目,一个是服务调用方项目,我们在调用方会引入接口实现项目依赖。

接口实现项目结构如下:

工程内容很简单,就是定义一个接口,两个实现类,然后配置实现类的全限定名。

接口类:GreetService.java

package com.xxx.service;

public interface GreetService {
	void say(String name);
}

实现类:HelloService.java

package com.xxx.service;
public class HelloService implements GreetService {

	public void say(String name) {
		System.out.println("hello2,"+name);
	}

}

实现类:GoodbyeService.java

package com.xxx.service;
public class GoodbyeService implements GreetService{

	public void say(String name) {
		System.out.println("goodbye,"+name);
	}
	
}

META-INF/services/com.xxx.service.GreetService

com.xxx.service.HelloService
com.xxx.service.GoodbyeService

项目打包,然后在服务调用项目中引入这个依赖,建立一个测试类,我们简单测试一下:

package com.xxx.spi;
import java.util.ServiceLoader;
import com.xxx.service.GreetService;
public class ServiceProviderInterfaceTest {

	public static void main(String[] args) {
		ServiceLoader<GreetService> services = ServiceLoader.load(GreetService.class);
		for(GreetService service:services){
			service.say("xxx");
		}
	}

}

运行程序,打印结果如下:

在实际中使用SPI机制的的有很多,比如mysql驱动,我们看看mysql-connector-java驱动的包结构:

 

 因为它实现了java.sql.Driver驱动接口,在配置文件中配置了实现该接口的类有两个,其中一个就是我们常用的com.mysql.jdbc.Driver类。

 我们的刚才调用方测试接口中使用了jdk提供的ServiceLoader工具类,我们根据这个思想,自己实现一个服务查找的类:

1、通过文件查找,找到实现类的全限定名。

2、通过全限定名来反射找到实体类。

3、实体类通过newInstance()实例化,然后加入一个集合。

一个简单的服务加载类如下:

package com.xxx.spi;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
import com.xxx.service.GreetService;
public class SimpleServiceLoader {
	
	private static final String PREFIX="/META-INF/services/";
	
	@SuppressWarnings("unchecked")
	public static <T> List<T> load(Class<T> cls){
		List<T> list = new ArrayList<T>();
		List<String> classList = read(cls);
		for(String className:classList){
			Class<T> c = null;
			try {
				c= (Class<T>)Class.forName(className);
				list.add(c.newInstance());
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return list;
	}
	
	public static List<String> read(Class<?> cls){
		String file = cls.getCanonicalName();
		String name = cls.getResource(PREFIX+file).getPath();
		List<String> list = new ArrayList<String>();
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new FileReader(new File(name)));
			String line = "";
			while((line = reader.readLine())!=null){
				list.add(line);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			if(reader!=null){
				try {
					reader.close();
				} catch (Exception e2) {
					e2.printStackTrace();
				}	
			}
		}
		return list;
	}

	public static void main(String[] args) {
		List<GreetService> services = load(GreetService.class);
		for(GreetService service:services){
			service.say("xxx");
		}
	}

}

运行该程序,打印信息如下:

hello2,xxx
goodbye,xxx

结果和使用ServiceLoader工具类加载是一样的。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luffy5459

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值