一、概述
我们都知道 Java的SPI机制:(service provider interface ) 对于该机制的详情概述请自行百度。其实Spi简单的是提供给服务提供商的开发者使用和扩展的(其实是接口编程+策略模式+配置文件的一种方式)。
场景:假如一个一个jar包中的一个接口A 分别有三个A接口的实现:B、C、D,我们在其他地方使用到了接口A的实现的时候那么我们不得不进行硬编码来指定对应的实现类,为了解耦我们就可以使用Spi机制来解决这个问题。
此Spi扩展加载机制的约定:
1.只扫描META-INF/services目录下的配置的spi服务接口文件。
2.META-INF/services目录下的spi服务接口文件名称必须与指定的spi接口名称相同。
3.META-INF/services目录下的spi服务接口文件内容包有对应接口的实现的类名称。
4.spi服务接口必须包含有@Spi注解。
5.spi服务接口实现类上包含有@SpiMeta 注解 可有可无。
6.@Spi注解可有指定实现类是单例模式还是原型模式(默认为原型模式)。
7.getExtension(String name)方法来获取对应服务实例(如果服务实现类中包含有@SpiMeta注解并且name不为空则以此name值来获取,否则按照此服务实现的类名来获取)。
8.通过@Spi注解可以指定是原型模式还是单例模式。
二、目录结构示意图
src
--- com.shotdog.panda.core.registry.RegistryFactory
----com.shotdog.panda.core.registry.support.ZookeeperRegistryFactory
META-INF
------------- services
-------------------------com.shotdog.panda.core.registry.RegistryFactory(内容:com.shotdog.panda.core.registry.support.ZookeeperRegistryFactory)
package com.shotdog.panda.core.extension;
import com.shotdog.panda.core.common.annotation.Spi;
import com.shotdog.panda.core.common.annotation.SpiMeta;
import com.shotdog.panda.core.common.enums.Scope;
import com.shotdog.panda.core.exception.PandaFrameworkException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/***
* Spi 扩展加载器
* jdk spi 增强
* @date 2017-10-31 21:42:04
*/
public class ExtensionLoader {
private final static Logger log = LoggerFactory.getLogger(ExtensionLoader.class);
private final static String PREFIX = "META-INF/services/";
private Class type;
private ClassLoader classLoader;
private volatile transient boolean init;
private ConcurrentHashMap> extensions = new ConcurrentHashMap>();
private ConcurrentHashMap singletons;
private static ConcurrentHashMap, ExtensionLoader>> extensionLoaders = new ConcurrentHashMap, ExtensionLoader>>();
public ExtensionLoader(Class type) {
this(type, Thread.currentThread().getContextClassLoader());
}
public ExtensionLoader(Class type, ClassLoader classLoader) {
this.type = type;
this.classLoader = classLoader;
}
/**
* 获取扩展加载器
*
* @param clz
* @param
* @return
*/
public synchronized static ExtensionLoader getExtensionLoader(Class clz) {
checkInterface(clz);
ExtensionLoader> extensionLoader = extensionLoaders.get(clz);
if (extensionLoader != null) return (ExtensionLoader) extensionLoader;
return newExtensionLoader(clz);
}
/**
* 创建一个新的扩展加载器
*
* @param clz
* @param
* @return
*/
private static ExtensionLoader newExtensionLoader(Class clz) {
ExtensionLoader> extensionLoader = extensionLoaders.get(clz);
if (extensionLoader != null) return (ExtensionLoader) extensionLoader;
ExtensionLoader loader = new ExtensionLoader(clz);
extensionLoaders.putIfAbsent(clz, loader);
return loader;
}
/**
* 检查接口是否为扩展加载的接口
*
* @param clz
* @param
*/
private static void checkInterface(Class clz) {
if (clz == null) {
throw new PandaFrameworkException("extension loader service interface is not allow null");
}
if (!clz.isAnnotationPresent(Spi.class)) {
throw new PandaFrameworkException(String.format("extension loader service interface has no (%s) annotation", Spi.class.getName()));
}
}
/***
* 获取扩展加载服务对象
* @param name
* @return
*/
public T getExtension(String name) {
if (!init) {
this.initExtensionLoader();
}
Class clz = this.extensions.get(name);
if (clz == null) return null;
try {
Spi spi = type.getAnnotation(Spi.class);
if (spi.scope() == Scope.SINGLETON) {
return this.newInstanceToSingleton(clz, name);
} else {
return clz.newInstance();
}
} catch (Exception e) {
throw new PandaFrameworkException(String.format("class:(%) newInstance fail", clz.getName()));
}
}
/**
* 创建一个新的单例对象
*
* @param clz
* @param name
* @return
* @throws IllegalAccessException
* @throws InstantiationException
*/
private T newInstanceToSingleton(Class clz, String name) throws IllegalAccessException, InstantiationException {
synchronized (singletons) {
T t = this.singletons.get(name);
if (t != null) return t;
t = clz.newInstance();
this.singletons.putIfAbsent(name, t);
return t;
}
}
private void initExtensionLoader() {
this.extensions = this.loadExtensions();
this.singletons = new ConcurrentHashMap();
this.init = true;
}
private ConcurrentHashMap> loadExtensions() {
String configFiles = PREFIX + this.type.getName();
List classNames = new ArrayList();
try {
Enumeration url = null;
if (this.classLoader == null) {
url = ClassLoader.getSystemResources(configFiles);
} else {
url = this.classLoader.getResources(configFiles);
}
while (url.hasMoreElements()) {
URL u = url.nextElement();
this.parseUrl(u, classNames);
}
} catch (Exception e) {
throw new PandaFrameworkException(String.format("extension loader loadExtensions :(%s) fail", configFiles), e);
}
return this.loadAllClasses(classNames);
}
/***
* 加载所有的类
* @param classNames
* @return
*/
private ConcurrentHashMap> loadAllClasses(List classNames) {
ConcurrentHashMap> classes = new ConcurrentHashMap>();
if (classNames == null || classNames.isEmpty()) return classes;
for (String className : classNames) {
try {
Class clz = null;
if (this.classLoader == null) {
clz = (Class) Class.forName(className);
} else {
clz = (Class) Class.forName(className, false, this.classLoader);
}
this.checkExtensionType(clz);
String name = this.getSpiName(clz);
if (!classes.containsKey(name)) {
classes.putIfAbsent(name, clz);
}
} catch (Exception e) {
throw new PandaFrameworkException(String.format("extension loader load class:(%s) fail", className));
}
}
return classes;
}
/**
* 获取spi服务名称
*
* @param clz
* @return
*/
private String getSpiName(Class clz) {
SpiMeta spiMeta = clz.getAnnotation(SpiMeta.class);
if (spiMeta != null && StringUtils.isNotBlank(spiMeta.name())) {
return spiMeta.name();
}
return clz.getSimpleName();
}
/***
* 检查扩展服务类类型
* @param clz
*/
private void checkExtensionType(Class clz) {
// 检查是否为 public 类型
if (!Modifier.isPublic(clz.getModifiers())) {
throw new PandaFrameworkException(String.format("class :(%s) is not public type class", clz.getName()));
}
// 检查是否为spi服务接口实现类
if (!type.isAssignableFrom(clz)) {
throw new PandaFrameworkException(String.format("class :(%s) is not assignable from interface ", clz.getName(), type.getName()));
}
// 检查是否有无参数默认构造方法
Constructor>[] constructors = clz.getConstructors();
for (Constructor constructor : constructors) {
if (constructor.getParameterTypes().length == 0) {
return;
}
}
throw new PandaFrameworkException(String.format("class :(%s) is has not default constructor method ", clz.getName()));
}
/**
* 解析每个 url
*
* @param url
* @param classNames
*/
private void parseUrl(URL url, List classNames) throws IOException {
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = url.openStream();
reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
while (this.parseReadLine(reader, classNames) > 0) ;
} catch (Exception e) {
throw new PandaFrameworkException("extension loader parseUrl fail");
} finally {
try {
if (reader != null) reader.close();
if (inputStream != null) inputStream.close();
} catch (Exception e) {
// ignore
}
}
}
/**
* 读取每一行
*
* @param reader
* @param classNames
* @return
* @throws IOException
*/
private int parseReadLine(BufferedReader reader, List classNames) throws IOException {
String line = reader.readLine();
if (line == null) return -1;
int index = line.indexOf("#");
if (index >= 0) line = line.substring(0, index);
line = line.trim();
int length = line.length();
if (length > 0) {
if (line.indexOf(' ') >= 0 || line.indexOf('\t') > 0) {
throw new PandaFrameworkException(String.format("Syntax error:(%s)", line));
}
// 校验首字母
int codePoint = line.codePointAt(0);
if (!Character.isJavaIdentifierStart(codePoint)) {
throw new PandaFrameworkException(String.format("Syntax error:(%s)", line));
}
// 循环检查内容是否合法
for (int i = Character.charCount(codePoint); i < length; i += Character.charCount(codePoint)) {
codePoint = line.codePointAt(i);
if (!Character.isJavaIdentifierPart(codePoint) && codePoint != '.') {
throw new PandaFrameworkException(String.format("Syntax error:(%s)", line));
}
if (!classNames.contains(line)) {
classNames.add(line);
}
}
}
return length + 1;
}
}
二、测试代码
spi接口
@Spi public interface EchoService { String echo(String name); }
spi服务接口实现类
@SpiMeta(name = "hello")
public class HelloEchoService implements EchoService {
public String echo(String name) {
return "hello " + name;
}
}
在 META-INF/services/目录下 创建一个名称为:com.shotdog.panda.test.extension.EchoService 文件
内容为:com.shotdog.panda.test.extension.HelloEchoService
测试代码如下:
EchoService hello = ExtensionLoader.getExtensionLoader(EchoService.class).getExtension("hello");
String shotdog = hello.echo("shotdog");
log.info(shotdog);