From 79211f055cf5a91749115cd0f79dc409553c0e05 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Wed, 17 Apr 2019 14:23:48 +0800 Subject: [PATCH] Add @SpiOrder annotation and update SPI loader for loading SPI with highest precedence Signed-off-by: Eric Zhao --- .../alibaba/csp/sentinel/spi/SpiOrder.java | 48 ++++++ .../alibaba/csp/sentinel/util/SpiLoader.java | 147 ++++++++++++++++-- 2 files changed, 183 insertions(+), 12 deletions(-) create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/spi/SpiOrder.java diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/spi/SpiOrder.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/spi/SpiOrder.java new file mode 100644 index 00000000..279ed310 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/spi/SpiOrder.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.spi; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Eric Zhao + * @since 1.6.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +public @interface SpiOrder { + + /** + * Represents the lowest precedence. + */ + int LOWEST_PRECEDENCE = Integer.MAX_VALUE; + /** + * Represents the highest precedence. + */ + int HIGHEST_PRECEDENCE = Integer.MIN_VALUE; + + /** + * The SPI precedence value. Lowest precedence by default. + * + * @return the precedence value + */ + int value() default LOWEST_PRECEDENCE; +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java index b057fc55..c4c20a55 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java @@ -15,11 +15,16 @@ */ package com.alibaba.csp.sentinel.util; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.spi.SpiOrder; + /** * @author Eric Zhao * @since 1.4.0 @@ -29,21 +34,139 @@ public final class SpiLoader { private static final Map SERVICE_LOADER_MAP = new ConcurrentHashMap(); public static T loadFirstInstance(Class clazz) { - String key = clazz.getName(); - // Not thread-safe, as it's expected to be resolved in a thread-safe context. - ServiceLoader serviceLoader = SERVICE_LOADER_MAP.get(key); - if (serviceLoader == null) { - serviceLoader = ServiceLoader.load(clazz); - SERVICE_LOADER_MAP.put(key, serviceLoader); - } - - Iterator iterator = serviceLoader.iterator(); - if (iterator.hasNext()) { - return iterator.next(); - } else { + try { + String key = clazz.getName(); + // Not thread-safe, as it's expected to be resolved in a thread-safe context. + ServiceLoader serviceLoader = SERVICE_LOADER_MAP.get(key); + if (serviceLoader == null) { + serviceLoader = ServiceLoader.load(clazz); + SERVICE_LOADER_MAP.put(key, serviceLoader); + } + + Iterator iterator = serviceLoader.iterator(); + if (iterator.hasNext()) { + return iterator.next(); + } else { + return null; + } + } catch (Throwable t) { + RecordLog.warn("[SpiLoader] ERROR: loadFirstInstance failed", t); + t.printStackTrace(); + return null; + } + } + + /** + * Load the SPI instance with highest priority. + * + * @param clazz class of the SPI + * @param SPI type + * @return the SPI instance with highest priority if exists, or else false + * @since 1.6.0 + */ + public static T loadHighestPriorityInstance(Class clazz) { + try { + String key = clazz.getName(); + // Not thread-safe, as it's expected to be resolved in a thread-safe context. + ServiceLoader serviceLoader = SERVICE_LOADER_MAP.get(key); + if (serviceLoader == null) { + serviceLoader = ServiceLoader.load(clazz); + SERVICE_LOADER_MAP.put(key, serviceLoader); + } + + SpiOrderWrapper w = null; + for (T spi : serviceLoader) { + int order = SpiOrderResolver.resolveOrder(spi); + RecordLog.info("[SpiLoader] Found {0} SPI: {1} with order " + order, clazz.getSimpleName(), + spi.getClass().getCanonicalName()); + if (w == null || order < w.order) { + w = new SpiOrderWrapper<>(order, spi); + } + } + return w == null ? null : w.spi; + } catch (Throwable t) { + RecordLog.warn("[SpiLoader] ERROR: loadHighestPriorityInstance failed", t); + t.printStackTrace(); return null; } } + /** + * Load and sorted SPI instance list. + * + * @param clazz class of the SPI + * @param SPI type + * @return sorted SPI instance list + * @since 1.6.0 + */ + public static List loadInstanceListSorted(Class clazz) { + try { + String key = clazz.getName(); + // Not thread-safe, as it's expected to be resolved in a thread-safe context. + ServiceLoader serviceLoader = SERVICE_LOADER_MAP.get(key); + if (serviceLoader == null) { + serviceLoader = ServiceLoader.load(clazz); + SERVICE_LOADER_MAP.put(key, serviceLoader); + } + + List> orderWrappers = new ArrayList<>(); + for (T spi : serviceLoader) { + int order = SpiOrderResolver.resolveOrder(spi); + // Since SPI is lazy initialized in ServiceLoader, we use online sort algorithm here. + SpiOrderResolver.insertSorted(orderWrappers, spi, order); + RecordLog.info("[SpiLoader] Found {0} SPI: {1} with order " + order, clazz.getSimpleName(), + spi.getClass().getCanonicalName()); + } + List list = new ArrayList<>(); + for (int i = 0; i < orderWrappers.size(); i++) { + list.add(i, orderWrappers.get(i).spi); + } + return list; + } catch (Throwable t) { + RecordLog.warn("[SpiLoader] ERROR: loadInstanceListSorted failed", t); + t.printStackTrace(); + return new ArrayList<>(); + } + } + + private static class SpiOrderResolver { + private static void insertSorted(List> list, T spi, int order) { + int idx = 0; + for (; idx < list.size(); idx++) { + if (list.get(idx).getOrder() > order) { + break; + } + } + list.add(idx, new SpiOrderWrapper<>(order, spi)); + } + + private static int resolveOrder(T spi) { + if (!spi.getClass().isAnnotationPresent(SpiOrder.class)) { + // Lowest precedence by default. + return SpiOrder.LOWEST_PRECEDENCE; + } else { + return spi.getClass().getAnnotation(SpiOrder.class).value(); + } + } + } + + private static class SpiOrderWrapper { + private final int order; + private final T spi; + + SpiOrderWrapper(int order, T spi) { + this.order = order; + this.spi = spi; + } + + int getOrder() { + return order; + } + + T getSpi() { + return spi; + } + } + private SpiLoader() {} }