在前面分析的序列化的文章中,我们看到有这么一段代码:
/** * Serialization. (SPI, Singleton, ThreadSafe) */ @SPI("hessian2") public interface Serialization {
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface SPI { /** * default extension name */ String value() default ""; }
这里有个@SPI注解,那么它有什么作用呢,接下来我们就来分析。
一 什么是SPI
SPI的全名为Service Provider Interface,是针对厂商或者插件的。系统模块之间基于接口编程,为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外。
JDK中也有SPI技术,java.util.ServiceLoader就是用SPI,java spi的具体约定为:当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。
二 JDK SPI
JDK中拥有SPI的支持,主要涉及java.util.ServiceLoader类的使用,我们可以举一个简单的例子。
package com.user; public interface UserService { public void test(); }
实现类:
public class SystemUserService implements UserService { public void test() { System.out.println("SystemUserService"); } }
public class CustomerUserService implements UserService { public void test() { System.out.println("CustomerUserService "); } }
这里实现了2个实现类。再写一个main方法测试:
public class ServiceMainTest { public static void main(String[] args){ ServiceLoader<UserService> spiLoader = ServiceLoader.load(UserService.class); Iterator<UserService> iteratorSpi=spiLoader.iterator(); while (iteratorSpi.hasNext()){ UserService userService=iteratorSpi.next(); userService.test(); } }
然后在META-INF文件夹下创建了services文件夹,并在services文件夹下创建了一个文件,文件名以接口的全限定名来命名,我这里是com.user.UserService。然后在这个文件中填入这个接口的两个实现类,类中间以换行隔开:
com.user.SystemUserService
com.user.CustomerUserService
执行上面的主函数,打印出SystemUserService,CustomerUserService 。其实是ServiceLoader调用load方法,根据传入的接口参数找到该接口的对应文件,然后一行一行的读取文件中的实现类,使用java反射机制实例化接口的实现对象。
三 Dubbo SPI
dubbo框架中大量使用了SPI技术,里面有很多个组件,每个组件在框架中都是以接口的形成抽象出来!具体的实现又分很多种,在程序执行时根据用户的配置来按需取接口的实现,方便了接口的各种实现灵活应用。
以Protocol 接口为例,dubbo中有一段这样的代码:
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
感觉这里的ExtensionLoader类有点类似JDK中的ServiceLoader类。
看getExtensionLoader()的代码:
@SuppressWarnings("unchecked") public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) throw new IllegalArgumentException("Extension type == null"); if (!type.isInterface()) { throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); } ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }
private ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
这段代码的意思很明显,
1.EXTENSION_LOADERS这个Map中以接口为key,以ExtensionLoader对象为value。
2.判断Map中根据接口get对象,如果没有就new个ExtensionLoader对象保存进去。并返回该ExtensionLoader对象。
3.注意创建ExtensionLoader对象的构造函数代码,将传入的接口type属性赋值给了ExtensionLoader类的type属性
4.创建ExtensionFactory objectFactory对象,构造方法中也是这种类似的方式。
getAdaptiveExtension()的代码:
1 @SuppressWarnings("unchecked") 2 public T getAdaptiveExtension() { 3 Object instance = cachedAdaptiveInstance.get(); 4 if (instance == null) { 5 if (createAdaptiveInstanceError == null) { 6 synchronized (cachedAdaptiveInstance) { 7 instance = cachedAdaptiveInstance.get(); 8 if (instance == null) { 9 try { 10 instance = createAdaptiveExtension(); 11 cachedAdaptiveInstance.set(instance); 12 } catch (Throwable t) { 13 createAdaptiveInstanceError = t; 14 throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t); 15 } 16 } 17 } 18 } else { 19 throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); 20 } 21 } 22 23 return (T) instance; 24 }
@SuppressWarnings("unchecked") private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e); } }
createAdaptiveExtension()方法返回了一个injectExtension实例,封装了AdaptiveExtension,就是一个动态代理。
1 private Class<?> getAdaptiveExtensionClass() { 2 getExtensionClasses(); 3 if (cachedAdaptiveClass != null) { 4 return cachedAdaptiveClass; 5 } 6 return cachedAdaptiveClass = createAdaptiveExtensionClass(); 7 } 8 9 private Class<?> createAdaptiveExtensionClass() { 10 String code = createAdaptiveExtensionClassCode(); 11 ClassLoader classLoader = findClassLoader(); 12 com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); 13 return compiler.compile(code, classLoader); 14 }
1 private Map<String, Class<?>> getExtensionClasses() { 2 Map<String, Class<?>> classes = cachedClasses.get(); 3 if (classes == null) { 4 synchronized (cachedClasses) { 5 classes = cachedClasses.get(); 6 if (classes == null) { 7 classes = loadExtensionClasses(); 8 cachedClasses.set(classes); 9 } 10 } 11 } 12 return classes; 13 }
1 private Map<String, Class<?>> loadExtensionClasses() { 2 final SPI defaultAnnotation = type.getAnnotation(SPI.class); 3 if (defaultAnnotation != null) { 4 String value = defaultAnnotation.value(); 5 if (value != null && (value = value.trim()).length() > 0) { 6 String[] names = NAME_SEPARATOR.split(value); 7 if (names.length > 1) { 8 throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() 9 + ": " + Arrays.toString(names)); 10 } 11 if (names.length == 1) cachedDefaultName = names[0]; 12 } 13 } 14 15 Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); 16 loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY); 17 loadFile(extensionClasses, DUBBO_DIRECTORY); 18 loadFile(extensionClasses, SERVICES_DIRECTORY); 19 return extensionClasses; 20 }
1 private void loadFile(Map<String, Class<?>> extensionClasses, String dir) { 2 String fileName = dir + type.getName(); 3 try { 4 Enumeration<java.net.URL> urls; 5 ClassLoader classLoader = findClassLoader(); 6 if (classLoader != null) { 7 urls = classLoader.getResources(fileName); 8 } else { 9 urls = ClassLoader.getSystemResources(fileName); 10 } 11 if (urls != null) { 12 while (urls.hasMoreElements()) { 13 java.net.URL url = urls.nextElement(); 14 try { 15 BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8")); 16 try { 17 String line = null; 18 while ((line = reader.readLine()) != null) { 19 final int ci = line.indexOf('#'); 20 if (ci >= 0) line = line.substring(0, ci); 21 line = line.trim(); 22 if (line.length() > 0) { 23 try { 24 String name = null; 25 int i = line.indexOf('='); 26 if (i > 0) { 27 name = line.substring(0, i).trim(); 28 line = line.substring(i + 1).trim(); 29 } 30 if (line.length() > 0) { 31 Class<?> clazz = Class.forName(line, true, classLoader); 32 if (!type.isAssignableFrom(clazz)) { 33 throw new IllegalStateException("Error when load extension class(interface: " + 34 type + ", class line: " + clazz.getName() + "), class " 35 + clazz.getName() + "is not subtype of interface."); 36 } 37 if (clazz.isAnnotationPresent(Adaptive.class)) { 38 if (cachedAdaptiveClass == null) { 39 cachedAdaptiveClass = clazz; 40 } else if (!cachedAdaptiveClass.equals(clazz)) { 41 throw new IllegalStateException("More than 1 adaptive class found: " 42 + cachedAdaptiveClass.getClass().getName() 43 + ", " + clazz.getClass().getName()); 44 } 45 } else { 46 try { 47 clazz.getConstructor(type); 48 Set<Class<?>> wrappers = cachedWrapperClasses; 49 if (wrappers == null) { 50 cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); 51 wrappers = cachedWrapperClasses; 52 } 53 wrappers.add(clazz); 54 } catch (NoSuchMethodException e) { 55 clazz.getConstructor(); 56 if (name == null || name.length() == 0) { 57 name = findAnnotationName(clazz); 58 if (name == null || name.length() == 0) { 59 if (clazz.getSimpleName().length() > type.getSimpleName().length() 60 && clazz.getSimpleName().endsWith(type.getSimpleName())) { 61 name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase(); 62 } else { 63 throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url); 64 } 65 } 66 } 67 String[] names = NAME_SEPARATOR.split(name); 68 if (names != null && names.length > 0) { 69 Activate activate = clazz.getAnnotation(Activate.class); 70 if (activate != null) { 71 cachedActivates.put(names[0], activate); 72 } 73 for (String n : names) { 74 if (!cachedNames.containsKey(clazz)) { 75 cachedNames.put(clazz, n); 76 } 77 Class<?> c = extensionClasses.get(n); 78 if (c == null) { 79 extensionClasses.put(n, clazz); 80 } else if (c != clazz) { 81 throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); 82 } 83 } 84 } 85 } 86 } 87 } 88 } catch (Throwable t) { 89 IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t); 90 exceptions.put(line, e); 91 } 92 } 93 } // end of while read lines 94 } finally { 95 reader.close(); 96 } 97 } catch (Throwable t) { 98 logger.error("Exception when load extension class(interface: " + 99 type + ", class file: " + url + ") in " + url, t); 100 } 101 } // end of while urls 102 } 103 } catch (Throwable t) { 104 logger.error("Exception when load extension class(interface: " + 105 type + ", description file: " + fileName + ").", t); 106 } 107 }
loadExtensionClasses方法判断ExtensionLoader类中的传入的type接口是否标注了SPI注解,并获取SPI注解的值。loadFile方法用来加载配置路径下的接口的实现类。
这里加载的路径为:
private static final String SERVICES_DIRECTORY = "META-INF/services/"; private static final String DUBBO_DIRECTORY = "META-INF/dubbo/"; private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
看一下文件META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol的内容:
registry=com.alibaba.dubbo.registry.integration.RegistryProtocol dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper mock=com.alibaba.dubbo.rpc.support.MockProtocol injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol com.alibaba.dubbo.rpc.protocol.http.HttpProtocol com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol
这里的机制跟jdk很相似,只不过这里文件里面是key=value的形式,前面我们讲了dubbo框架的协议有hession,http,rmi,webservice,dubbo等好几种实现,所以可以很方便的根据用户的配置取那种协议的实现。
createAdaptiveExtensionClassCode()的代码太长了,就不一行一行的贴出来了。如果代理类生成好了 ,接下来看下injectExtension的构造方法:
1 private T injectExtension(T instance) { 2 try { 3 if (objectFactory != null) { 4 for (Method method : instance.getClass().getMethods()) { 5 if (method.getName().startsWith("set") 6 && method.getParameterTypes().length == 1 7 && Modifier.isPublic(method.getModifiers())) { 8 Class<?> pt = method.getParameterTypes()[0]; 9 try { 10 String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; 11 Object object = objectFactory.getExtension(pt, property); 12 if (object != null) { 13 method.invoke(instance, object); 14 } 15 } catch (Exception e) { 16 logger.error("fail to inject via method " + method.getName() 17 + " of interface " + type.getName() + ": " + e.getMessage(), e); 18 } 19 } 20 } 21 } 22 } catch (Exception e) { 23 logger.error(e.getMessage(), e); 24 } 25 return instance; 26 }
设置相应的属性并返回该实例。那么返回了代理实例,dubbo怎么知道具体的实现类是什么呢。看一下DubboProtocol的代码:
public static DubboProtocol getDubboProtocol() { if (INSTANCE == null) { ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(DubboProtocol.NAME); // load } return INSTANCE; }
public static final String NAME = "dubbo";
调用了ExtensionLoader的getExtension方法,其中name参数就是用户配置的协议名称,没有配置就是默认的dubbo:
1 @SuppressWarnings("unchecked") 2 public T getExtension(String name) { 3 if (name == null || name.length() == 0) 4 throw new IllegalArgumentException("Extension name == null"); 5 if ("true".equals(name)) { 6 return getDefaultExtension(); 7 } 8 Holder<Object> holder = cachedInstances.get(name); 9 if (holder == null) { 10 cachedInstances.putIfAbsent(name, new Holder<Object>()); 11 holder = cachedInstances.get(name); 12 } 13 Object instance = holder.get(); 14 if (instance == null) { 15 synchronized (holder) { 16 instance = holder.get(); 17 if (instance == null) { 18 instance = createExtension(name); 19 holder.set(instance); 20 } 21 } 22 } 23 return (T) instance; 24 }
1 @SuppressWarnings("unchecked") 2 private T createExtension(String name) { 3 Class<?> clazz = getExtensionClasses().get(name); 4 if (clazz == null) { 5 throw findException(name); 6 } 7 try { 8 T instance = (T) EXTENSION_INSTANCES.get(clazz); 9 if (instance == null) { 10 EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance()); 11 instance = (T) EXTENSION_INSTANCES.get(clazz); 12 } 13 injectExtension(instance); 14 Set<Class<?>> wrapperClasses = cachedWrapperClasses; 15 if (wrapperClasses != null && wrapperClasses.size() > 0) { 16 for (Class<?> wrapperClass : wrapperClasses) { 17 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); 18 } 19 } 20 return instance; 21 } catch (Throwable t) { 22 throw new IllegalStateException("Extension instance(name: " + name + ", class: " + 23 type + ") could not be instantiated: " + t.getMessage(), t); 24 } 25 }
这样就获取相应的protocol实现返回。dubbo中的很多接口都使用这样的方式,这样做让程序非常的灵活,让接口的实现插拔更加方便。如果想增加一个接口的实现,只需要按照SPI的配置方式增加配置文件,不需要修改以前其他类的代码。