Java实现SPI基础工具类

[size=medium][b]概述:[/b][/size]
前端时间看了一下dubbo源码被它使用的基于SPI(service provider interface)开发模式所吸引,这种方式组织的程序可以方便dubbo使用者自己扩展和实现自己的插件。
废话不多说了,讲代码吧。

开发过dubbo过滤器的同学应该很熟悉这种配置,在“classpath/services/接口全名”有一个文件用于定义该接口的所有实现类。并且在配置文件中加入自己配置的名字就可以用了。

这里我模仿这种方式使用反射机制创建了这些服务实现,并供系统通过名字定位需要使用的服务具体实现。没什么太难的东西大家看看就知道了。

[size=medium][b]ExtensionServiceLoader工具类的实现:[/b][/size]
该类中loadExtensionClasses为核心方法。他首先会加载了所有SPI类文件,然后解析文件中的定义键值对,最后生成class的map供以后代码使用。代码中有注释可以参考这里就不多说。

package org.qhy.spi.pkg.anonation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
String value();
}


[size=medium][b]SPI 注解:[/b][/size]
所有需要定位为spi的接口都加上这个注解,并且可以给一个默认的实现名称

package org.qhy.spi.pkg.internal;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.qhy.spi.pkg.anonation.SPI;

/**
* ClassName: org.qhy.spi.pkg.internal.ExtensionServiceLoader <br/>
* Description: spi服务接口加载类实现. <br/>
*/
public class ExtensionServiceLoader<T> {
private static final String SERVICES_DIRECTORY = "META-INF"+File.separator+"services"+File.separator;


//存放不同类型的loader
private static final ConcurrentMap<Class<?>, ExtensionServiceLoader<?>> loaderMap = new ConcurrentHashMap<Class<?>, ExtensionServiceLoader<?>>();

//存放不同实例
private final ConcurrentMap<String, Object> instanceMap = new ConcurrentHashMap<String, Object>();
//加载不同的所有的实现的类定义
private final ConcurrentMap<String, Class<?>> extensionClassesMap = new ConcurrentHashMap<String, Class<?>>();
private Class<?> type;

/**
* Description: . <br/>
* @param extensionType
* @return
* @throws Exception
*/
public static <T> ExtensionServiceLoader<T> getServiceLoader(Class<?> extensionType) throws Exception {
if(extensionType == null){
throw new IllegalArgumentException("extensionType is null!");
}
if(!extensionType.isAnnotationPresent(SPI.class)){
throw new IllegalArgumentException("extensionType ("+extensionType+")Invalid extension,because: No annotations (@"+SPI.class.getSimpleName()+")!");
}
//从map中获取
ExtensionServiceLoader<T> serviceLoader = (ExtensionServiceLoader<T>) loaderMap.get(extensionType);
if(serviceLoader == null){
try {
serviceLoader = new ExtensionServiceLoader<T>(extensionType);
} catch (Exception e) {
throw e;
}
loaderMap.put(extensionType, serviceLoader);
}
return serviceLoader;
}
public T getServiceInstance(String name) throws Exception{
if(name == null || name.trim().length()==0){
throw new IllegalArgumentException("name is null!");
}
//从map中取实例如果取不到 就创建病存放到map中
T t = (T)instanceMap.get(name);

if(t == null){
Class<?> clazz = extensionClassesMap.get(name);
if(clazz == null){
throw new IllegalArgumentException("name:["+name+"] not defination in file("+SERVICES_DIRECTORY+type.getName()+")");
}
try {
t = (T)clazz.newInstance();
} catch (Exception e) {
throw e;
}
instanceMap.putIfAbsent(name, t);
}
return t;
}
public T getDefaultInstance() throws Exception{
SPI spi = type.getAnnotation(SPI.class);
return this.getServiceInstance(spi.value());
}

private ExtensionServiceLoader(Class<?> extensionType) throws IllegalStateException, ClassNotFoundException, IOException {
this.type = extensionType;//该loader对应的类型-一接口一loader
this.loadExtensionClasses(extensionType);
}
/**
*
* Description: 加载不同的类定义. <br/>
* @param extensionType 扩展类型
* @throws ClassNotFoundException
* @throws IOException
* @throws IllegalStateException
*/
private void loadExtensionClasses(Class<?> extensionType) throws ClassNotFoundException, IOException,IllegalStateException{
Enumeration<java.net.URL> urls;
//文件名 也就是接口名
String fileName = SERVICES_DIRECTORY+extensionType.getName();
ClassLoader classLoader = ExtensionServiceLoader.class.getClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
classLoader = ClassLoader.getSystemClassLoader();
}
if (urls != null && urls.hasMoreElements()) {
while (urls.hasMoreElements()) {
java.net.URL url = urls.nextElement();
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
String line = null;
try{
while ((line = reader.readLine()) != null) {
if(line == null || line.trim().length()==0 || line.contains("#")){
continue;
}
//读取文件一行定义并处理
line =line.trim();
String[] defArray = line.split("=");
if(defArray.length != 2){
continue;
}
//拆分文件,大一部分是名字,第二部分是类全名
String name = defArray[0],className=defArray[1];
Class<?> clazz =Class.forName(className,true,classLoader);
//判断文件定义的类,是不是加载接口的子类
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("class line defination [" + className + "] not an subType of("+type.getName()+")");
}
extensionClassesMap.put(name,clazz);
}
}finally{
reader.close();
}

}
}
}
}



