本文是Dubbo系列】无处不在的SPI系列文章的第三小节。
【Dubbo系列】无处不在的SPI:崭露头角 中主要介绍了JDK的SPI机制,以及ServiceLoader的使用。
【Dubbo系列】无处不在的SPI之羽翼渐丰 中补充了SPI和API的区别,并对JDK的SPI进行改写,通过ExtensionLoader实现了仅加载所需要的扩展实现类,达到节约资源的目的。
上文文末提到一个问题,如何为不同的服务(Dubbo中称为扩展点,下文中统一把 服务称为扩展点,服务提供者称为扩展点实现类)提供不同的ExtensionLoader类?
我们可以通过工厂方法来生成ExtensionLoader
类,并把扩展加载类缓存起来,这样在调用ExtensionLoader.getExtensionLoader
方法时首先获取缓存中的值,不存在则创建。以得到单一实例对象。
静态工厂来生成 ExtensionLoader
// 缓存ExtensionLoader类
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<> ();
// 通过静态工厂获取ExtensionLoader
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
// 参数校验
if (type == null) {
throw new IllegalArgumentException (" getExtensionLoader params type == null");
}
// 服务接口校验
if(!type.isInterface ()){
throw new IllegalArgumentException (" extension type " + type + " is not interface! ");
}
if (!withExtensionAnnotation(type)){
throw new IllegalArgumentException (" type WITHOUT @" + SPI.class.getSimpleName () + " Annotation ");
}
ExtensionLoader<T> extensionLoader = (ExtensionLoader<T>) EXTENSION_LOADERS.get (type);
if (extensionLoader == null) {
// 通过增加参数type,确定ExtensionLoader对象的服务类型
EXTENSION_LOADERS.putIfAbsent (type,new ExtensionLoader<T> (type));
extensionLoader = (ExtensionLoader<T>) EXTENSION_LOADERS.get (type);
}
return extensionLoader;
}
接下来,需要把构造函数改为私有函数,并添加私有变量type,用于指定ExtensionLoader
对象的服务类型。
这样可以移除掉getExtension、getExtensionClasses、getExtensionClasses、loadResource
等方法中的type参数,使方法所需要的参数 type 内聚到类中,增强ExtensionLoader类的内聚性。
// 构造函数改为私有
private ExtensionLoader(Class<T> type) {
this.type = type;
}
// 获取Extension class 的接口
private Class<T> type;
写个测试类进行测试:
@Test
public void invokeExtension(){
Protocol protocol = ExtensionLoader.getExtensionLoader (Protocol.class).getExtension ("dubbo");
String echo = protocol.invoke ();
assertEquals("dubbo_protocol", echo);
}
扩展点自动注入
如果一个A服务接口中使用了B服务接口,那么生成扩展类A实现对象时,如何自动注入B服务的扩展实现类呢?
举个例子:
新建一个 Ext1 扩展点:
package com.ryan.myspi.ext1;
import com.ryan.SPI;
@SPI
public interface Ext1 {
String echo();
}
建立扩展点实现类:
package com.ryan.myspi.ext1.impl;
import com.ryan.myspi.Protocol;
import com.ryan.myspi.ext1.Ext1;
public class Ext1Impl1 implements Ext1 {
private Protocol hession;
public void setHession(Protocol protocol) {
this.hession = protocol;
}
@Override
public String echo() {
hession.invoke ();
return "Ext1Impl1-echo";
}
}
Ext1Impl1扩展点实现类中使用Protocol的扩展点,这里使用约束 :属性名表示属性Protocol的扩展实现类name。
然后在生成Ext1的扩展实现类是,通过反射注入name为hession的Protocol扩展实现类,实现流程如下:
优化getExtension方法,使用Holder对象来持有经过注入的扩展实现类:
// 经过注入的扩展实现类缓存
private final ConcurrentMap<String, Holder<Object>> injectedCachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
public T getExtension(String name) {
if (name == null || name == "") {
throw new IllegalArgumentException (" getExtension param name == null ");
}
// 通过ConcurrentMap保证线程安全
// Holder类作为方法参数传入方式时,其中的value引用可以被改变
Holder<Object> objectHolder = injectedCachedInstances.get (name);
if (objectHolder == null) {
// 线程同时到时,ConcurrentMap保证线程安全性,使得拿到的objectHolder为同一个对象
injectedCachedInstances.putIfAbsent (name,new Holder<Object> ());
objectHolder = injectedCachedInstances.get (name);
}
Object instance = objectHolder.get ();
if (instance == null) {
synchronized (objectHolder) {
Object o = objectHolder.get ();
if (o == null) {
objectHolder.set (createExtension (name));
instance = objectHolder.get ();
}
}
}
return (T) instance;
}
如果缓存不存在时,调用createExtension
:
// 新增经过注入的扩展类
public T createExtension(String name) {
if (name == null || name == "") {
throw new IllegalArgumentException (" getExtension param name == null ");
}
try {
Class<?> clazz = getExtensionClasses ().get (name);
// 查缓存
T instance = (T) EXTENSION_INSTANCES.get (clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent (clazz, clazz.newInstance ());
instance = (T) EXTENSION_INSTANCES.get (clazz);
}
// 注入依赖的扩展点
injectExtension (instance);
return instance;
} catch (Throwable e) {
throw new IllegalArgumentException (" clazz " + type + " newInstance error");
}
}
其中injectExtension
方法用于注入依赖的扩展点实现类:
private T injectExtension(T instance) {
if (instance == null) {
throw new IllegalArgumentException (" injectExtension param instance == null");
}
// 通过反射注入扩展类
for (Method method : instance.getClass ().getMethods ()) {
if (method.getName ().startsWith ("set")
&& method.getParameterTypes ().length == 1
&& Modifier.isPublic (method.getModifiers ())) {
// 注入
Class<?> pt = method.getParameterTypes ()[0];
// 获取参数
try {
String methodName = method.getName ();
// 属性名称,也就是依赖的扩展实现类name
String property = methodName.length ()>3 ? methodName.substring (3, 4).toLowerCase () + methodName.substring (4) : "";
// 获取type为property的扩展类,Ext1Impl1中为hession
Object ob = ExtensionLoader.getExtensionLoader (pt).getExtension (property);
// 反射调用set方法注入该扩展类
if(ob!=null) {
method.invoke (instance, ob);
}
} catch (Exception e) {
System.out.println ("fail to inject via method :" + method.getName ());
}
}
}
return instance;
}
经过上面方法实现了依赖扩展类注入,写一个测试来验证一下:
@Test
public void injectExtension(){
Ext1 ext1 = ExtensionLoader.getExtensionLoader (Ext1.class).getExtension ("ext1Impl1");
assertEquals ("hessian_protocol",ext1.echo ());
}
小结
本文通过静态工厂实现ExtensionLoader
类的创建,并加入缓存;同时通过反射实现扩展点依赖的注入。但是不太灵活。Duboo中使用了 扩展点自适应 的方式,实现了扩展点自动引入,在方法调用时,才选择具体执行的是哪一个扩展点实现。下文接着讨论。
如果需要原文代码的朋友请关注公众号 : *码农的日常记录,并在后台回复:spi1*
推荐阅读:
线程池的前世今生
了解更多请关注微信公众号