前言
上一章分析了Java SPI机制的原理,是通过遍历查找实现类,并实例化对象。实际工作却需要按需查找并实例化对象。我自己写了一个简单的demo,仅在官方的API做少量修改即可。
1. demo源码
package com.feng.spi.demo.loader;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.ServiceConfigurationError;
public class ServiceLoader<S> {
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// Cached providers, in instantiation order
private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
// The current cached names
private LinkedHashMap<String, String> names = new LinkedHashMap<>();
public void reload() {
names.clear();
providers.clear();
initImplBeanNames();
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
reload();
}
public S loadServiceInstance(String name){
if (!providers.containsKey(name)) {
loadServiceClass(name);
}
return providers.get(name);
}
private static void fail(Class<?> service, String msg, Throwable cause)
throws ServiceConfigurationError {
throw new ServiceConfigurationError(service.getName() + ": " + msg,
cause);
}
private static void fail(Class<?> service, String msg)
throws ServiceConfigurationError {
throw new ServiceConfigurationError(service.getName() + ": " + msg);
}
private static void fail(Class<?> service, URL u, int line, String msg)
throws ServiceConfigurationError {
fail(service, u + ":" + line + ": " + msg);
}
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc)
throws IOException, ServiceConfigurationError {
String ln = r.readLine();
if (ln == null) {
return -1;
}
int eqIndex = ln.indexOf("=");
String name = ln.substring(0, eqIndex);
ln = ln.substring(eqIndex+1);
int ci = ln.indexOf('#');
if (ci >= 0) ln = ln.substring(0, ci);
ln = ln.trim();
int n = ln.length();
if (n != 0) {
if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
fail(service, u, lc, "Illegal configuration-file syntax");
int cp = ln.codePointAt(0);
if (!Character.isJavaIdentifierStart(cp))
fail(service, u, lc, "Illegal provider-class name: " + ln);
for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
cp = ln.codePointAt(i);
if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
fail(service, u, lc, "Illegal provider-class name: " + ln);
}
if (!names.containsKey(name))
names.put(name, ln);
}
return lc + 1;
}
private void parse(Class<?> service, URL u)
throws ServiceConfigurationError {
InputStream in = null;
BufferedReader r = null;
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
while ((lc = parseLine(service, u, r, lc)) >= 0) ;
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally {
try {
if (r != null) r.close();
if (in != null) in.close();
} catch (IOException y) {
fail(service, "Error closing configuration file", y);
}
}
}
private void loadServiceClass(String name) {
String implClassName = names.get(name);
if (implClassName == null || implClassName.trim().length() == 0) {
fail(service,
"Provider " + name + " impl class not found");
}
if (providers.get(name) != null) {
return;
}
Class<?> c = null;
try {
c = Class.forName(implClassName, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + implClassName + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + implClassName + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(name, p);
} catch (Throwable x) {
fail(service,
"Provider " + implClassName + " could not be instantiated",
x);
}
}
private void initImplBeanNames() {
Enumeration<URL> configs = null;
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
if (!configs.hasMoreElements()) {
return;
}
parse(service, configs.nextElement());
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
ClassLoader prev = null;
while (cl != null) {
prev = cl;
cl = cl.getParent();
}
return load(service, prev);
}
public String toString() {
return "com.feng.spi.demo.loader.ServiceLoader[" + service.getName() + "]";
}
}
2. 配置文件修改
3. main demo
运行
总结
其实就是实现了,安装key取实现类的名称,并按照key实例化对象,还可以在接口或者实现类的类或者方法加上注解,然后在加载配置文件,或者实例化bean的过程中解析这些注解,可以实现更灵活的SPI机制,如果需要引入dubbo的jar,也可以遵循dubbo的SPI规范,直接使用dubbo SPI。
上面的demo还可以优化一下,使用单例模式,每个接口仅创建一个ServiceLoader,对接口的实现类的实例化做线程安全控制
private synchronized void loadServiceClass(String name) {
String implClassName = names.get(name);
if (implClassName == null || implClassName.trim().length() == 0) {
fail(service,
"Provider " + name + " impl class not found");
return;
}
if (providers.get(name) != null) {
return;
}
Class<?> c = null;
try {
c = Class.forName(implClassName, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + implClassName + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + implClassName + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(name, p);
} catch (Throwable x) {
fail(service,
"Provider " + implClassName + " could not be instantiated",
x);
}
}
private static Map<Class, ServiceLoader> serviceLoaderMap = new ConcurrentHashMap<>();使用静态存储对象
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader) {
synchronized (service){
if (serviceLoaderMap.containsKey(service)){
return serviceLoaderMap.get(service);
}
ServiceLoader<S> serviceLoader = new ServiceLoader<>(service, loader);
serviceLoaderMap.put(service, serviceLoader);
return serviceLoader;
}
}
达到单实例,与线程安全的能力。