Java SPI - ServiceLoader 使用简介 java 优雅地获取接口实现

原文地址

Java SPI - ServiceLoader 使用简介

问题引入

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

困难

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

  • 初步方案
    和同事讨论,是通过扫描包的 class 的方式。然后判断是否为定制标准的子类。

  • 缺点
    觉得很别扭,需要限定死实现类的包名称,而且性能也较差。

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

定义接口和实现

  • Say.java
/**
 * 

接口

* *
 Created: 2018/5/27 上午10:36  
*
 Project: tech-validation  
* * @author houbinbin * @version 1.0 * @since JDK 1.7 */
public interface Say { /** * 说 */ void say(); }
  • DefaultSay.java
package com.github.houbb.forname.impl;

import com.github.houbb.forname.Say;

/**
 * 

* *
 Created: 2018/5/27 上午10:37  
*
 Project: tech-validation  
* * @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.Say
com.github.houbb.forname.impl.DefaultSay

测试

  • SayTest.java
public class SayTest {

    @Test
    public void spiTest() {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        ServiceLoader 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.servicegroupId>
            <artifactId>auto-serviceartifactId>
            <version>1.0-rc4version>
            <optional>trueoptional>
        dependency>
    dependencies>

接口和定义

  • Sing.java
/**
 * 

接口

* *
 Created: 2018/5/27 上午10:36  
*
 Project: tech-validation  
* * @author houbinbin * @version 1.0 * @since JDK 1.7 */
public interface Sing { /** * 唱歌 */ void sing(); }
  • DefaultSing.java
@AutoService(Sing.class)
public class DefaultSing implements Sing {

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

}

测试

  • SingTest.java
public class SingTest {

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

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

}
  • 结果
Sing a song...

简单总结

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

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

项目源码

github spi

你可能感兴趣的:(Java)