SPI机制

# 什么是SPI

SPI(Service Provider Interface),是JDK内置的一种服务发现机制,用来将调用方与服务提供方解耦,通常用于框架开发、组件替换。比如Javajava.sql.Driver接口,其他数据库厂商MySQL、PostgreSQL提供不同的实现。通过SPI机制,我们在使用数据库驱动时不需要显示指定某个厂家的,只需要引入驱动jar包,系统即可完成加载。

# 代码示例

# 服务接口定义

package cn.wangjie.learn.spi;

public interface IShout {
    void shout();
}
1
2
3
4
5

# 服务提供方实现接口

package cn.wangjie.learn.spi;

public class Cat implements IShout{
    @Override
    public void shout() {
        System.out.println("cat shout:喵喵喵");
    }
}
1
2
3
4
5
6
7
8
package cn.wangjie.learn.spi;

public class Dog implements IShout{
    @Override
    public void shout() {
        System.out.println("dog shout:汪汪汪");
    }
}
1
2
3
4
5
6
7
8

# 服务提供方在resources下新建META-INF/services/,使用接口全限定名新建文件cn.wangjie.learn.spi.IShout

cn.wangjie.learn.spi.Dog
cn.wangjie.learn.spi.Cat
1
2

# 调用方调用

package cn.wangjie.learn.spi;

import java.util.*;

public class SPIMain {
    public static void main(String[] args) {
        ServiceLoader<IShout> shouts = ServiceLoader.load(IShout.class);
        for (IShout shout : shouts) {
            shout.shout();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 运行打印日志

dog shout:汪汪汪
cat shout:喵喵喵
1
2

# 服务发现原理

查看ServiceLoader类结构 picture 进入ServiceLoader.load方法

public static <S> ServiceLoader<S> load(Class<S> service) {
    //获取当前线程的类加载器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    //该方法return new ServiceLoader<>(service, loader)
    return ServiceLoader.load(service, cl);
}
1
2
3
4
5
6
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();
}
1
2
3
4
5
6
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

private LazyIterator lookupIterator;

public void reload() {
    providers.clear();
    //提供一个懒加载的迭代器
    lookupIterator = new LazyIterator(service, loader);
}
1
2
3
4
5
6
7
8
9

ServiceLoader实现了Iterable接口

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();
        }

    };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

当获取服务提供类时,通过迭代器遍历

先从providers中获取已经加载过的,之后从lookupIterator中加载

查看LazyIterator

private static final String PREFIX = "META-INF/services/";

private class LazyIterator
    implements Iterator<S>
{

    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null;

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

    private boolean hasNextService() {
        if (nextName != null) {
            return true;
        }
        if (configs == null) {
            try {
                //加载META-INF/services/+全限定名的文件  多个服务提供方可能在不同的jar包,可能会加载多个资源文件
                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;
    }

    private S nextService() {
        if (!hasNextService())
            throw new NoSuchElementException();
        String cn = nextName;
        nextName = null;
        Class<?> c = null;
        try {
            //加载服务提供类
            c = Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            fail(service,
                    "Provider " + cn + " not found");
        }
        if (!service.isAssignableFrom(c)) {
            fail(service,
                    "Provider " + cn  + " not a subtype");
        }
        try {
            //实例化服务提供类  服务提供类需要有无参构造方法
            S p = service.cast(c.newInstance());
            //加载过的放到list中
            providers.put(cn, p);
            return p;
        } catch (Throwable x) {
            fail(service,
                    "Provider " + cn + " could not be instantiated",
                    x);
        }
        throw new Error();          // This cannot happen
    }

    public boolean hasNext() {
        if (acc == null) {
            return hasNextService();
        } else {
            PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                public Boolean run() { return hasNextService(); }
            };
            return AccessController.doPrivileged(action, acc);
        }
    }

    public S next() {
        if (acc == null) {
            return nextService();
        } else {
            PrivilegedAction<S> action = new PrivilegedAction<S>() {
                public S run() { return nextService(); }
            };
            return AccessController.doPrivileged(action, acc);
        }
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

JDK自带的SPI主要是通过加载META-INF/services/+全限定名的文件获取服务提供类,然后迭代器遍历每一个服务提供类,通过反射实例化服务提供类。

# SPI机制的缺陷

  1. 只能通过迭代器遍历,逐个实例化服务提供类
  2. ServiceLoader非线程安全类
上次更新: 2023/04/09, 16:34:32
最近更新
01
go-admin-ui项目仿写练手1-登录页
06-29
02
maven依赖问题
06-17
03
JVM相关命令
02-21
更多文章>