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