[size=medium][b]使用方式:[/b][/size]
下面是我自己测试的一些代码。
主要逻辑为首先要创建业务接口(IEcho)并添加@SPI注解,然后为他添加两个具体的实现(Echo1、Echo2)这样就定义好了我们的SPI服务。接下来就需要根据名字调用具体的服务。

将服务实现配置到到配置文件中。(jdk也有一个实现叫serviceLoader应该具体自己百度一下。jdk使用的方式就是将接口名作为文件名然后文件列表中包含该接口的实现列表分别以name=classFullName)先入为主我也使用的这种方式配置服务,下面为具体测试代码。

[size=medium][b]IEcho 一个具体的业务接口:[/b][/size]
注意SPI注解

package org.qhy.spi.api;

import org.qhy.spi.pkg.anonation.SPI;

/**
* @author qihuiyong
*
*/
@SPI(value = "echo1")
public interface IEcho {
public void echo();
}



[size=medium][b]IEcho具体实现类:[/b][/size]

/********************************实现1******************************************/
package org.qhy.spi.impl;

import org.qhy.spi.api.IEcho;

public class Echo1 implements IEcho {

@Override
public void echo() {
System.out.println("echo:Opration_11111111");
}

}


/********************************实现2******************************************/
package org.qhy.spi.impl;

import org.qhy.spi.api.IEcho;
public class Echo2 implements IEcho {

@Override
public void echo() {
System.out.println("echo:Opration_2222222222222222");
}

}



[size=medium][b]配置文件:[/b][/size]
文件路径一般为 classes\META-INF\services\org.qhy.spi.api.IEcho


echo1=org.qhy.spi.impl.Echo1
echo2=org.qhy.spi.impl.Echo2
#echo3=org.qhy.spi.impl.Echo3
#exe=org.qhy.spi.impl.Execute2


[size=medium][b]测试代码:[/b][/size]


package org.qhy.spi;

import org.qhy.spi.api.IEcho;
import org.qhy.spi.pkg.internal.ExtensionServiceLoader;


public class Test {

public static void main(String[] args) throws Exception {
ExtensionServiceLoader<IEcho> serviceLoader = ExtensionServiceLoader.getServiceLoader(IEcho.class);
IEcho echo2 =serviceLoader.getServiceInstance("echo2");
IEcho defaultEcho =serviceLoader.getDefaultInstance();
echo2.echo();
defaultEcho.echo();
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值