Java SPI机制学习笔记

摘要:
JavaSPI实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。SPI的应用之一是可替换的插件机制。packageautocomplete;importjava.util.Iterator;importjava.util.ServiceLoader;/***Createdbylovesqccon16-2-29.*/publicclassPrefixMatcherTest{publicstaticvoidmain{ServiceLoadermatcher=ServiceLoader.load;IteratormatcherIter=matcher.iterator();while{PrefixMatcherwordMatcher=matcherIter.next();System.out.println;String[]prefixes=newString[]{"a","b","c","d","e","f","g","i","l","n","p","r","s","t","v","w","do","finally"};for{System.out.println;}}}}要写个ServiceLoader的简单实现也不难:1.读取配置文件,获取实现类的全名称字符串;2.使用Java反射机制来构造服务实现类的实例。不过JDK自带的java.util.ServiceLoader实现得更加严谨一些,使用了ClassLoader来加载类,并使用迭代器来获取服务实现类。

最近在阅读框架源代码时,常常看到 SPI 的子包, 忍不住查了下: Service Provider Interface : 服务提供接口。

JavaSPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。具体而言:

STEP1. 定义一组接口, 假设是 autocomplete.PrefixMatcher;

STEP2. 写出接口的一个或多个实现(autocomplete.EffectiveWordMatcher,autocomplete.SimpleWordMatcher);

STEP3. 在 src/main/resources/ 下建立 /META-INF/services 目录, 新增一个以接口命名的文件autocomplete.PrefixMatcher, 内容是要应用的实现类(autocomplete.EffectiveWordMatcher 或 autocomplete.SimpleWordMatcher 或两者);

STEP4. 使用 ServiceLoader 来加载配置文件中指定的实现。

SPI 的应用之一是可替换的插件机制。比如查看 JDBC 数据库驱动包,mysql-connector-java-5.1.18.jar就有一个 /META-INF/services/java.sql.Driver 里面内容是com.mysql.jdbc.Driver 。

Java SPI机制学习笔记第1张

代码示例:

1. 编写接口和实现类: autocomplete.PrefixMatcher, autocomplete.EffectiveWordMatcher,autocomplete.SimpleWordMatcher 见《输入自动提示与补全功能的设计与实现》

2. 在 src/main/resources/ 下建立文件 /META-INF/services/ autocomplete.PrefixMatcher 填入上述两个类之一或两者都填; 

3. 编写测试类。

复制代码
package autocomplete;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
 * Created by lovesqcc on 16-2-29.
 */
public class PrefixMatcherTest {
    public static void main(String[] args) {
        ServiceLoader<PrefixMatcher> matcher = ServiceLoader.load(PrefixMatcher.class);
        Iterator<PrefixMatcher> matcherIter = matcher.iterator();
        while (matcherIter.hasNext()) {
            PrefixMatcher wordMatcher = matcherIter.next();
            System.out.println(wordMatcher.getClass().getName());
            String[] prefixes = new String[] {"a", "b", "c", "d", "e", "f", "g", "i",
                    "l", "n", "p", "r", "s", "t", "v", "w", "do", "finally"};
            for (String prefix: prefixes) {
                System.out.println(wordMatcher.obtainMatchedWords(prefix));
            }
        }
    }
}
复制代码

要写个 ServiceLoader 的简单实现也不难: 1. 读取配置文件,获取实现类的全名称字符串; 2. 使用 Java 反射机制来构造服务实现类的实例。可以使用泛型方法,避免获取的时候做类型转换。不过 JDK 自带的java.util.ServiceLoader 实现得更加严谨一些,使用了 ClassLoader 来加载类,并使用迭代器来获取服务实现类。思路大体相同。

复制代码
package autocomplete;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
 * Created by lovesqcc on 16-2-29.
 * A very Simple JavaSPI implementation using java reflection
 */
public class SimpleServiceLoader {
    private static final String PREFIX = "/META-INF/services/";
    public static <T> List<T> load(Class<T> cls) {
        List<String> implClasses = readServiceFile(cls);
        List<T> implList = new ArrayList<T>();
        for (String implClass : implClasses) {
            Class<T> c = null;
            try {
                c = (Class<T>) Class.forName(implClass);
                implList.add(c.newInstance());
            } catch (Exception e) {
                return new ArrayList<T>();
            }
        }
        return implList;
    }
    private static List<String> readServiceFile(Class<?> cls) {
        String infName = cls.getCanonicalName();
        String fileName = cls.getResource(PREFIX+infName).getPath();
        try {
            BufferedReader br = new BufferedReader(new FileReader(new File(fileName)));
            String line = "";
            List<String> implClasses = new ArrayList<String>();
            while ((line = br.readLine()) != null) {
                implClasses.add(line);
            }
            return implClasses;
        } catch (FileNotFoundException fnfe) {
            System.out.println("File not found: " + fileName);
            return new ArrayList<String>();
        } catch (IOException ioe) {
            System.out.println("Read file failed: " + fileName);
            return new ArrayList<String>();
        }
    }
    public static void main(String[] args) {
        List<PrefixMatcher> implList = load(PrefixMatcher.class);
        if (implList != null && implList.size() >0) {
            for (PrefixMatcher matcher: implList) {
                System.out.println(matcher.obtainMatchedWords("sh"));
            }
        }
    }
}
复制代码

