add simple spi impl
This commit is contained in:
		
							
								
								
									
										379
									
								
								src/main/java/io/github/ehlxr/extension/ExtensionLoader.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										379
									
								
								src/main/java/io/github/ehlxr/extension/ExtensionLoader.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,379 @@ | ||||
| /* | ||||
|  * The MIT License (MIT) | ||||
|  * | ||||
|  * Copyright © 2020 xrv <xrg@live.com> | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in | ||||
|  * all copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
|  * THE SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| package io.github.ehlxr.extension; | ||||
|  | ||||
| import io.netty.util.internal.StringUtil; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import java.io.BufferedReader; | ||||
| import java.io.InputStreamReader; | ||||
| import java.io.PrintWriter; | ||||
| import java.io.StringWriter; | ||||
| import java.net.URL; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.*; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
| import java.util.concurrent.ConcurrentMap; | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| /** | ||||
|  * 加载和管理扩展(简化版 Dubbo SPI) | ||||
|  * | ||||
|  * @author ehlxr | ||||
|  */ | ||||
| @SuppressWarnings("unused") | ||||
| public class ExtensionLoader<T> { | ||||
|     private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class); | ||||
|  | ||||
|     private static final String EXTENSIONS_DIRECTORY = "META-INF/extensions/"; | ||||
|     private static final String EXTENSIONS_INTERNAL_DIRECTORY = "META-INF/extensions/internal/"; | ||||
|  | ||||
|     private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*,+\\s*"); | ||||
|  | ||||
|     private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(); | ||||
|     private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(); | ||||
|  | ||||
|     private final Class<T> type; | ||||
|     private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>(); | ||||
|     private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>(); | ||||
|     private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>(); | ||||
|     private final Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<>(); | ||||
|     private String defaultExtension; | ||||
|  | ||||
|     private ExtensionLoader(Class<T> type) { | ||||
|         this.type = type; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * {@link ExtensionLoader} 的工厂方法。 | ||||
|      * | ||||
|      * @param type 扩展点接口类型 | ||||
|      * @param <T>  扩展点类型 | ||||
|      * @return {@link ExtensionLoader} 实例 | ||||
|      * @throws IllegalArgumentException 参数为 <code>null</code>; | ||||
|      *                                  或是扩展点接口上没有 {@link SPI} 注解。 | ||||
|      */ | ||||
|     @SuppressWarnings("unchecked") | ||||
|     public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { | ||||
|         if (type == null) { | ||||
|             throw new IllegalArgumentException("SPI type == null"); | ||||
|         } | ||||
|         if (!type.isInterface()) { | ||||
|             throw new IllegalArgumentException("SPI type(" + type.getName() + ") is not interface!"); | ||||
|         } | ||||
|         if (!type.isAnnotationPresent(SPI.class)) { | ||||
|             throw new IllegalArgumentException("type(" + type.getName() + | ||||
|                     ") is not a extension, because WITHOUT @SPI Annotation!"); | ||||
|         } | ||||
|  | ||||
|         ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); | ||||
|         if (loader == null) { | ||||
|             EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<>(type)); | ||||
|             loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); | ||||
|         } | ||||
|         return loader; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the String of Throwable, like the output of {@link Throwable#printStackTrace()}. | ||||
|      * | ||||
|      * @param throwable the input throwable. | ||||
|      */ | ||||
|     private static String throwable2String(Throwable throwable) { | ||||
|         StringWriter w = new StringWriter(1024); | ||||
|         try (PrintWriter p = new PrintWriter(w)) { | ||||
|             throwable.printStackTrace(p); | ||||
|             return w.toString(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public T getExtension(String name) { | ||||
|         if (StringUtil.isNullOrEmpty(name)) { | ||||
|             throw new IllegalArgumentException("SPI name == null"); | ||||
|         } | ||||
|  | ||||
|         Holder<Object> holder = cachedInstances.get(name); | ||||
|         if (holder == null) { | ||||
|             cachedInstances.putIfAbsent(name, new Holder<>()); | ||||
|             holder = cachedInstances.get(name); | ||||
|         } | ||||
|  | ||||
|         Object instance = holder.get(); | ||||
|         if (instance == null) { | ||||
|             synchronized (cachedInstances) { | ||||
|                 instance = holder.get(); | ||||
|                 if (instance == null) { | ||||
|                     instance = createExtension(name); | ||||
|                     holder.set(instance); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         //noinspection unchecked | ||||
|         return (T) instance; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 返回缺省的扩展。 | ||||
|      * | ||||
|      * @throws IllegalStateException 指定的扩展没有设置缺省扩展点 | ||||
|      */ | ||||
|     public T getDefaultExtension() { | ||||
|         loadExtensionClasses0(); | ||||
|  | ||||
|         if (null == defaultExtension || defaultExtension.length() == 0) { | ||||
|             throw new IllegalStateException("No default extension on extension " + type.getName()); | ||||
|         } | ||||
|         return getExtension(defaultExtension); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取扩展点实现的所有扩展点名。 | ||||
|      * | ||||
|      * @since 0.1.0 | ||||
|      */ | ||||
|     public Set<String> getSupportedExtensions() { | ||||
|         Map<String, Class<?>> classes = getExtensionClasses(); | ||||
|         return Collections.unmodifiableSet(new HashSet<>(classes.keySet())); | ||||
|     } | ||||
|  | ||||
|     public String getExtensionName(Class<?> spi) { | ||||
|         getExtensionClasses(); | ||||
|         return cachedNames.get(spi); | ||||
|     } | ||||
|  | ||||
|     private T createExtension(String name) { | ||||
|         Class<?> clazz = getExtensionClasses().get(name); | ||||
|         if (clazz == null) { | ||||
|             throw findExtensionClassLoadException(name); | ||||
|         } | ||||
|         try { | ||||
|             //noinspection unchecked | ||||
|             T instance = (T) EXTENSION_INSTANCES.get(clazz); | ||||
|             if (instance == null) { | ||||
|                 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); | ||||
|                 //noinspection unchecked | ||||
|                 instance = (T) EXTENSION_INSTANCES.get(clazz); | ||||
|             } | ||||
|  | ||||
|             return instance; | ||||
|         } catch (Throwable t) { | ||||
|             throw new IllegalStateException("SPI instance(name: " + name + ", class: " + | ||||
|                     type + ")  could not be instantiated: " + t.getMessage(), t); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private Map<String, Class<?>> getExtensionClasses() { | ||||
|         Map<String, Class<?>> classes = cachedClasses.get(); | ||||
|         if (classes == null) { | ||||
|             synchronized (cachedClasses) { | ||||
|                 classes = cachedClasses.get(); | ||||
|                 if (classes == null) { | ||||
|                     loadExtensionClasses0(); | ||||
|                     classes = cachedClasses.get(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return classes; | ||||
|     } | ||||
|  | ||||
|     private IllegalStateException findExtensionClassLoadException(String name) { | ||||
|         for (Map.Entry<String, IllegalStateException> entry : exceptions.entrySet()) { | ||||
|             if (entry.getKey().toLowerCase().contains(name.toLowerCase())) { | ||||
|                 return entry.getValue(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         int i = 1; | ||||
|         StringBuilder buf = new StringBuilder("No such extension " + type.getName() + " by name " + name); | ||||
|         for (Map.Entry<String, IllegalStateException> entry : exceptions.entrySet()) { | ||||
|             if (i == 1) { | ||||
|                 buf.append(", possible causes: "); | ||||
|             } | ||||
|  | ||||
|             buf.append("\r\n("); | ||||
|             buf.append(i++); | ||||
|             buf.append(") "); | ||||
|             buf.append(entry.getKey()); | ||||
|             buf.append(":\r\n"); | ||||
|             buf.append(throwable2String(entry.getValue())); | ||||
|         } | ||||
|         return new IllegalStateException(buf.toString()); | ||||
|     } | ||||
|  | ||||
|     private void loadExtensionClasses0() { | ||||
|         final SPI annotation = type.getAnnotation(SPI.class); | ||||
|         if (annotation != null) { | ||||
|             String value = annotation.value(); | ||||
|             if ((value = value.trim()).length() > 0) { | ||||
|                 String[] names = NAME_SEPARATOR.split(value); | ||||
|                 if (names.length > 1) { | ||||
|                     throw new IllegalStateException("more than 1 default extension name on extension " + | ||||
|                             type.getName() + ": " + Arrays.toString(names)); | ||||
|                 } | ||||
|                 if (names.length == 1 && names[0].trim().length() > 0) { | ||||
|                     defaultExtension = names[0].trim(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         Map<String, Class<?>> extensionClasses = new HashMap<>(); | ||||
|         loadFile(extensionClasses, EXTENSIONS_DIRECTORY); | ||||
|         loadFile(extensionClasses, EXTENSIONS_INTERNAL_DIRECTORY); | ||||
|         cachedClasses.set(extensionClasses); | ||||
|     } | ||||
|  | ||||
|     private void loadFile(Map<String, Class<?>> extensionClasses, String dir) { | ||||
|         String fileName = dir + type.getName(); | ||||
|         try { | ||||
|             Enumeration<URL> urls; | ||||
|             ClassLoader classLoader = ExtensionLoader.class.getClassLoader(); | ||||
|             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(), StandardCharsets.UTF_8)) | ||||
|                     ) { | ||||
|                         String line; | ||||
|                         while ((line = reader.readLine()) != null) { | ||||
|                             // delete comments | ||||
|                             final int ci = line.indexOf('#'); | ||||
|                             if (ci >= 0) { | ||||
|                                 line = line.substring(0, ci); | ||||
|                             } | ||||
|                             line = line.trim(); | ||||
|                             if (line.length() == 0) { | ||||
|                                 continue; | ||||
|                             } | ||||
|  | ||||
|                             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<? extends T> clazz = Class.forName(line, true, classLoader).asSubclass(type); | ||||
|                                     if (!type.isAssignableFrom(clazz)) { | ||||
|                                         throw new IllegalStateException("Error when load extension class(interface: " + | ||||
|                                                 type.getName() + ", class line: " + clazz.getName() + "), class " | ||||
|                                                 + clazz.getName() + "is not subtype of interface."); | ||||
|                                     } | ||||
|  | ||||
|                                     if (name == null || name.length() == 0) { | ||||
|                                         // clazz: xxx.xxx.ZfyAPI | ||||
|                                         // type: xxx.xxx.API | ||||
|                                         // -> name: zfy | ||||
|                                         if (clazz.getSimpleName().length() > type.getSimpleName().length() | ||||
|                                                 && clazz.getSimpleName().endsWith(type.getSimpleName())) { | ||||
|                                             name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() | ||||
|                                                     - type.getSimpleName().length()).toLowerCase(); | ||||
|                                         } else { | ||||
|                                             throw new IllegalStateException("No such extension name for the class " | ||||
|                                                     + clazz.getName() + " in the config " + url); | ||||
|                                         } | ||||
|                                     } | ||||
|  | ||||
|  | ||||
|                                     if (!cachedNames.containsKey(clazz)) { | ||||
|                                         cachedNames.put(clazz, name); | ||||
|                                     } | ||||
|  | ||||
|                                     Class<?> c = extensionClasses.get(name); | ||||
|                                     if (c == null) { | ||||
|                                         extensionClasses.put(name, clazz); | ||||
|                                     } else if (c != clazz) { | ||||
|                                         throw new IllegalStateException("Duplicate extension " | ||||
|                                                 + type.getName() + " name " + name + " on " + c.getName() | ||||
|                                                 + " and " + clazz.getName()); | ||||
|                                     } | ||||
|                                 } | ||||
|                             } catch (Throwable t) { | ||||
|                                 IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " | ||||
|                                         + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t); | ||||
|                                 exceptions.put(line, e); | ||||
|                             } | ||||
|                         } | ||||
|                     } catch (Throwable t) { | ||||
|                         logger.error("Exception when load extension class(interface: " + | ||||
|                                 type + ", class file: " + url + ") in " + url, t); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } catch (Throwable t) { | ||||
|             logger.error("Exception when load extension class(interface: " + | ||||
|                     type + ", description file: " + fileName + ").", t); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return this.getClass().getName() + "[" + type.getName() + "]"; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Holds a value of type <code>T</code>. | ||||
|      */ | ||||
|     @SuppressWarnings("unused") | ||||
|     private static final class Holder<T> { | ||||
|         /** | ||||
|          * The value contained in the holder. | ||||
|          */ | ||||
|         private volatile T value; | ||||
|  | ||||
|         /** | ||||
|          * Creates a new holder with a <code>null</code> value. | ||||
|          */ | ||||
|         Holder() { | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Create a new holder with the specified value. | ||||
|          * | ||||
|          * @param value The value to be stored in the holder. | ||||
|          */ | ||||
|         public Holder(T value) { | ||||
|             this.value = value; | ||||
|         } | ||||
|  | ||||
|         public T get() { | ||||
|             return value; | ||||
|         } | ||||
|  | ||||
|         public void set(T value) { | ||||
|             this.value = value; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										48
									
								
								src/main/java/io/github/ehlxr/extension/SPI.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/main/java/io/github/ehlxr/extension/SPI.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| /* | ||||
|  * The MIT License (MIT) | ||||
|  * | ||||
|  * Copyright © 2020 xrv <xrg@live.com> | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in | ||||
|  * all copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
|  * THE SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| package io.github.ehlxr.extension; | ||||
|  | ||||
| import io.github.ehlxr.did.extension.ExtensionLoader; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| /** | ||||
|  * 把一个接口标识成扩展点。 | ||||
|  * <p> | ||||
|  * 没有此注释的接口 {@link ExtensionLoader} 会拒绝接管 | ||||
|  * | ||||
|  * @author ehlxr | ||||
|  * @see ExtensionLoader | ||||
|  */ | ||||
| @Documented | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Target({ElementType.TYPE}) | ||||
| public @interface SPI { | ||||
|  | ||||
|     /** | ||||
|      * the default extension name. | ||||
|      */ | ||||
|     String value() default ""; | ||||
| } | ||||
		Reference in New Issue
	
	Block a user