JAV spi 和扩展方式

SPI 机制(Service Provider Interface)其实源自服务提供者框架(Service Provider Framework,参考【EffectiveJava】page6),是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了 spi 接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔

在开发中,有很多地方都适用到了 java 的 spi 机制;例如:JSBC设计…等,这里我们不去研究这些示例,直接看一下他的实现方式

一、JAVA SPI

其实 javaspi 核心核心就是 ServiceLoader 这个类;我们可以通过在 resource/META-INF/services 下,放置要实例化的类实现的统一接口全路径的文件,在里面每行写上实例化类的全路径,即可使用 spi 的方式实例化,如下:

首先定义一个接口:

package com.spi.test.source;

public interface Developer {

    void hello();
}

然后实现两个实现类:

package com.spi.test.source;

public class Make1Developer implements Developer {
    public void hello() {
        System.out.println("make1 的 develop");
    }
}

public class Make2Developer implements Developer {
    public void hello() {
        System.out.println("make2 的 develop");
    }
}

然后在 resource/META-INF/services 下创建 com.spi.test.source.Developer 文件内容如下:

com.spi.test.source.Make1Developer
com.spi.test.source.Make2Developer

测试类如下:

public static void main(String[] args) {
    ServiceLoader<Developer> serviceLoader = ServiceLoader.load(Developer.class);
    // 循环调用下实例的方法
    serviceLoader.forEach(item -> {
        System.out.println("正在执行的类是:" + item.getClass().toString());
        item.hello();
    });
}

二、扩展 java spi

上线是 jdk 自己携带的 SPI 功能,但是他设计的并不太好,只实现了基础的功能;另外 dubboSPI 也有自己的扩展方式,而且实现上也有了优化,

dubbo spi 对比 java spi 的优化点:

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源
  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因

有兴趣可以去看一下 dubbo-common.jar 下的 org.apache.dubbo.common.extension.ExtensionLoader 类的实现;这里不在详细说明,我们这里只简单模仿 java spi 自己扩展一个 spi 的功能

首先定义一个注解,这里我们加一步校验,就是接口上必须加上注解,才可以将实现其接口的类实例化:

package com.spi.test.my.source;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SpiActive {
}

然后自己实现一个扩展类加载器辅助类:

package com.spi.test.my.source;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.*;

import static java.nio.charset.StandardCharsets.UTF_8;

public class ExpansionLoader<T> implements Iterable<T> {

    /**
     * 约定第三方实现配置文件目录
     **/
    private static final String SERVICE_DIRECTORY = "META-INF/expansions/";

	/**
	 * 接口的类型,用于获取此接口下的第三方实现
	 **/
    private final Class<T> type;

	/**
	 * 实例列表,用于保存获取到的第三方实现的列表
	 **/
    private Set<T> targets;

    public ExpansionLoader(Class<T> type) {
        this.type = type;
        this.targets = this.loadExtensionFile();
    }

    /**
     * 工厂方法,用于通过接口获取第三方实现的类加载器
     *
     * @param type 		接口的类型
     * @return 		返回一个指定接口类型的类加载器辅助类
     **/
    public static <T> ExpansionLoader<T> load(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Spi需要知道你想要找到哪个功能的第三方实现!");
        }
        if (!type.isInterface()) {
            throw new IllegalArgumentException("只支持寻找接口类型的第三方实现!");
        }
        if (type.getAnnotation(SpiActive.class) == null) {
            throw new IllegalArgumentException("目标接口必须被@Spi注解标注!");
        }
        return new ExpansionLoader<>(type);
    }

    private Set<T> loadExtensionFile() {
        String fileName = ExpansionLoader.SERVICE_DIRECTORY + this.type.getName();
        try {
            ClassLoader classLoader = ExpansionLoader.class.getClassLoader();
            Enumeration<URL> urls = classLoader.getResources(fileName);
            if (urls == null) {
                return Collections.emptySet();
            }
            URL resourceUrl = urls.nextElement();
            return loadResource(classLoader, resourceUrl);
        } catch (IOException e) {
            return Collections.emptySet();
        }
    }

    private Set<T> loadResource(ClassLoader classLoader, URL resourceUrl) {
        Set<T> set = new HashSet<>();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                final int ci = line.indexOf('#');
                if (ci == 0) {
                    continue;
                } else if (ci > 0) {
                    line = line.substring(0, ci);
                }
                Class<T> clazz = (Class<T>) classLoader.loadClass(line.trim());
                set.add(clazz.newInstance());
            }
        } catch (Exception e) {}
        return set;
    }

    @Override
    public Iterator<T> iterator() {
        return this.targets.iterator();
    }
}

以上是工具部分;下面开始定义第三方需要实现的接口:

package com.spi.test.my;

import com.spi.test.my.source.SpiActive;

@SpiActive
public interface Production {

    void hello();
}

然后添加两个实现类:

package com.spi.test.my;

public class Make1Production implements Production {
    @Override
    public void hello() {
        System.out.println("make1 的 Production");
    }
}

public class Make2Production implements Production {
    @Override
    public void hello() {
        System.out.println("make2 的 Production");
    }
}

然后在 resource/META-INF/expansions 下创建 com.spi.test.my.Production 文件内容如下:

com.spi.test.my.Make1Production
com.spi.test.my.Make2Production

测试类如下:

public static void main(String[] args) {
    ExpansionLoader<Production> extensionLoader = ExpansionLoader.load(Production.class);
    extensionLoader.forEach(item -> {
        System.out.println("正在执行的类是:" + item.getClass().toString());
        item.hello();
    });
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值