中间件技术一直都是自己最感兴趣,有分布式服务框架,分布式消息队列,分布式缓存,分布式数据服务等等,参与开发其中任一一个项目都会学到很多知识,很幸运在开始参加工作后的第一个项目就是分布式服务框架,后来接触到了dubbo,被dubbo的设计所吸引,虽然有时间偶尔也会看看dubbo的代码,但没有系统的好好总结沉淀下来,看得如同过往云烟,时间也就那样的流逝了。最近也不断地在反思,工作快2两年了,像是一直在原地踏步,很多时候都是急于求成,自知其然,却不知其所以然,看着距离自己的第一个目标还那么的远,所以决定好好的静下心来先从学dubbo开始。
dubbo是服务框架,服务框架是用于实现服务的复用的框架,是软件框架。那框架是什么?让我联想到了建筑框架,建筑框架确定了整个建筑的结构,建筑框架允许你在不改变结构的基础上,自由改变其内容。例如,你可以用墙体随意分隔房间。所以框架就好比建筑框架,可以这样说,框架的本质就是“扩展”。在一个框架中,实现丰富的功能固然重要,然而更重要的是:建立良好的扩展机制。
dubbo基于JDK中的SPI机制扩展了一套自己的扩展机制,让使用者方便地自己扩展和实现自己的插件。
下面介绍JDK提供的SPI机制(java.util.ServiceLoader):
SPI英文为Service Provider Interface单从字面可以理解为Service提供者接口,正如从SPI的名字去理解SPI就是Service提供者接口;提供给服务提供厂商与扩展框架功能的开发者使用的接口。
SPI 机制的约定:
1) 在META-INF/services/目录中创建以接口全限定名命名的文件该文件内容为Api具体实现类的全限定名
2) 使用ServiceLoader类动态加载META-INF中的实现类
3) 如SPI的实现类为Jar则需要放在主程序classPath中
4) Api具体实现类必须有一个不带参数的构造方法
下面是基于JDK的SPI的showcase
//工具类(对接用户和SPI):
package com.mcg.java.tool.serviceloader.utils;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
//工具类
public class ServicerLoaderUtils
{
//保存spi加载的服务
private static ConcurrentMap<String,Object> services=new ConcurrentHashMap<String,Object>();
//用户调用方法,获取相应的服务
public static Object getService(String name,Class<?> clazz) throws Exception
{
Object service= services.get(name);
if(null==service)
{
load(clazz);
service= services.get(name);
}
return service;
}
//加载服务
public static <T> void load(Class<T> service) throws Exception
{
for (T t:ServiceLoader.load(service))
{
String name=t.getClass().getAnnotation(ServiceAnnotation.class).name();
services.putIfAbsent(name, t);
}
}
}
//注解工具类:
package com.mcg.java.tool.serviceloader.utils;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceAnnotation
{
String name() default "default";
}
//服务接口:
package com.mcg.java.tool.serviceloader;
public interface Handler
{
void handler();
}
//服务实现:
package com.mcg.java.tool.serviceloader.HandlerImpl;
import com.mcg.java.tool.serviceloader.Handler;
import com.mcg.java.tool.serviceloader.utils.ServiceAnnotation;
@ServiceAnnotation
public class DefaultHandlerImpl implements Handler
{
public void handler()
{
System.out.println("this is defaultHandler");
}
}
package com.mcg.java.tool.serviceloader.HandlerImpl;
import com.mcg.java.tool.serviceloader.Handler;
import com.mcg.java.tool.serviceloader.utils.ServiceAnnotation;
@ServiceAnnotation(name="handlerImpl1")
public class HandlerImpl1 implements Handler
{
public void handler()
{
System.out.println("this is handlerImpl1");
}
}
package com.mcg.java.tool.serviceloader.HandlerImpl;
import com.mcg.java.tool.serviceloader.Handler;
import com.mcg.java.tool.serviceloader.utils.ServiceAnnotation;
@ServiceAnnotation(name="handlerImpl2")
public class HandlerImpl2 implements Handler
{
public void handler()
{
System.out.println("this is handlerImpl2");
}
}
//SPI配置文件(META-INF/services/com.mcg.java.tool.serviceloader.Handler)
com.mcg.java.tool.serviceloader.HandlerImpl.DefaultHandlerImpl
com.mcg.java.tool.serviceloader.HandlerImpl.HandlerImpl1
com.mcg.java.tool.serviceloader.HandlerImpl.HandlerImpl2
//测试:
package com.mcg.java.tool.serviceloader;
import com.mcg.java.tool.serviceloader.utils.ServicerLoaderUtils;
public class ServiceloaderTest
{
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception
{
Handler handler=(Handler) ServicerLoaderUtils.getService("default",Handler.class);
handler.handler();
Handler handler1=(Handler) ServicerLoaderUtils.getService("handlerImpl1",Handler.class);
handler1.handler();
Handler handler2=(Handler) ServicerLoaderUtils.getService("handlerImpl2",Handler.class);
handler2.handler();
}
}
//结果:
this is defaultHandler
this is handlerImpl1
this is handlerImpl2
在上面的showcase中,ServicerLoaderUtils和ServiceAnnotation是两个工具类,只要基于这两个工具类就可以实现基于名字来获取不同的服务,然后基于策略模式执行相应的服务。ServicerLoaderUtils的load方法中对接ServiceLoader来实现服务的加载。
ServiceLoader的源码分析
上面showcase中先根据ServiceLoader的load静态方法根据目标接口加载出一个ServiceLoader实例,然后可以遍历这个实例(实现了Iterable接口),获取到接口的所有实现类。
ServiceLoader有几个重要属性:
//查找实现类的前级目录
private static final String PREFIX = "META-INF/services/";
//要加载的接口
private Class<S> service;
//
private ClassLoader loader;
// 缓存已经加载过的实现类,其中key为实现类的全名
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
//延迟加载器
private LazyIterator lookupIterator;
在上面调用load方法中,会创建ServiceLoader实例,会初始化的重要属性,此时还没有进行任何接口实现类的加载操作,属于延迟加载类型的。
在执行for循环的时候,由于ServiceLoader实现了Iterable接口,即实现了该接口的iterator()方法,实现内容如下:
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
其实for循环就是调用上述hasNext()和next()方法的过程。
第一次循环遍历会使用lookupIterator去查找,之后就缓存到providers中。LazyIterator会去加载类路径下/META-INF/services/接口全称 文件的url地址,使用如下代码来加载:
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
文件加载并解析完成之后,得到一系列的接口实现类的完整类名,调用next()方法时才回去真正执行接口实现类的加载操作,并根据无参构造器创建出一个实例,存到providers中;
之后再次遍历ServiceLoader,就直接遍历providers中的数据
S p = service.cast(c.newInstance());
providers.put(cn, p);
ServiceLoader缺点分析
虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。
获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类