阅读 45

JDK的SPI 机制

这是我参与更文挑战的第2天,活动详情查看: 更文挑战 本文主要对JDK的SPI机制进行说明,以及相关的源码示例

1. 什么是JDK SPI 机制

  • 在项目的资源文件目录下创建META-INF/services/目录
  • META-INF/services/目录下,创建名称为接口限定名的文件, 如com.m.code.HelloService
  • 在以接口限定名的文件中将实现类的限定名称分行显示即可,如:
com.m.code.service.impl.HelloServiceImpl
com.m.code.service.impl.SimpleHelloServiceImpl
复制代码
  • 通过ServiceLoader.load方法即可加载指定接口的实现类

2. 源码分析

主要核心类是java.util.ServiceLoader

  • 在类里可以看到加载的目录
private static final String PREFIX = "META-INF/services/";
复制代码
  • 调用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();
    }
复制代码
  • 核心方法
        private S nextService() {
            // 判断是否还有service
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                // 创建 Class
                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());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }
复制代码

3. 实践

3.1 创建接口HelloService

/**
 * HelloService 接口
 */
public interface HelloService {
    void say();
}
复制代码

3.2 创建接口HelloService的实现类

/**
 * HelloService 接口实现类
 */
public class HelloServiceImpl implements HelloService {
    @Override
    public void say() {
        System.out.println("Hello!!!");
    }
}
复制代码
/**
 * HelloService 接口实现类
 * 简单实现
 */
public class SimpleHelloServiceImpl implements HelloService {
    @Override
    public void say() {
        System.out.println("simple Hello!");
    }
}
复制代码

3.3 在项目的resources下面新增META-INF/services/目录

并在目录下面创建一个名称为接口限定名的文件com.m.code.service.HelloService

3.4 在文件中填写对应接口的接口实现类的限定名称

com.m.code.service.impl.HelloServiceImpl
com.m.code.service.impl.SimpleHelloServiceImpl
复制代码

3.5 在方法中测试

/**
 * HelloService测试
 * 测试JDK SPI
 */
public class Main {
    public static void main(String[] args) {
        // 通过ServiceLoader加装HelloService的实现类
        ServiceLoader<HelloService> serviceLoader = ServiceLoader.load(HelloService.class);
        // 遍历打印一下结果
        for (HelloService next : serviceLoader) {
            next.say();
        }
    }
}
复制代码
文章分类
后端
文章标签