`
qihuiyong6
  • 浏览: 41011 次
  • 性别: 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 spi实现工程

    在Java的`java.util.ServiceLoader`类中,SPI的实现方式得以体现。 在这个“java spi实现工程”中,我们可以看到一个简单的Java SPI应用实例。下面将详细讲解Java SPI的基本原理、使用步骤以及相关的关键组件。 1....

    java的spi测试

    首先,SPI的核心在于`java.util.ServiceLoader`类,它是用来加载服务提供者的工具。服务提供者是实现了特定接口的类,这些接口通常定义在Java标准库或者其他核心模块中。例如,`java.sql.Driver`接口就是数据库驱动...

    java-mp3spi

    总之,Java MP3SPI是Java开发者处理MP3音频文件的重要工具,它增强了JMF的功能,使得在Java环境中实现MP3播放变得简单易行。通过这个库,开发者可以轻松地构建多媒体应用,例如音乐播放器,而无需依赖平台特定的库或...

    java SPI机制实现服务接口和服务实现分离源码Demo

    总结来说,Java SPI机制是一种强大的服务发现和加载工具,它促进了服务接口和服务实现的解耦,提高了代码的可扩展性和可维护性。通过定义服务接口,编写实现类,并在配置文件中指定实现,我们可以在运行时动态地加载...

    Java的Spi使用实例

    总结,Java SPI机制是Java平台提供的一种强大的可扩展性工具,它使得开发者可以轻松地添加、替换服务实现,而无需修改主程序代码。在实际开发中,如JDBC驱动、JAXP解析器等都广泛使用了SPI。理解并掌握SPI的使用,能...

    java spi-demo示例

    在"java spi-demo示例"中,我们可以看到一个名为"spi-parent"的压缩包文件,这通常是一个Maven或Gradle项目结构,包含了多个模块,每个模块可能代表一个SPI服务的实现。在这个例子中,可能有以下几个关键部分: 1. ...

    java spi 学习记录

    在Java SPI中,服务提供者是实现了特定接口的类,而服务消费者则通过`ServiceLoader`来查找和加载这些提供者。这个过程是解耦的,因为消费者的代码并不直接引用服务提供者的实现,而是依赖于运行时的配置。 标签...

    JAVA_API1.6文档(中文)

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

    Java Service Provider实现

    2. **ServiceLoader**:Java的`java.util.ServiceLoader`类是用于加载服务提供者的工具类。通过迭代`ServiceLoader.load()`返回的迭代器,开发者可以获取到所有可用的服务提供者实例。 3. **FactoryFinder**:在...

    SPI入门级Demo(四:服务实现者-乘法服务)

    SPI(Service Provider Interface)是Java提供的一种服务发现机制,它允许开发者通过定义接口并在类路径下放置相应的实现类,使得程序在运行时动态地加载这些实现。本篇将通过一个入门级的Demo来讲解如何创建并使用...

    JNDI简介与SPI实现

    在Java中,SPI主要由`java.util.ServiceLoader`类来实现。服务提供者需要在`META-INF/services`目录下创建一个以接口全限定名命名的文本文件,文件内容包含该接口的所有实现类的全限定名。这样,当使用`...

    Java 1.6 API 中文 New

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

    java api最新7.0

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

    mp3spi1.9.4

    在这个场景中,"mp3spi1.9.4" 可能是针对MPEG音频格式(即MP3)的一个SPI实现,它为Java应用程序提供了解码、编码或处理MP3文件的能力。 MpegAudioSPI1.9.4 是这个库的具体版本号,表明这是该库的1.9.4次更新。通常...

    java类包的介绍与使用

    `java.awt` 包是抽象窗口工具包,包含了创建用户界面(UI)和图形图像的基本类,如 `Window`, `Frame`, `Panel`, `Button` 等。它是 Java GUI 编程的基础,提供了基本的组件和布局管理器。 `java.awt.color` 提供了...

    SPI入门级Demo(三:服务实现者-加法服务)

    这个机制在Java中主要由`java.util.ServiceLoader`类来实现。在本示例中,我们将探讨如何创建一个简单的SPI入门级Demo,具体是关于实现一个加法服务。 首先,理解SPI的基本流程: 1. **定义接口**:服务接口是所有...

    Java基于腾讯云短信和阿里云短信整合的一个简单demo.zip

    源码通常包含Maven或Gradle构建文件,以及相关的Java类,可能还包含了配置文件,如application.properties或yaml文件。 6. **API集成**: 这个项目展示了如何将外部服务(如腾讯云和阿里云的短信服务)的API集成到...

    spi-Demo.rar

    4. **服务加载器(Service Loader)**:这是Java SPI内置的工具类,负责读取服务配置文件,并根据其中的信息加载服务提供者。开发者可以通过`java.util.ServiceLoader.load()`方法获取到服务提供者的实例。 5. **...

    java jdk-api-1.6 中文 chmd

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

    软件架构:SPI的Demo

    在Java中,SPI主要通过`java.util.ServiceLoader`类来实现。 在描述中提到了一个博客链接,虽然具体内容没有提供,但我们可以假设这个博客详细解释了如何创建和使用SPI的示例。通常,SPI的实现包括以下几个步骤: ...

Global site tag (gtag.js) - Google Analytics