天天看點

聊聊如何實作一個支援鍵值對的SPI

  如果用過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;