`
qihuiyong6
  • 浏览: 39583 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java实现SPI基础工具类

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

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

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

ExtensionServiceLoader工具类的实现:
该类中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();
}


SPI 注解:
所有需要定位为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();
				}
				
			}
        }
	}
}



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

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

IEcho 一个具体的业务接口:
注意SPI注解
package org.qhy.spi.api;

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

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



IEcho具体实现类:
/********************************实现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");
	}

}



配置文件:
文件路径一般为 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


测试代码:

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();
	}
}


分享到:
评论

相关推荐

    JAVA_API1.6文档(中文)

    java.util 包含 collection 框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。 java.util.concurrent 在并发编程中很常用的实用工具类...

    Java 1.6 API 中文 New

    java.util 包含 collection 框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。 java.util.concurrent 在并发编程中很常用的实用工具类。...

    java api最新7.0

    java.util 包含 collection 框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。 java.util.concurrent 在并发编程中很常用的实用工具类。...

    JavaAPI1.6中文chm文档 part1

    java.util 包含 collection 框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。 java.util.concurrent 在并发编程中很常用的实用工具类...

    java动态日志注入工具anylog.zip

    anylog 是一个可以在代码的任意区域无入侵地加入日志的工具,适用于线上问题排查。 anylog 为开发人员提供一个易于使用的平台,帮助开发... 如果要深入了解spi机制,请自行google:java spi 标签:anylog

    java jdk-api-1.6 中文 chmd

    java.util 包含 collection 框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。 java.util.concurrent 在并发编程中很常用的实用工具类...

    JavaAPI中文chm文档 part2

    java.util 包含 collection 框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。 java.util.concurrent 在并发编程中很常用的实用工具类...

    Java后端开发库,涵盖:常用工具类、SPI扩展、分布式锁、限流、分布式链路追踪等。.zip

    对于UI/UX设计,有界面设计工具,如Sketch、Adobe XD,可以帮助设计师快速构建应用程序界面模型,并生成规范的设计稿供开发人员参考实现。 跨平台支持: 跨平台开发工具如Xamarin、React Native和Flutter,让...

    JAVA_API1.8中文文档(CHM版).rar

    中文版,平时开发超实用工具。 Java 2 Platform 软件包 java.applet 提供创建 applet 所必需的类和 applet 用来与其 applet 上下文通信的类。...java.lang 提供利用 Java 编程语言进行程序设计的基础类。 ......

    [Java参考文档]

    java.util 包含 collection 框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。 java.util.concurrent 在并发编程中很常用的实用工具类...

    [Java参考文档].JDK_API 1.6

    java.util 包含 collection 框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。 java.util.concurrent 在并发编程中很常用的实用工具类。...

    JDK_1_6 API

    java.util 包含 collection 框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。 java.util.concurrent 在并发编程中很常用的实用工具类...

    java对音乐文件的jar包

    java对音乐文件的操作的jar工具类,含有三个jl MP3 tritonus

    JCTP(CTP JAVA接口) 0.3.2

    增加:JCTPStructUtil工具类 修正:交易API部分函数出现空指针异常的问题 修正:行情API部分函数出现空指针异常的问题 变更:发布包中增加JCTP.jar *********************************************** JCTP 0.3.1 ...

    Java_EE_6规范中文版

    javaee 6 规范 chm版本 第1章 引言 1.1 感谢 1.2 版本1.3的感谢 1.3 版本1.4的感谢 ...13.1 JNLP(Java Web Start) 13.2 Java EE SPI 附录 附录A 早期版本的部署描述符 附录B 修订历史 科瑞网酷

    2010年11月6日招聘会参会企业名单.xls

    javax.sql.rowset.serial 提供实用工具类,允许 SQL 类型与 Java 编程语言数据类型之间的可序列化映射关系。 javax.sql.rowset.spi 第三方供应商在其同步提供者的实现中必须使用的标准类和接口。 javax.swing 提供...

    javatools源码-astra:Astra:用于分析和重构Java源代码的Java工具

    java工具源码 什么是阿斯特拉? Astra是用于分析和重构Java源代码的Java工具。 例如: “对类型A的引用应改为对类型B的引用” “方法A的调用者应添加一个附加参数B ” “查找带有A注释的类” 阿斯特拉(Astra)已在...

Global site tag (gtag.js) - Google Analytics