Java SPI源码加载全过程详解

这里写目录标题

  • 1、概念
  • 2、使用方法
  • 3、源码解析
          • 1. 如测试代码所示,从ServiceLoaderload方法进入初始化,再通过迭代器加载所有的实现类。
          • 2. ServiceLoader中的属性和内部类
          • 3. load方法的源码
          • 4. ServiceLoader实现了Iterable接口,其唯一的遍历方式也是通过Iterator迭代器,即其内部类LazyIterator,每次的next()操作会初始化一个实现类并生成一个实例
  • 4、总结

1、概念

SPI 全称是 Service Provider Interface ,中文意思是服务提供商接口。起初是给服务商做插件开发的,是为了满足随时可插拔的需求而诞生的一种机制。

Java SPI 是一种本地服务发现机制。使用的是策略模式,一个接口多种实现,使用时根据标准化接口配置文件灵活生成实现类,使得接口易扩展。


2、使用方法

  1. 定义一个接口及相关方法
  2. 编写该接口的实现类
  3. 在META-INF/services/目录下,创建一个以接口全限定名命名的文件,如com.example.demo.PrintService
  4. 文件内容为每行一个具体实现类的全限定名,若注释可以写在#后面
  5. 在代码中通过java.util.ServiceLoader来加载具体的实现类

举例:

  1. 项目结构:接口+接口实现类+配置文件
    Java SPI源码加载全过程详解_第1张图片

  2. 接口及实现类代码

//接口及对应方法
public interface PrintService {
     
     void print();
}
//实现类1
public class PrintServiceImpl1 implements PrintService {
     

    @Override
    public void print() {
     
        System.out.println("This is PrintServiceImpl-1");
    }
}
//实现类2
public class PrintServiceImpl2 implements PrintService {
     
    @Override
    public void print() {
     
        System.out.println("This is PrintServiceImpl-2");
    }
}
  1. 配置文件com.example.demo.service.PrintService
com.example.demo.impl.PrintServiceImpl2
com.example.demo.impl.PrintServiceImpl1
  1. 测试
//测试代码
public class DemoApplication {
     

    public static void main(String[] args) {
     

        ServiceLoader<PrintService>services=ServiceLoader.load(PrintService.class);
        for(PrintService printService:services){
     
            printService.print();
        }
    }
}

Java SPI源码加载全过程详解_第2张图片


3、源码解析

1. 如测试代码所示,从ServiceLoaderload方法进入初始化,再通过迭代器加载所有的实现类。
2. ServiceLoader中的属性和内部类
public final class ServiceLoader<S> implements Iterable<S>{
     

	//接口全限定名文件的路径前缀
    private static final String PREFIX = "META-INF/services/";

	//需要加载的接口
    // The class or interface representing the service being loaded
    private final Class<S> service;

	//用于加载接口实现类的类加载器
    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;
	
	//创建serviceloader时获取的访问控制上下文 
    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;

	//服务缓存池,key:接口实现类的全限定名,value:实现类
    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

	//实现类的迭代器,延迟加载
    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;

	...省略已实现方法

	// Private inner class implementing fully-lazy provider lookup
    //
    private class LazyIterator implements Iterator<S>{
     
		//接口类型
        Class<S> service;
        //类加载器
        ClassLoader loader;
        //META-INF/services/内配置文件的URL集合
        Enumeration<URL> configs = null;
        //需加载的实现类的全限定名集合
        Iterator<String> pending = null;
        //下一个实现类的全限定名,用于迭代器延迟加载下一个实现类
        String nextName = null;

		...省略已实现方法
	}

}
3. load方法的源码

ServiceLoader.load(Class)

 public static <S> ServiceLoader<S> load(Class<S> service) {
     
 		//获取类加载器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        //进入load的重载方法
        return ServiceLoader.load(service, cl);
    }

进入load重载方法

public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
     
		//按接口和类加载器生成新的对象
        return new ServiceLoader<>(service, loader);
    }

ServiceLoader类构造器

