在前面分析的序列化的文章中,我们看到有这么一段代码:

/**
 * 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的配置方式增加配置文件,不需要修改以前其他类的代码。

 

 

 

posted on 2019-01-15 17:05  jameszheng  阅读(458)  评论(0编辑  收藏  举报