ServiceLoader是jdk提供的一个简单的服务提供者加载设施,一个服务(接口)的实现者在其资源目录META-INF/services 中放置提供者配置文件 来标识服务提供者。
文件名称是服务类型的完全限定名称。该文件包含一个具体提供者类的完全限定名称列表,每行一个。
通过ServiceLoader.load创建服务实现者加载器,通过iterator以延迟方式加载此加载器服务的可用提供者。
本帮助类对ServiceLoader的功能进行了增强:
1.对加载到的服务提供者进行缓存,避免重复加载。
2.提供一种按key/value的方式来配置服务提供者,按key来获取具体服务提供者的扩展。
代码如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 服务提供者加载设施帮助类,应用中每个ClassLoader都有不同的ServiceLoaderHelp,因为对同一个服务,使用不同的ClassLoader,
* 加载的jar包可能不同,资源目录META-INF/services下放置的服务提供者配置文件也就可能不同,导致不同ClassLoader加载到的服务提供者不同。
*/
public class ServiceLoaderHelp {
private static final Logger log = LoggerFactory.getLogger(ServiceLoaderHelp.class);
private static final String PREFIX = "META-INF/services/";
private static final ConcurrentHashMap<ClassLoader, ServiceLoaderHelp> MAP_Help_CACHE = new ConcurrentHashMap<ClassLoader, ServiceLoaderHelp>();
private final ClassLoader classLoader;
private final ConcurrentHashMap<Class<?>, Object> INSTANCE_CACHE = new ConcurrentHashMap<Class<?>, Object>();
private final ConcurrentHashMap<Class<?>, Map<String, Class<?>>> MAP_CLASSES_CACHE = new ConcurrentHashMap<Class<?>, Map<String, Class<?>>>();
private final ConcurrentHashMap<Class<?>, ConcurrentHashMap<String, Object>> MAP_INSTANCES_CACHE = new ConcurrentHashMap<Class<?>, ConcurrentHashMap<String, Object>>();
private ServiceLoaderHelp(ClassLoader classLoader){
this.classLoader = classLoader;
}
/**
* 根据ClassLoader获取服务提供者加载设施帮助类。
* @param classLoader
* @return 服务提供者加载设施帮助类
*/
public static ServiceLoaderHelp getHelpByClassLoader(ClassLoader classLoader){
ServiceLoaderHelp help = MAP_Help_CACHE.get(classLoader);
if (help == null){
help = new ServiceLoaderHelp(classLoader);
MAP_Help_CACHE.putIfAbsent(classLoader, help);
return MAP_Help_CACHE.get(classLoader);
} else {
return help;
}
}
/**
* 根据服务接口Class获取服务提供者,若有多个服务提供者取ServiceLoader.load到的第一个服务提供者。
* @param classType 服务接口Class
* @return 服务提供者
*/
@SuppressWarnings("unchecked")
public <T> T getInstance(Class<T> classType) {
T instance = (T) INSTANCE_CACHE.get(classType);
if (instance == null) {
try {
instance = ServiceLoader.load(classType, classLoader).iterator().next();
INSTANCE_CACHE.putIfAbsent(classType, instance);
return (T) INSTANCE_CACHE.get(classType);
} catch (Throwable e) {
log.error("Cannot Load " + classType.getName());
throw new RuntimeException("Cannot Load " + classType, e);
}
} else {
if (classType.isAssignableFrom(instance.getClass())) {
return instance;
} else {
throw new RuntimeException("instance not type " + classType);
}
}
}
/**
* 根据服务接口Class获取服务提供者列表。
* @param classType 服务接口Class
* @return 服务提供者列表
*/
@SuppressWarnings("unchecked")
public <T> List<T> getInstances(Class<T> classType) {
List<T> list = (List<T>) INSTANCE_CACHE.get(classType);
if (list == null) {
try {
list = new ArrayList<T>();
for (T instance : ServiceLoader.load(classType, classLoader)) {
list.add(instance);
}
INSTANCE_CACHE.putIfAbsent(classType, list);
return (List<T>) INSTANCE_CACHE.get(classType);
} catch (RuntimeException e) {
log.error("Cannot Load List By" + classType.getName());
throw new RuntimeException("Cannot Load List By" + classType, e);
}
} else {
if (List.class.isAssignableFrom(list.getClass())) {
return list;
} else {
throw new RuntimeException("instance not List, type " + classType);
}
}
}
/**
* 根据服务接口Class和服务提供者key获取服务提供者。
* 此种服务提供者加载方式,在资源目录META-INF/services下放置的服务提供者配置文件的内容不是ServiceLoader
* 规范中的每行一个“具体服务提供者类的完全限定名称”,
* 而是每行一个“key=具体服务提供者类的完全限定名称”。
* 此方式提供了根据配置选取服务提供者的便利。
*
* @param classType 服务接口Class
* @param key 服务提供者key
* @return 服务提供者
*/
@SuppressWarnings("unchecked")
public <T> T getInstance(Class<T> classType, String key) {
ConcurrentHashMap<String, Object> objMap = MAP_INSTANCES_CACHE.get(classType);
if (objMap == null) {
objMap = new ConcurrentHashMap<String, Object>();
MAP_INSTANCES_CACHE.putIfAbsent(classType, objMap);
}
objMap = MAP_INSTANCES_CACHE.get(classType);
T obj = (T) objMap.get(key);
if (obj != null) {
return obj;
}
Map<String, Class<?>> classMap = MAP_CLASSES_CACHE.get(classType);
if (classMap == null) {
loadFile(classType);
}
classMap = MAP_CLASSES_CACHE.get(classType);
if (classMap == null) {
throw new IllegalStateException("Failed to load extension class(interface: " + classType + ", key: " + key
+ ")");
}
Class<?> clazz = classMap.get(key);
if (clazz != null) {
try {
Object newObj = clazz.newInstance();
Object provious = objMap.putIfAbsent(key, newObj);
return null == provious ? (T) newObj : (T) provious;
} catch (Exception e) {
throw new IllegalStateException("Failed to load extension class(interface: " + classType + ", key: "
+ key + ")", e);
}
}
throw new IllegalStateException("Failed to load extension class(interface: " + classType + ", key: " + key
+ ")");
}
private void loadFile(Class<?> type) {
String fileName = PREFIX + type.getName();
Map<String, Class<?>> map = new HashMap<String, Class<?>>();
try {
Enumeration<URL> urls;
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
try {
String line = null;
while ((line = reader.readLine()) != null) {
final int ci = line.indexOf('#');
if (ci >= 0)
line = line.substring(0, ci);
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
Class<?> clazz = Class.forName(line, false, classLoader);
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException(
"Error when load extension class(interface: " + type
+ ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
map.put(name, clazz);
}
} catch (Throwable t) {
}
}
} // end of while read lines
} finally {
reader.close();
}
} catch (Throwable t) {
log.error("", "Exception when load extension class(interface: " + type + ", class file: "
+ url + ") in " + url, t);
}
} // end of while urls
}
} catch (Throwable t) {
log.error("", "Exception when load extension class(interface: " + type + ", description file: "
+ fileName + ").", t);
}
MAP_CLASSES_CACHE.put(type, map);
}
}