ServiceLoader 的实现涉及到如下概念: 指向对象类型的 Class<S> 对象; 类加载器 ClassLoader; 服务实现类的资源抽象; 服务实现类的全名字符串。结合类加载器和资源抽象获得服务实现类的全名字符串,再通过类加载器获取 Class<S> 对象, 最后通过 Class<S> 对象来构造服务实现类 S 的实例 s 。

ServiceLoader 的成员为 <Class<S> service, ClassLoader loader, LinkedHashMap<String,S> providers, LazyIterator lookupIterator>, 其中 service 是服务接口,loader 是类加载器, providers 是服务实现类的缓存, lookupIterator 是获取服务实现类的迭代器,是 ServiceLoader 的内部类。 LazyIterator 的成员是 <Class<S> service, ClassLoader loader,Enumeration<URL> configs,Iterator<String> pending, String nextName>, configs 存放服务实现类的资源配置抽象, pending 存放服务实现类的全名字符串, nextName 是下一个可获取的服务实现类的全名字符串。加载资源使用到 classLoader 的getSystemResources 和 getResources 方法。Java里的资源抽象使用类 URL 来唯一标识,无论是本地文件 ( file:/// ) 还是网络文件 (http(s):// )。由于要从文件或网络读取文本字符串,因此要使用 BufferedReader 。

在 Java 中,Class<T> 和 ClassLoader 是造物之始。万物皆是“某类T” 的存在物,而“某类T” 是“万类之类 Class<T>” 的存在物,类别也是一种存在物,存在物即 Object。实例 t -> 类别 T -> 所有类别的抽象 Class<T> -> Object。要创造类别 T 的实例,先通过某种方式(ClassLoader)找到该物的“种子”(Class<T> 对象),然后通过该种子来创造具体的物 t。要生成一个 Integer 对象,先找到 Class<Integer> , 然后 newInstance 出 Integer 的实例。而造物也要有个规则,“女娲造物”和“凡人造物”,如果要造一模一样的物种,必须先经由女娲造物,否则就会造成混乱(至少软件中会出现问题)。在 Java 里就有 BootstrapClassLoader -> ExtClassLoader -> AppClassLoader -> CustomClassLoader 的先后规则。关于类加载器可参见 【《Java类加载器总结》《深入探讨Java类加载器》】,阅读一个 ClassLoader 的实现。

作者:@琴水玉

转载请注明出处:https://www.cnblogs.com/lovesqcc/p/5229353.html

免责声明:文章转载自《Java SPI机制学习笔记》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇APMServ5.2.6win10系统Apache、MySQL5.1启动失败解决办法kafka消费数据策略下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

5分钟让你学会用最高效的工具解析所有Json

如果你是一个Android开发工程师,学会解析Json字符串是你的必修课,本篇文章主要以实例的方式手把手教你怎么做,花五分钟时间阅读本篇文章你就可以学会解析所有的Json字符串啦。 准备: json字符串 fastjson HiJson格式化json工具 开始教程: fastjson: 常用工作中解析json的工具类有谷歌的GSON,jackson...

ftp上传文件

  /// 上传文件   /// </summary>   /// <param name="fileBytes"></param>   /// <param name="originalName"></param>   ///...

C#编程总结(八)数字签名

C#编程总结(八)数字签名 在日常工作中,有很多文件需要领导审阅、签名和盖章,由于公司业务开展,跨地域、跨国业务也日益普遍,领导签名盖章变得很麻烦,开始的时候人们通过邮寄、传真等方式来解决,但是耗费时间、人力、物力。在网络化日益深入的今天,需要领导审批、签字盖章的东西越来越多,时间也越来越紧迫,数字签名的出现,很好了解决了这一问题。推动了互联网及跨国集团的...

.NET链接Oracle 参数绑定问题

在.NET项目中链接Oracle使用的驱动是 Oracle.ManagedDataAccess.dll ,这里下载 所遇到的问题 使用存储过程一个参数没有问题,发现两个或两个以上会有参数没传过来的现象。 最后通过排查发现是没有添加参数绑定(问题找了好长时间,刚开始还以为驱动的问题+_+)。 需要设置设置属性 BindByName = true; 下面附上 ...

C#中String跟string的“区别”

  string是c#中的类,String是.net Framework的类(在C# IDE中不会显示蓝色) C# string映射为.net Framework的String 如果用string,编译器会把它编译成String,所以如果直接用String就可以让编译器少做一点点工作。   如果使用C#,建议使用string,比较符合规范 string始终...

ftp操作方法整理

1.整理简化了下C#的ftp操作,方便使用    1.支持创建多级目录    2.批量删除    3.整个目录上传    4.整个目录删除    5.整个目录下载 2.调用方法展示, var ftp = new FtpHelper("10.136.12.11", "qdx1213123", "123ddddf");//初始化ftp,...