private ServiceLoader(Class<S> svc, ClassLoader cl) {
     
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        //类加载器为空则初始化
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        //访问控制器初始化
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        //重置缓存
        reload();
    }

reload方法

public void reload() {
     
		//服务缓存池清空
        providers.clear();
        //初始化迭代器
        lookupIterator = new LazyIterator(service, loader);
    }

LazyIterator类构造器

private LazyIterator(Class<S> service, ClassLoader loader) {
     
            this.service = service;
            this.loader = loader;
        }

由上可见load方法并未加载具体的实现类,只对ServiceLoad类进行了初始化。接口实现类在使用迭代器遍历时才开始加载。

4. ServiceLoader实现了Iterable接口,其唯一的遍历方式也是通过Iterator迭代器,即其内部类LazyIterator,每次的next()操作会初始化一个实现类并生成一个实例

迭代器从LazyIterator.next()进入,调用的是LazyIterator.nextService()

private S nextService() {
     
			//判断是否还有下一个类需要加载
            if (!hasNextService())
                throw new NoSuchElementException();
            //cn是文件中实现类的全限定名,如com.example.demo.impl.PrintServiceImpl2
            String cn = nextName;
            //nextName置空会在hasNextService()中重新分配下一个全限定名
            nextName = null;
            Class<?> c = null;
            try {
     
            	//通过反射来生成接口实现类
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
     
                fail(service,
                     "Provider " + cn + " not found");
            }
            //校验实现类c是否 和接口相同或是该类的子类
            if (!service.isAssignableFrom(c)) {
     
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
     
            	//生成实现类的一个实例,并对实例进行强制类型转换
                S p = service.cast(c.newInstance());
                //放入缓存池,key:实现类全限定名 value:一个实例
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
     
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

在进入nextService()前会先执行hasNextService()方法,生成nextService()中所需的实现类全限定名nextName

private boolean hasNextService() {
     
			//全限定名不为空说明还未生产对应类
            if (nextName != null) {
     
                return true;
            }
            //若配置文件URL未获取则去获取所有URL
            if (configs == null) {
     
                try {
     
                	//拼接配置文件路径
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
     
                    fail(service, "Error locating configuration files", x);
                }
            }
            //获取该接口配置文件中是的所有全限定名的迭代器
            while ((pending == null) || !pending.hasNext()) {
     
                if (!configs.hasMoreElements()) {
     
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            //获取下一个实现类全限定名
            nextName = pending.next();
            return true;
        }

parse(Class service, URL u)方法会解析URL对应的资源文件,对每一行进行解析

private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError
    {
     
        InputStream in = null;
        BufferedReader r = null;
        ArrayList<String> names = new ArrayList<>();
        try {
     
            in = u.openStream();
            //字符流读取文件
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            //解析每一行
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
     
            fail(service, "Error reading configuration file", x);
        } finally {
     
            try {
     
                if (r != null) r.close();
                if (in != null) in.close();
            } catch (IOException y) {
     
                fail(service, "Error closing configuration file", y);
            }
        }
        return names.iterator();
    }

parseLine(Class service, URL u, BufferedReader r, int lc, List names)解析一行字符串并加入全限定名集合

private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) 
		throws IOException, ServiceConfigurationError
    {
     
    	//读取一行
        String ln = r.readLine();
        if (ln == null) {
     
            return -1;
        }
        //#后面为注释,只截取#前的字符串
        int ci = ln.indexOf('#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        
        int n = ln.length();
        if (n != 0) {
     
        	//检查是否存在不合法的符号
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            //检查首字母是否合法
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            //判断其他字符是否合法
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
     
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            //不存在则加入全限定名的缓存池
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
    }

4、总结

Java SPI 是通过反射生成文件中全限定名对应的类,并交由接口或父类管理。它能使第三方插件和业务代码分离。其虽然采用了延迟加载的形式,但使用时会对文件进行全量遍历来生成所有实现类及其对应实例,比较耗费资源。

如果有疑问,欢迎评论~
如果成功解决了你的问题,点个赞再走吖~

你可能感兴趣的:(java,spi,1024程序员节)