Java SPI

什么是SPI


SPI是jdk中的一种服务发现机制,在java中可以用来扩展API和第三方实现。相比于API,可以动态替换发现。

我的理解:SPI是一种动态服务发现方式。如果我们想要调用别人实现的方法,那么肯定是调用别人的实现类来操作。但是java里面是面向接口编程,实现多个类之间的解耦。所以SPI操作就是你可以通过调用接口来调用实现类。那么如何才能知道接口和实现类之间的绑定呢?就是通过配置文件,那么我们怎么加载配置文件,使用Classloader类加载器去加载,这样就可以了。

jdk中使用SPI方式


  1. 在jar包的META-INF/services目录下创建一个以"接口全限定名"为命名的文件,内容为实现类的全限定名。
  2. 接口实现类所在的jar包在classpath下。
  3. 主程序通过java.util.ServiceLoader动态状态实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM。
  4. SPI的实现类必须带一个无参构造方法。

自定义SPI实现同包调用


思路:

  • 创建META-INF/services目录,放在classpath下面(放在resources下面)。
  • 在META-INF/services目录下创建一个以"接口全限定名"为命名的文件,内容为实现类的全限定名。
  • 主程序通过java.util.ServiceLoader动态状态实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM。

目录结构:

com.sunpy.permissionservice.spi.ISpiSunpyService文件:

com.sunpy.permissionservice.spi.SpiSunpyServiceOne
com.sunpy.permissionservice.spi.SpiSunpyServiceTwo

接口和实现类:

public interface ISpiSunpyService {

    public void outMsg();
}

public class SpiSunpyServiceOne implements ISpiSunpyService{

    @Override
    public void outMsg() {
        System.out.println("SpiSunpyServiceOne服务信息");
    }
}

public class SpiSunpyServiceTwo implements ISpiSunpyService{

    @Override
    public void outMsg() {
        System.out.println("SpiSunpyServiceTwo服务信息");
    }
}

测试:

@Test
public void spiTest() {
    ServiceLoader serviceLoader = ServiceLoader.load(ISpiSunpyService.class);
    serviceLoader.forEach(ISpiSunpyService::outMsg);
}

自定义SPI实现引入jar包调用


public class SunpyTest {

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/services/com.sunpy.spi.ISpiService";


    public static Map> loadClassName() throws IOException {
        ClassLoader classLoader = SunpyTest.class.getClassLoader();
        Enumeration urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        Map> map = new HashMap<>();
        List list = new ArrayList<>();

        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            URLConnection urlConnection = url.openConnection();
            BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
            String content = "";

            while ((content = br.readLine()) != null) {
                list.add(content);
            }
            map.put(FACTORIES_RESOURCE_LOCATION, list);
        }

        return map;
    }

    public static void doMethod() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        Map> map = loadClassName();
        List list = map.get(FACTORIES_RESOURCE_LOCATION);
        for (String clazzName : list) {
            Class clazz = Class.forName(clazzName);

            Method method = clazz.getMethod("outMsg");
            method.invoke(clazz.newInstance());
        }

    }

    public static void main(String[] args) throws Exception {
        doMethod();
    }
}

SPI应用场景


  • springboot中的SPI
    配置文件:

获取spring中的实例,委派给SpringFactoriesLoader.loadFactoryNames加载全限定类名。

// 获取spring的实例
private  Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) {
    // 获取类加载器
    ClassLoader classLoader = getClassLoader();
    /**
     * 使用给定的类加载器从“META-INF/spring.factories”加载给定类型的工厂实现的全限定类名。
     */
    Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 创建实例集合
    List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

SpringFactoriesLoader.loadFactoryNames的实现

private static Map> loadSpringFactories(ClassLoader classLoader) {
    Map> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    result = new HashMap<>();
    try {
        // public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
        Enumeration urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                String[] factoryImplementationNames =
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                for (String factoryImplementationName : factoryImplementationNames) {
                    result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                            .add(factoryImplementationName.trim());
                }
            }
        }

        // Replace all lists with unmodifiable lists containing unique elements
        result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
        cache.put(classLoader, result);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    return result;
}

查看:

实例化类:

@SuppressWarnings("unchecked")
private  List createSpringFactoriesInstances(Class type, Class[] parameterTypes,
        ClassLoader classLoader, Object[] args, Set names) {
    List instances = new ArrayList<>(names.size());
    // 拿着加载的所有的全限定名,进行实例化
    for (String name : names) {
        try {
            // 反射加载全限定名的Class
            Class instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            // 反射获取Constructor类
            Constructor constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            // 反射通过构造器实例化类
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

参考


https://xie.infoq.cn/article/2021f8d92c93c94b96ebf4a3d
https://www.cnblogs.com/xrq730/p/11440174.html
https://blog.csdn.net/weixin_43476824/article/details/125739140

你可能感兴趣的:(Java SPI)