记录一次JDK SPI的使用,并能在Service Loader加载实现类后将其注入到Spring容器中,如此可以在实现类中使用自动注入其他依赖等。

Service Provider Interface

SPI工程目录大致如下,基于Spring Boot搭建,使用到Lombok(可视情况去除):

src
    └─com
        └─ym
            └─demo
                └─spi
                    ├─ extra
                    │  ├─ SPIBeanAutoWire.java
                    │  └─ SpringUtil.java
                    │
                    ├─ service
                    │  └─ DemoSPIService.java
                    │
                    ├─ DemoManager.java
                    └─ ServiceLoaderHelper

SPI对外开放接口类

需要在使用包定义实现类,并且按照ServiceLoader约定标准文件方式以该类的全类名(包名+类名)新建文件

public interface DemoManager {
    Object someMethod(String param);
}

SPI的实际使用Service

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

//使用懒加载保证实现类加载
@Lazy
@Service
@Slf4j
public class DemoSPIService {
    private final DemoManager demoManager;

    public DemoSPIService() {
        demoManager = ServiceLoaderHelper.loadSingleService(DemoManager.class, true);
    }

    public Object someMethod(String param) {
        if (demoManager == null) {
            log.error("未发现 DemoManager 服务提供者");
            return null;
        }

        return demoManager.someMethod(param);
    }
}

ServiceLoader的工具类

import org.springframework.beans.factory.config.AutowireCapableBeanFactory;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;

public class ServiceLoaderHelper {

    public static <T> List<T> loadServices(Class<T> spiClazz) {
        ServiceLoader<T> serviceLoader = ServiceLoader.load(spiClazz);
        Iterator<T> itr = serviceLoader.iterator();
        List<T> services = new ArrayList<T>();
        while (itr.hasNext()) {
            services.add(itr.next());
        }
        return services;
    }

    public static <T> T loadSingleService(Class<T> spiClazz) {
        return loadSingleService(spiClazz, false);
    }

    /**
     * 加载单个服务
     *
     * @param allowNoService 支持无实现可运行
     */
    public static <T> T loadSingleService(Class<T> spiClazz, boolean allowNoService) {
        ServiceLoader<T> serviceLoader = ServiceLoader.load(spiClazz);
        Iterator<T> itr = serviceLoader.iterator();
        T service = null;
        while (itr.hasNext()) {
            if (service == null) {
                service = itr.next();
            } else {
                StringBuilder sb = new StringBuilder(service.getClass().getName());
                while (itr.hasNext()) {
                    sb.append(",").append(itr.next().getClass().getName());
                }
                // 这里需要结合实际情况进行处理,可以自己定义SystemConfigurationException继承RuntimeException
                throw new SystemConfigurationException("配置了多个" + spiClazz + "的实现,包括:" + sb.toString());
            }
        }
        //增加allowNoService支持
        if (service == null && !allowNoService) {
            throw new SystemConfigurationException("未配置" + spiClazz + "的实现。");
        }

        //判断是否自动注入Spring容器
        if (service instanceof SPIBeanAutowire) {
            AutowireCapableBeanFactory beanFactory = SpringUtil.getApplicationContext().getAutowireCapableBeanFactory();
            beanFactory.autowireBeanProperties(service, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
        }

        return service;
    }

}

用来判断是否注入Spring容器的接口类

public interface SPIBeanAutowire {
}

Spring容器工具类

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    public SpringUtil() {
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtil.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

SPI某服务使用示例

@Service
public class SomeService {
    //使用懒加载保证实现类加载
    @Lazy
    @Autowired
    private DemoSPIService demoSPIService;

    public void someServiceMethod(String param) {
        demoSPIService.someMethod(param);
    }
}

Service Provider

SPI的使用及对应实现工程。

SPI的实现类

public class DemoManagerImpl implements DemoManager, SPIBeanAutowire {
    @Autowired
    private OtherService otherService;

    @Override
    public Object someMethod(String param) {
        return otherService.anotherMethod(param);
    }
}

ServiceLoader的约定文件

文件路径:src/META-INF/services/

文件名:com.ym.demo.spi.DemoManager(该处为示例,取:SPI 的全类名)

文件内容:com.xxx.xxx.DemoManagerImpl(该处为示例,取:SPI实现类的全类名)