SPI(service provider interface)详解

概述

SPI全称为service provider interface ,是JDK自带的一种服务提供发现机制,类似于一种小型的IOC容器,可以自动依赖注入,动态替换服务扩展.一般用于提供第三方实现和扩展,可以增加框架的扩展和替换一些组件.目前很多框架使用了这个机制,比如spring,dubbo,还是数据库驱动包,设置JDK里面的NIO包中的SelectorProvider也是使用的这个机制.

SPI约定

  1. 在META-INF/services/目录中创建接口的权限名命名的文件,该文件内容为接口实现的全限名,文件编码为UTF-8
  2. 使用SeriveLoader.load(Class class) 动态加载接口的实现
  3. 实现类必须有一个无参构造
  4. 如果SPI的实现是一个jar,放在项目的classpath目录下

springmvc对servlet3.0的支持

在servlet3.0情况下,可以不使用web.xml文件,可以使用WebApplicationInitializer来初始化一个WebApplicationContext.

WebApplicationInitializer加载机制:javaEE容器启动的时候会通过SPI机制寻找javax.servlet.ServletContainerInitializer的实现类,在spring-web包下面,有这个文件的定义.

springweb_spi

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
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {

List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer) waiClass.newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}

if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}

AnnotationAwareOrderComparator.sort(initializers);
servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers);

for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}

在onStartup方法上面有一段这样的注释:

Because this class declares @{@code >HandlesTypes(WebApplicationInitializer.class)},
Servlet 3.0+ containers will automatically scan the classpath for implementations of Spring’s {@code WebApplicationInitializer} interface and provide the set of all
such types to the {@code webAppInitializerClasses} parameter of this >method.

因为这个类的注解是HandlesTypes(WebApplicationInitializer.class),servlet3.0容器会自动扫描classpath下面对WebApplicationInitializer接口的实现类,并以set类型的参数提供给SpringServletContainerInitializer.onStartup()方法

自己使用SPI特性实现一个service

新建一个spi-demo的工程,项目结构如下图:

springweb_spi

  1. 定义service接口

    1
    2
    3
    4
    5
    6
    package com.spi.demo.service;

    public interface UserService {

    String sayHello(String userName);
    }
  2. 提供provider实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.spi.demo.service.impl;

    import com.spi.demo.service.UserService;

    public class UserServiceImpl implements UserService {

    @Override
    public String sayHello(String userName) {
    return new StringBuffer("hello,").append(userName).append(".").toString();
    }
    }
  3. 添加配置
    resouces/META-INF/services/目录下新建com.spi.demo.service.UserService文件,编码为UTF-8

    文件内容如下:

    1
    com.spi.demo.service.impl.UserServiceImpl
  4. 运行测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package com.spi.demo;

    import com.spi.demo.service.UserService;
    import java.util.ServiceLoader;

    public class Console {

    public static void main(String[] args) {
    ServiceLoader<UserService> serviceLoader = ServiceLoader.load(UserService.class);
    serviceLoader.iterator()
    .forEachRemaining(userService -> System.out.println(userService.sayHello("张三")));
    }

    }

    运行输出结果如下

    1
    hello,张三.

参考资料

  1. https://en.wikipedia.org/wiki/Service_provider_interface
  2. https://docs.oracle.com/javase/8/docs/api/