程序员我爱编程Java 杂谈

Java SPI - ServiceLoader 使用简介

2018-05-27  本文已影响249人  老马啸西风2020

问题引入

以前一直想指定一套标准,让别人按照这个标准来实现,并编写好对应的容器。
然后我在代码中动态获取这些实现,让代码运行起来。
类似于写个 windows 环境,让开发者自己开发对应的软件。

困难

如何获取某个接口的实现?

SPI 的解决方式

今天在阅读 hibernate-validator 源码时受到了启发。
可以通过 SPI 的方式,更加自然的解决这个问题。

SPI

SPI 是 Service Provider Interfaces 的缩写。

本文简单介绍下如何使用,具体原理,暂时不做深究。

简单实现

文件目录

.
├── java
│   └── com
│       └── github
│           └── houbb
│               └── forname
│                   ├── Say.java
│                   ├── Sing.java
│                   └── impl
│                       ├── DefaultSay.java
│                       └── DefaultSing.java
└── resources
    └── META-INF
        └── services
            └── com.github.houbb.forname.Say

定义接口和实现

/**
 * <p> 接口 </p>
 *
 * <pre> Created: 2018/5/27 上午10:36  </pre>
 * <pre> Project: tech-validation  </pre>
 *
 * @author houbinbin
 * @version 1.0
 * @since JDK 1.7
 */
public interface Say {

    /**
     * 说
     */
    void say();

}
package com.github.houbb.forname.impl;

import com.github.houbb.forname.Say;

/**
 * <p> </p>
 *
 * <pre> Created: 2018/5/27 上午10:37  </pre>
 * <pre> Project: tech-validation  </pre>
 *
 * @author houbinbin
 * @version 1.0
 * @since JDK 1.7
 */
public class DefaultSay implements Say {

    @Override
    public void say() {
        System.out.println("Default say");
    }

}

编写 services 实现指定

resources 目录下,创建 META-INF/services 文件夹,以接口全路径名
com.github.houbb.forname.Say 为文件名称,内容为对应的实现类全路径。
如果是多个,就直接换行隔开。

com.github.houbb.forname.impl.DefaultSay

测试

public class SayTest {

    @Test
    public void spiTest() {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        ServiceLoader<Say> loader = ServiceLoader.load(Say.class, classLoader);

        for (Say say : loader) {
            say.say();
        }
    }

}
Default say

简单总结

Java 中,可以通过 ServiceLoader 类比较方便的找到该类的所有子类实现。
META-INF/services 下的实现指定和实现子类实现完全可以和接口定义完全分开。

每次都要手动创建实现指定文件,比较繁琐。

Auto 就为解决这个问题而生。

Auto 版本

jar 的引入

    <dependencies>
        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.0-rc4</version>
            <optional>true</optional>
        </dependency>
    </dependencies>

接口和定义

/**
 * <p> 接口 </p>
 *
 * <pre> Created: 2018/5/27 上午10:36  </pre>
 * <pre> Project: tech-validation  </pre>
 *
 * @author houbinbin
 * @version 1.0
 * @since JDK 1.7
 */
public interface Sing {

    /**
     * 唱歌
     */
    void sing();

}
@AutoService(Sing.class)
public class DefaultSing implements Sing {

    @Override
    public void sing() {
        System.out.println("Sing a song...");
    }

}

测试

public class SingTest {

    @Test
    public void spiTest() {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        ServiceLoader<Sing> loader = ServiceLoader.load(Sing.class, classLoader);

        for (Sing sing : loader) {
            sing.sing();
        }
    }

}
Sing a song...

简单总结

通过 google 的 auto,可以在编译时自动为我们生成对应的接口实现指定文件。在 target 对应的文件下可以看到。

实现原理,也相对简单。通过 java 的编译时注解,生成对应的文件即可。

项目源码

github spi

上一篇下一篇

猜你喜欢

热点阅读