如果用過JDK提供的SPI機制的朋友,大概就會知道它無法按需加載。之前寫過一篇文章聊聊基于jdk實作的spi如何與spring整合實作依賴注入。利用spring的依賴注入來實作spi按需加載,這種方案就是要借用spring。今天我們在聊聊另外一種實作方式,就是我們自己手寫一個
實作思路
整體思路和jdk實作spi差不多,如果對jdk實作的spi不了解,可以檢視我之前寫的文章java之spi機制簡介。差别就是我們在配置檔案是以key-value的形式存在,形如
springMysql=com.github.lybgeek.dialect.mysql.SpringMysqlDialect
實作邏輯鄭州看心理醫生多少錢http://www.hyde8025.com/
1、約定好要進行解析的目錄,比如META-INF/services/
private static final String SERVICE_DIRECTORY = "META-INF/services/";
2、約定好要解析的檔案名命名,比如
com.github.lybgeek.dialect.SpringSqlDialect
3、約定好檔案内容格式,比如
4、擷取約定好的目錄,解析檔案,并将相應内容放入緩存
/**
* Load files under SERVICE_DIRECTORY.
*/
private void loadDirectory(final Map> classes) {
String fileName = SERVICE_DIRECTORY + clazz.getName();
try {
ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
Enumeration urls = classLoader != null ? classLoader.getResources(fileName)
: ClassLoader.getSystemResources(fileName);
if (urls != null) {
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
loadResources(classes, url);
}
} catch (IOException t) {
log.error("load extension class error {}", fileName, t);
private void loadResources(final Map> classes, final URL url) throws IOException {
try (InputStream inputStream = url.openStream()) {
Properties properties = new Properties();
properties.load(inputStream);
properties.forEach((k, v) -> {
String name = (String) k;
String classPath = (String) v;
if (StringUtils.isNotBlank(name) && StringUtils.isNotBlank(classPath)) {
loadClass(classes, name, classPath);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("load extension resources error", e);
});
} catch (IOException e) {
private void loadClass(final Map> classes,
final String name, final String classPath) throws ClassNotFoundException {
Class subClass = Class.forName(classPath);
if (!clazz.isAssignableFrom(subClass)) {
throw new IllegalStateException("load extension resources error," + subClass + " subtype is not of " + clazz);
Activate annotation = subClass.getAnnotation(Activate.class);
if (annotation == null) {
throw new IllegalStateException("load extension resources error," + subClass + " with Activate annotation");
Class oldClass = classes.get(name);
if (oldClass == null) {
classes.put(name, subClass);
} else if (oldClass != subClass) {
throw new IllegalStateException("load extension resources error,Duplicate class " + clazz.getName() + " name " + name + " on " + oldClass.getName() + " or
" + subClass.getName());
5、根據key,去緩存查找相應的類執行個體
public T getActivate(final String name) {
if (StringUtils.isBlank(name)) {
throw new NullPointerException("get Activate name is null");
Holder objectHolder = cachedInstances.get(name);
if (objectHolder == null) {
cachedInstances.putIfAbsent(name, new Holder<>());
objectHolder = cachedInstances.get(name);
Object value = objectHolder.getValue();
if (value == null) {
synchronized (cachedInstances) {
value = objectHolder.getValue();
value = createExtension(name);
objectHolder.setValue(value);
return (T) value;
核心代碼
@Slf4j
@SuppressWarnings("all")
public final class ExtensionLoader {
private static final Map, ExtensionLoader> LOADERS = new ConcurrentHashMap<>();
private final Class clazz;
private final Holder>> cachedClasses = new Holder<>();
private final Map> cachedInstances = new ConcurrentHashMap<>();
private final Map, Object> ActivateInstances = new ConcurrentHashMap<>();
private String cachedDefaultName;
* Instantiates a new Extension loader.
*
* @param clazz the clazz.
private ExtensionLoader(final Class clazz) {
this.clazz = clazz;
if (clazz != ExtensionFactory.class) {
ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getExtensionClasses();
* Gets extension loader.
* @param the type parameter
* @param clazz the clazz
* @return the extension loader.
public static ExtensionLoader getExtensionLoader(final Class clazz) {
if (clazz == null) {
throw new NullPointerException("extension clazz is null");
if (!clazz.isInterface()) {
throw new IllegalArgumentException("extension clazz (" + clazz + ") is not
interface!");
if (!clazz.isAnnotationPresent(SPI.class)) {
throw new IllegalArgumentException("extension clazz (" + clazz + ") without @" + SPI.class + " Annotation");
ExtensionLoader extensionLoader = (ExtensionLoader) LOADERS.get(clazz);
if (extensionLoader != null) {
return extensionLoader;
LOADERS.putIfAbsent(clazz, new ExtensionLoader<>(clazz));
return (ExtensionLoader) LOADERS.get(clazz);
* Gets default Activate.
* @return the default Activate.
public T getDefaultActivate() {
getExtensionClasses();
if (StringUtils.isBlank(cachedDefaultName)) {
return null;
return getActivate(cachedDefaultName);
* Gets Activate.
* @param name the name
* @return the Activate.
public Set getSupportedExtensions() {
Map> clazzes = getExtensionClasses();
return Collections.unmodifiableSet(new TreeSet<>(clazzes.keySet()));
@SuppressWarnings("unchecked")
private T createExtension(final String name) {
Class aClass = getExtensionClasses().get(name);
if (aClass == null) {
throw new IllegalArgumentException("name is error");
Object o = ActivateInstances.get(aClass);
if (o == null) {
ActivateInstances.putIfAbsent(aClass, aClass.newInstance());
o = ActivateInstances.get(aClass);
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: "
+ aClass + ") could not be instantiated: " + e.getMessage(), e);
return (T) o;
* Gets extension classes.
* * @return the extension classes
public Map> getExtensionClasses() {
Map> classes = cachedClasses.getValue();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.getValue();
classes = loadExtensionClass();
cachedClasses.setValue(classes);
return classes;
private Map> loadExtensionClass() {
SPI annotation = clazz.getAnnotation(SPI.class);
if (annotation != null) {
String value = annotation.value();
if (StringUtils.isNotBlank(value)) {
cachedDefaultName = value;
Map> classes = new HashMap<>(16);
loadDirectory(classes);
throw new IllegalStateException("load extension resources error,Duplicate class " +
clazz.getName() + " name " + name + " on " + oldClass.getName() + " or " +
subClass.getName());
* The type Holder.
* @param the type parameter.
public static class Holder {
private volatile T value;
* Gets value.
* @return the value
public T getValue() {
return value;
* Sets value.
* @param value the value
public void setValue(final T value) {
this.value = value;