概述
SPI(Service Provider Interface),服务提供者接口,它是JDK提供的一种服务发现机制。目前在很多开源项目中使用,如common-logging,JDBC等。
举个例子,我们在项目开发中经常会用到各种序列化,一般情况下,我们会定义好接口,再继承接口实现具体逻辑,如hession。如果一段时间后想替换成protobuffer,使用SPI机制,就可以通过配置文件可以实现动态替换。因此SPI是"基于接口编程+策略模式+配置文件"组合实现的动态加载机制。
实现:
1. 定义序列化接口
public interface Serializer {
/**
* 序列化
* @param msg
* @return
* @throws IOException
*/
byte[] serialize(Object msg) throws IOException;
/**
* 反序列化
* @param data
* @param type
* @param <T>
* @return
* @throws IOException
*/
<T> T deserialize(byte[] data, Class<T> type) throws IOException;
}
2. 继承Serializer接口,实现fasjson,hessian,kryo和protostuff四种具体序列化方式
public class ProtostuffSerializer implements Serializer {
private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<>();
private static Objenesis objenesis = new ObjenesisStd(true);
@Override
public byte[] serialize(Object msg) throws IOException {
LinkedBuffer buffer = null;
try {
buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
Schema schema = getSchema(msg.getClass());
return ProtostuffIOUtil.toByteArray(msg, schema, buffer);
}catch (Exception e){
throw e;
}finally {
if (buffer != null) {
buffer.clear();
}
}
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) throws IOException {
try {
T message = objenesis.newInstance(clazz);
Schema schema = getSchema(clazz);
ProtostuffIOUtil.mergeFrom(data, message, schema);
return message;
}catch (Exception e){
throw e;
}
}
private static <T> Schema getSchema(Class<T> clazz){
Schema schema = cachedSchema.get(clazz);
if(schema == null){
schema = RuntimeSchema.createFrom(clazz);
cachedSchema.put(clazz, schema);
}
return schema;
}
}
3. 在resources目录下新建META-INF/services目录,文件名是接口全限定名:com.*.serializer.Serializer,文件内容为具体实现类的全限定名:com.*.serializer.protostuff.ProtostuffSerializer
4. Serializer接口的具体实现类就是文件中定义的实现
Serializer serializer = SpiLoderFactory.getInstance(Serializer.class);
public static <T> T getInstance(Class<?> clazz){
return (T) ServiceLoader.load(clazz).iterator().next();
}
5. ServiceLoader类中定义了文件读取的目录,也就是为什么我们自定义的文件要存在于META-INF/services下
6. 基本原理还是通过反射将对象放到providers这个linkedHashMap中,通过遍历即可访问
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}