* Add `@Spi` annotation as the general annotation for SPI definition. * Add isDefault in @Spi, add loadDefaultInstance and improve loadFirstInstanceOrDefault method, improve test cases * Add SpiLoaderException class for thrown when something goes wrong while loading Provider * Rearrange packages of base SPI mechanism NOTE: this PR contains breaking changes regarding API.master
@@ -27,7 +27,7 @@ import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; | |||
import com.alibaba.csp.sentinel.property.PropertyListener; | |||
import com.alibaba.csp.sentinel.property.SentinelProperty; | |||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||
import com.alibaba.csp.sentinel.util.SpiLoader; | |||
import com.alibaba.csp.sentinel.spi.SpiLoader; | |||
import com.alibaba.csp.sentinel.util.StringUtil; | |||
/** | |||
@@ -59,7 +59,7 @@ public final class GatewayApiDefinitionManager { | |||
} | |||
private static void initializeApiChangeObserverSpi() { | |||
List<ApiDefinitionChangeObserver> listeners = SpiLoader.loadInstanceList(ApiDefinitionChangeObserver.class); | |||
List<ApiDefinitionChangeObserver> listeners = SpiLoader.of(ApiDefinitionChangeObserver.class).loadInstanceList(); | |||
for (ApiDefinitionChangeObserver e : listeners) { | |||
API_CHANGE_OBSERVERS.put(e.getClass().getCanonicalName(), e); | |||
RecordLog.info("[GatewayApiDefinitionManager] ApiDefinitionChangeObserver added: {}" | |||
@@ -27,13 +27,13 @@ import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker; | |||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException; | |||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | |||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage; | |||
import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.6.1 | |||
*/ | |||
@SpiOrder(-4000) | |||
@Spi(order = -4000) | |||
public class GatewayFlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||
@Override | |||
@@ -21,8 +21,9 @@ import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder; | |||
* @author Eric Zhao | |||
* @since 1.6.1 | |||
* | |||
* @deprecated since 1.7.2, we can use @SpiOrder(-4000) to adjust the order of {@link GatewayFlowSlot}, | |||
* @deprecated since 1.7.2, we can use @Spi(order = -4000) to adjust the order of {@link GatewayFlowSlot}, | |||
* this class is reserved for compatibility with older versions. | |||
* | |||
* @see GatewayFlowSlot | |||
* @see DefaultSlotChainBuilder | |||
*/ | |||
@@ -15,7 +15,7 @@ | |||
*/ | |||
package com.alibaba.csp.sentinel.cluster.client.codec; | |||
import com.alibaba.csp.sentinel.util.SpiLoader; | |||
import com.alibaba.csp.sentinel.spi.SpiLoader; | |||
import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter; | |||
import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
@@ -34,7 +34,7 @@ public final class ClientEntityCodecProvider { | |||
} | |||
private static void resolveInstance() { | |||
RequestEntityWriter writer = SpiLoader.loadFirstInstance(RequestEntityWriter.class); | |||
RequestEntityWriter writer = SpiLoader.of(RequestEntityWriter.class).loadFirstInstance(); | |||
if (writer == null) { | |||
RecordLog.warn("[ClientEntityCodecProvider] No existing request entity writer, resolve failed"); | |||
} else { | |||
@@ -42,7 +42,7 @@ public final class ClientEntityCodecProvider { | |||
RecordLog.info("[ClientEntityCodecProvider] Request entity writer resolved: {}", | |||
requestEntityWriter.getClass().getCanonicalName()); | |||
} | |||
ResponseEntityDecoder decoder = SpiLoader.loadFirstInstance(ResponseEntityDecoder.class); | |||
ResponseEntityDecoder decoder = SpiLoader.of(ResponseEntityDecoder.class).loadFirstInstance(); | |||
if (decoder == null) { | |||
RecordLog.warn("[ClientEntityCodecProvider] No existing response entity decoder, resolve failed"); | |||
} else { | |||
@@ -22,6 +22,7 @@ import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; | |||
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; | |||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; | |||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
import java.util.Collection; | |||
@@ -31,6 +32,7 @@ import java.util.Collection; | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
@Spi(isDefault = true) | |||
public class DefaultTokenService implements TokenService { | |||
@Override | |||
@@ -16,9 +16,8 @@ | |||
package com.alibaba.csp.sentinel.cluster.server; | |||
import com.alibaba.csp.sentinel.cluster.TokenService; | |||
import com.alibaba.csp.sentinel.cluster.flow.DefaultTokenService; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.util.SpiLoader; | |||
import com.alibaba.csp.sentinel.spi.SpiLoader; | |||
/** | |||
* @author Eric Zhao | |||
@@ -37,7 +36,7 @@ public final class TokenServiceProvider { | |||
} | |||
private static void resolveTokenServiceSpi() { | |||
service = SpiLoader.loadFirstInstanceOrDefault(TokenService.class, DefaultTokenService.class); | |||
service = SpiLoader.of(TokenService.class).loadFirstInstanceOrDefault(); | |||
if (service != null) { | |||
RecordLog.info("[TokenServiceProvider] Global token service resolved: " | |||
+ service.getClass().getCanonicalName()); | |||
@@ -18,7 +18,7 @@ package com.alibaba.csp.sentinel.cluster.server.codec; | |||
import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder; | |||
import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.util.SpiLoader; | |||
import com.alibaba.csp.sentinel.spi.SpiLoader; | |||
/** | |||
* @author Eric Zhao | |||
@@ -34,7 +34,7 @@ public final class ServerEntityCodecProvider { | |||
} | |||
private static void resolveInstance() { | |||
ResponseEntityWriter writer = SpiLoader.loadFirstInstance(ResponseEntityWriter.class); | |||
ResponseEntityWriter writer = SpiLoader.of(ResponseEntityWriter.class).loadFirstInstance(); | |||
if (writer == null) { | |||
RecordLog.warn("[ServerEntityCodecProvider] No existing response entity writer, resolve failed"); | |||
} else { | |||
@@ -42,7 +42,7 @@ public final class ServerEntityCodecProvider { | |||
RecordLog.info("[ServerEntityCodecProvider] Response entity writer resolved: {}", | |||
responseEntityWriter.getClass().getCanonicalName()); | |||
} | |||
RequestEntityDecoder decoder = SpiLoader.loadFirstInstance(RequestEntityDecoder.class); | |||
RequestEntityDecoder decoder = SpiLoader.of(RequestEntityDecoder.class).loadFirstInstance(); | |||
if (decoder == null) { | |||
RecordLog.warn("[ServerEntityCodecProvider] No existing request entity decoder, resolve failed"); | |||
} else { | |||
@@ -15,12 +15,12 @@ | |||
*/ | |||
package com.alibaba.csp.sentinel.cluster.server.processor; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.ServiceLoader; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import com.alibaba.csp.sentinel.cluster.annotation.RequestType; | |||
import com.alibaba.csp.sentinel.spi.ServiceLoaderUtil; | |||
import com.alibaba.csp.sentinel.spi.SpiLoader; | |||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||
/** | |||
@@ -31,15 +31,13 @@ public final class RequestProcessorProvider { | |||
private static final Map<Integer, RequestProcessor> PROCESSOR_MAP = new ConcurrentHashMap<>(); | |||
private static final ServiceLoader<RequestProcessor> SERVICE_LOADER = ServiceLoaderUtil.getServiceLoader( | |||
RequestProcessor.class); | |||
static { | |||
loadAndInit(); | |||
} | |||
private static void loadAndInit() { | |||
for (RequestProcessor processor : SERVICE_LOADER) { | |||
List<RequestProcessor> processors = SpiLoader.of(RequestProcessor.class).loadInstanceList(); | |||
for (RequestProcessor processor : processors) { | |||
Integer type = parseRequestType(processor); | |||
if (type != null) { | |||
PROCESSOR_MAP.put(type, processor); | |||
@@ -70,5 +70,17 @@ public final class Constants { | |||
*/ | |||
public static volatile boolean ON = true; | |||
/** | |||
* Order of default processor slots | |||
*/ | |||
public static final int ORDER_NODE_SELECTOR_SLOT = -10000; | |||
public static final int ORDER_CLUSTER_BUILDER_SLOT = -9000; | |||
public static final int ORDER_LOG_SLOT = -8000; | |||
public static final int ORDER_STATISTIC_SLOT = -7000; | |||
public static final int ORDER_AUTHORITY_SLOT = -6000; | |||
public static final int ORDER_SYSTEM_SLOT = -5000; | |||
public static final int ORDER_FLOW_SLOT = -2000; | |||
public static final int ORDER_DEGRADE_SLOT = -1000; | |||
private Constants() {} | |||
} |
@@ -16,7 +16,7 @@ | |||
package com.alibaba.csp.sentinel.cluster.client; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.util.SpiLoader; | |||
import com.alibaba.csp.sentinel.spi.SpiLoader; | |||
/** | |||
* Provider for a universal {@link ClusterTokenClient} instance. | |||
@@ -38,7 +38,7 @@ public final class TokenClientProvider { | |||
} | |||
private static void resolveTokenClientInstance() { | |||
ClusterTokenClient resolvedClient = SpiLoader.loadFirstInstance(ClusterTokenClient.class); | |||
ClusterTokenClient resolvedClient = SpiLoader.of(ClusterTokenClient.class).loadFirstInstance(); | |||
if (resolvedClient == null) { | |||
RecordLog.info( | |||
"[TokenClientProvider] No existing cluster token client, cluster client mode will not be activated"); | |||
@@ -16,7 +16,7 @@ | |||
package com.alibaba.csp.sentinel.cluster.server; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.util.SpiLoader; | |||
import com.alibaba.csp.sentinel.spi.SpiLoader; | |||
/** | |||
* @author Eric Zhao | |||
@@ -31,7 +31,7 @@ public final class EmbeddedClusterTokenServerProvider { | |||
} | |||
private static void resolveInstance() { | |||
EmbeddedClusterTokenServer s = SpiLoader.loadFirstInstance(EmbeddedClusterTokenServer.class); | |||
EmbeddedClusterTokenServer s = SpiLoader.of(EmbeddedClusterTokenServer.class).loadFirstInstance(); | |||
if (s == null) { | |||
RecordLog.warn("[EmbeddedClusterTokenServerProvider] No existing cluster token server, cluster server mode will not be activated"); | |||
} else { | |||
@@ -41,6 +41,11 @@ public final class SentinelConfig { | |||
*/ | |||
public static final int APP_TYPE_COMMON = 0; | |||
/** | |||
* Parameter value for using context classloader. | |||
*/ | |||
private static final String CLASSLOADER_CONTEXT = "context"; | |||
private static final Map<String, String> props = new ConcurrentHashMap<>(); | |||
private static int appType = APP_TYPE_COMMON; | |||
@@ -307,6 +312,15 @@ public final class SentinelConfig { | |||
private static String toEnvKey(/*@NotBlank*/ String propKey) { | |||
return propKey.toUpperCase().replace('.', '_'); | |||
} | |||
/** | |||
* Whether use context classloader via config parameter | |||
* | |||
* @return Whether use context classloader | |||
*/ | |||
public static boolean shouldUseContextClassloader() { | |||
String classloaderConf = SentinelConfig.getConfig(SentinelConfig.SPI_CLASSLOADER); | |||
return CLASSLOADER_CONTEXT.equalsIgnoreCase(classloaderConf); | |||
} | |||
private SentinelConfig() {} | |||
} |
@@ -21,7 +21,7 @@ import java.util.ServiceLoader; | |||
import java.util.concurrent.atomic.AtomicBoolean; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.spi.ServiceLoaderUtil; | |||
import com.alibaba.csp.sentinel.spi.SpiLoader; | |||
/** | |||
* Load registered init functions and execute in order. | |||
@@ -43,9 +43,9 @@ public final class InitExecutor { | |||
return; | |||
} | |||
try { | |||
ServiceLoader<InitFunc> loader = ServiceLoaderUtil.getServiceLoader(InitFunc.class); | |||
List<InitFunc> initFuncs = SpiLoader.of(InitFunc.class).loadInstanceListSorted(); | |||
List<OrderWrapper> initList = new ArrayList<OrderWrapper>(); | |||
for (InitFunc initFunc : loader) { | |||
for (InitFunc initFunc : initFuncs) { | |||
RecordLog.info("[InitExecutor] Found init func: {}", initFunc.getClass().getCanonicalName()); | |||
insertSorted(initList, initFunc); | |||
} | |||
@@ -19,7 +19,7 @@ import java.util.ArrayList; | |||
import java.util.List; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.util.SpiLoader; | |||
import com.alibaba.csp.sentinel.spi.SpiLoader; | |||
/** | |||
* Get all {@link MetricExtension} via SPI. | |||
@@ -35,7 +35,7 @@ public class MetricExtensionProvider { | |||
} | |||
private static void resolveInstance() { | |||
List<MetricExtension> extensions = SpiLoader.loadInstanceList(MetricExtension.class); | |||
List<MetricExtension> extensions = SpiLoader.of(MetricExtension.class).loadInstanceList(); | |||
if (extensions.isEmpty()) { | |||
RecordLog.info("[MetricExtensionProvider] No existing MetricExtension found"); | |||
@@ -17,7 +17,7 @@ package com.alibaba.csp.sentinel.slotchain; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder; | |||
import com.alibaba.csp.sentinel.util.SpiLoader; | |||
import com.alibaba.csp.sentinel.spi.SpiLoader; | |||
/** | |||
* A provider for creating slot chains via resolved slot chain builder SPI. | |||
@@ -41,7 +41,7 @@ public final class SlotChainProvider { | |||
} | |||
// Resolve the slot chain builder SPI. | |||
slotChainBuilder = SpiLoader.loadFirstInstanceOrDefault(SlotChainBuilder.class, DefaultSlotChainBuilder.class); | |||
slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault(); | |||
if (slotChainBuilder == null) { | |||
// Should not go through here. | |||
@@ -21,7 +21,8 @@ import com.alibaba.csp.sentinel.slotchain.DefaultProcessorSlotChain; | |||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; | |||
import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder; | |||
import com.alibaba.csp.sentinel.util.SpiLoader; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
import com.alibaba.csp.sentinel.spi.SpiLoader; | |||
import java.util.List; | |||
@@ -31,14 +32,14 @@ import java.util.List; | |||
* @author qinan.qn | |||
* @author leyou | |||
*/ | |||
@Spi(isDefault = true) | |||
public class DefaultSlotChainBuilder implements SlotChainBuilder { | |||
@Override | |||
public ProcessorSlotChain build() { | |||
ProcessorSlotChain chain = new DefaultProcessorSlotChain(); | |||
// Note: the instances of ProcessorSlot should be different, since they are not stateless. | |||
List<ProcessorSlot> sortedSlotList = SpiLoader.loadPrototypeInstanceListSorted(ProcessorSlot.class); | |||
List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted(); | |||
for (ProcessorSlot slot : sortedSlotList) { | |||
if (!(slot instanceof AbstractLinkedProcessorSlot)) { | |||
RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain"); | |||
@@ -18,12 +18,13 @@ package com.alibaba.csp.sentinel.slots.block.authority; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import com.alibaba.csp.sentinel.Constants; | |||
import com.alibaba.csp.sentinel.context.Context; | |||
import com.alibaba.csp.sentinel.node.DefaultNode; | |||
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||
import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
/** | |||
* A {@link ProcessorSlot} that dedicates to {@link AuthorityRule} checking. | |||
@@ -31,7 +32,7 @@ import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
* @author leyou | |||
* @author Eric Zhao | |||
*/ | |||
@SpiOrder(-6000) | |||
@Spi(order = Constants.ORDER_AUTHORITY_SLOT) | |||
public class AuthoritySlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||
@Override | |||
@@ -17,6 +17,7 @@ package com.alibaba.csp.sentinel.slots.block.degrade; | |||
import java.util.List; | |||
import com.alibaba.csp.sentinel.Constants; | |||
import com.alibaba.csp.sentinel.Entry; | |||
import com.alibaba.csp.sentinel.context.Context; | |||
import com.alibaba.csp.sentinel.node.DefaultNode; | |||
@@ -25,7 +26,7 @@ import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; | |||
import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
/** | |||
* A {@link ProcessorSlot} dedicates to circuit breaking. | |||
@@ -33,7 +34,7 @@ import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
* @author Carpenter Lee | |||
* @author Eric Zhao | |||
*/ | |||
@SpiOrder(-1000) | |||
@Spi(order = Constants.ORDER_DEGRADE_SLOT) | |||
public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||
@Override | |||
@@ -15,12 +15,13 @@ | |||
*/ | |||
package com.alibaba.csp.sentinel.slots.block.flow; | |||
import com.alibaba.csp.sentinel.Constants; | |||
import com.alibaba.csp.sentinel.context.Context; | |||
import com.alibaba.csp.sentinel.node.DefaultNode; | |||
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||
import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||
import com.alibaba.csp.sentinel.util.function.Function; | |||
@@ -137,7 +138,7 @@ import java.util.Map; | |||
* @author jialiang.linjl | |||
* @author Eric Zhao | |||
*/ | |||
@SpiOrder(-2000) | |||
@Spi(order = Constants.ORDER_FLOW_SLOT) | |||
public class FlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||
private final FlowRuleChecker checker; | |||
@@ -18,6 +18,7 @@ package com.alibaba.csp.sentinel.slots.clusterbuilder; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import com.alibaba.csp.sentinel.Constants; | |||
import com.alibaba.csp.sentinel.EntryType; | |||
import com.alibaba.csp.sentinel.context.Context; | |||
import com.alibaba.csp.sentinel.context.ContextUtil; | |||
@@ -30,7 +31,7 @@ import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; | |||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; | |||
import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
/** | |||
* <p> | |||
@@ -45,7 +46,7 @@ import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
* | |||
* @author jialiang.linjl | |||
*/ | |||
@SpiOrder(-9000) | |||
@Spi(isSingleton = false, order = Constants.ORDER_CLUSTER_BUILDER_SLOT) | |||
public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||
/** | |||
@@ -15,19 +15,20 @@ | |||
*/ | |||
package com.alibaba.csp.sentinel.slots.logger; | |||
import com.alibaba.csp.sentinel.Constants; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.context.Context; | |||
import com.alibaba.csp.sentinel.node.DefaultNode; | |||
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||
import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
/** | |||
* A {@link com.alibaba.csp.sentinel.slotchain.ProcessorSlot} that is response for logging block exceptions | |||
* to provide concrete logs for troubleshooting. | |||
*/ | |||
@SpiOrder(-8000) | |||
@Spi(order = Constants.ORDER_LOG_SLOT) | |||
public class LogSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||
@Override | |||
@@ -15,6 +15,7 @@ | |||
*/ | |||
package com.alibaba.csp.sentinel.slots.nodeselector; | |||
import com.alibaba.csp.sentinel.Constants; | |||
import com.alibaba.csp.sentinel.context.Context; | |||
import com.alibaba.csp.sentinel.context.ContextUtil; | |||
import com.alibaba.csp.sentinel.node.ClusterNode; | |||
@@ -22,7 +23,7 @@ import com.alibaba.csp.sentinel.node.DefaultNode; | |||
import com.alibaba.csp.sentinel.node.EntranceNode; | |||
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||
import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
@@ -123,7 +124,7 @@ import java.util.Map; | |||
* @see EntranceNode | |||
* @see ContextUtil | |||
*/ | |||
@SpiOrder(-10000) | |||
@Spi(isSingleton = false, order = Constants.ORDER_NODE_SELECTOR_SLOT) | |||
public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> { | |||
/** | |||
@@ -21,7 +21,7 @@ import com.alibaba.csp.sentinel.node.Node; | |||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; | |||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback; | |||
import com.alibaba.csp.sentinel.slots.block.flow.PriorityWaitException; | |||
import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
import com.alibaba.csp.sentinel.util.TimeUtil; | |||
import com.alibaba.csp.sentinel.Constants; | |||
import com.alibaba.csp.sentinel.EntryType; | |||
@@ -48,7 +48,7 @@ import com.alibaba.csp.sentinel.slots.block.BlockException; | |||
* @author jialiang.linjl | |||
* @author Eric Zhao | |||
*/ | |||
@SpiOrder(-7000) | |||
@Spi(order = Constants.ORDER_STATISTIC_SLOT) | |||
public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||
@Override | |||
@@ -15,12 +15,13 @@ | |||
*/ | |||
package com.alibaba.csp.sentinel.slots.system; | |||
import com.alibaba.csp.sentinel.Constants; | |||
import com.alibaba.csp.sentinel.context.Context; | |||
import com.alibaba.csp.sentinel.node.DefaultNode; | |||
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||
import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
/** | |||
* A {@link ProcessorSlot} that dedicates to {@link SystemRule} checking. | |||
@@ -28,7 +29,7 @@ import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
* @author jialiang.linjl | |||
* @author leyou | |||
*/ | |||
@SpiOrder(-5000) | |||
@Spi(order = Constants.ORDER_SYSTEM_SLOT) | |||
public class SystemSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||
@Override | |||
@@ -1,45 +0,0 @@ | |||
/* | |||
* 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 | |||
* | |||
* http://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.util.ServiceLoader; | |||
import com.alibaba.csp.sentinel.config.SentinelConfig; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.7.0 | |||
*/ | |||
public final class ServiceLoaderUtil { | |||
private static final String CLASSLOADER_DEFAULT = "default"; | |||
private static final String CLASSLOADER_CONTEXT = "context"; | |||
public static <S> ServiceLoader<S> getServiceLoader(Class<S> clazz) { | |||
if (shouldUseContextClassloader()) { | |||
return ServiceLoader.load(clazz); | |||
} else { | |||
return ServiceLoader.load(clazz, clazz.getClassLoader()); | |||
} | |||
} | |||
public static boolean shouldUseContextClassloader() { | |||
String classloaderConf = SentinelConfig.getConfig(SentinelConfig.SPI_CLASSLOADER); | |||
return CLASSLOADER_CONTEXT.equalsIgnoreCase(classloaderConf); | |||
} | |||
private ServiceLoaderUtil() {} | |||
} |
@@ -0,0 +1,54 @@ | |||
/* | |||
* Copyright 1999-2018 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 | |||
* | |||
* http://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.*; | |||
/** | |||
* Annotation for Provider class of SPI. | |||
* | |||
* @see SpiLoader | |||
* @author cdfive | |||
*/ | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@Target({ElementType.TYPE}) | |||
@Documented | |||
public @interface Spi { | |||
/** | |||
* Alias name of Provider class | |||
*/ | |||
String value() default ""; | |||
/** | |||
* Whether create singleton instance | |||
*/ | |||
boolean isSingleton() default true; | |||
/** | |||
* Whether is the default Provider | |||
*/ | |||
boolean isDefault() default false; | |||
/** | |||
* Order priority of Provider class | |||
*/ | |||
int order() default 0; | |||
int ORDER_HIGHEST = Integer.MIN_VALUE; | |||
int ORDER_LOWEST = Integer.MAX_VALUE; | |||
} |
@@ -0,0 +1,534 @@ | |||
/* | |||
* Copyright 1999-2018 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 | |||
* | |||
* http://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 com.alibaba.csp.sentinel.config.SentinelConfig; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||
import com.alibaba.csp.sentinel.util.StringUtil; | |||
import java.io.*; | |||
import java.lang.reflect.Modifier; | |||
import java.net.URL; | |||
import java.util.*; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import java.util.concurrent.atomic.AtomicBoolean; | |||
/** | |||
* A simple SPI loading facility. | |||
* | |||
* <p>SPI is short for Service Provider Interface.</p> | |||
* | |||
* <p> | |||
* Service is represented by a single type, that is, a single interface or an abstract class. | |||
* Provider is implementations of Service, that is, some classes which implement the interface or extends the abstract class. | |||
* </p> | |||
* | |||
* <p> | |||
* For Service type: | |||
* Must interface or abstract class. | |||
* </p> | |||
* | |||
* <p> | |||
* For Provider class: | |||
* Must have a zero-argument constructor so that they can be instantiated during loading. | |||
* </p> | |||
* | |||
* <p> | |||
* For Provider configuration file: | |||
* 1. The file contains a list of fully-qualified binary names of concrete provider classes, one per line. | |||
* 2. Space and tab characters surrounding each name, as well as blank lines, are ignored. | |||
* 3. The comment line character is #, all characters following it are ignored. | |||
* </p> | |||
* | |||
* <p> | |||
* Provide common functions, such as: | |||
* Load all Provider instance unsorted/sorted list. | |||
* Load highest/lowest order priority instance. | |||
* Load first-found or default instance. | |||
* Load instance by aliasname or provider class. | |||
* </p> | |||
* | |||
* @author Eric Zhao | |||
* @author cdfive | |||
* @since 1.4.0 | |||
* @see com.alibaba.csp.sentinel.spi.Spi | |||
* @see java.util.ServiceLoader | |||
*/ | |||
public final class SpiLoader<S> { | |||
// Default path for the folder of Provider configuration file | |||
private static final String SPI_FILE_PREFIX = "META-INF/services/"; | |||
// Cache the SpiLoader instances, key: classname of Service, value: SpiLoader instance | |||
private static final ConcurrentHashMap<String, SpiLoader> SPI_LOADER_MAP = new ConcurrentHashMap<>(); | |||
// Cache the classes of Provider | |||
private final List<Class<? extends S>> classList = Collections.synchronizedList(new ArrayList<Class<? extends S>>()); | |||
// Cache the sorted classes of Provider | |||
private final List<Class<? extends S>> sortedClassList = Collections.synchronizedList(new ArrayList<Class<? extends S>>()); | |||
/** | |||
* Cache the classes of Provider, key: aliasName, value: class of Provider. | |||
* Note: aliasName is the value of {@link Spi} when the Provider class has {@link Spi} annotation and value is not empty, | |||
* otherwise use classname of the Provider. | |||
*/ | |||
private final ConcurrentHashMap<String, Class<? extends S>> classMap = new ConcurrentHashMap<>(); | |||
// Cache the singleton instance of Provider, key: classname of Provider, value: Provider instance | |||
private final ConcurrentHashMap<String, S> singletonMap = new ConcurrentHashMap<>(); | |||
// Whether this SpiLoader has beend loaded, that is, loaded the Provider configuration file | |||
private final AtomicBoolean loaded = new AtomicBoolean(false); | |||
// Default provider class | |||
private Class<? extends S> defaultClass = null; | |||
// The Service class, must be interface or abstract class | |||
private Class<S> service; | |||
/** | |||
* Create SpiLoader instance via Service class | |||
* Cached by className, and load from cache first | |||
* | |||
* @param service Service class | |||
* @param <T> Service type | |||
* @return SpiLoader instance | |||
*/ | |||
public static <T> SpiLoader<T> of(Class<T> service) { | |||
AssertUtil.notNull(service, "SPI class cannot be null"); | |||
AssertUtil.isTrue(service.isInterface() || Modifier.isAbstract(service.getModifiers()), | |||
"SPI class[" + service.getName() + "] must be interface or abstract class"); | |||
String className = service.getName(); | |||
SpiLoader<T> spiLoader = SPI_LOADER_MAP.get(className); | |||
if (spiLoader == null) { | |||
synchronized (SpiLoader.class) { | |||
spiLoader = SPI_LOADER_MAP.get(className); | |||
if (spiLoader == null) { | |||
SPI_LOADER_MAP.putIfAbsent(className, new SpiLoader<>(service)); | |||
spiLoader = SPI_LOADER_MAP.get(className); | |||
} | |||
} | |||
} | |||
return spiLoader; | |||
} | |||
/** | |||
* Reset and clear all SpiLoader instances. | |||
* Package privilege, used only in test cases. | |||
*/ | |||
synchronized static void resetAndClearAll() { | |||
Set<Map.Entry<String, SpiLoader>> entries = SPI_LOADER_MAP.entrySet(); | |||
for (Map.Entry<String, SpiLoader> entry : entries) { | |||
SpiLoader spiLoader = entry.getValue(); | |||
spiLoader.resetAndClear(); | |||
} | |||
SPI_LOADER_MAP.clear(); | |||
} | |||
// Private access | |||
private SpiLoader(Class<S> service) { | |||
this.service = service; | |||
} | |||
/** | |||
* Load all Provider instances of the specified Service | |||
* | |||
* @return Provider instances list | |||
*/ | |||
public List<S> loadInstanceList() { | |||
load(); | |||
return createInstanceList(classList); | |||
} | |||
/** | |||
* Load all Provider instances of the specified Service, sorted by order value in class's {@link Spi} annotation | |||
* | |||
* @return Sorted Provider instances list | |||
*/ | |||
public List<S> loadInstanceListSorted() { | |||
load(); | |||
return createInstanceList(sortedClassList); | |||
} | |||
/** | |||
* Load highest order priority instance, order value is defined in class's {@link Spi} annotation | |||
* | |||
* @return Provider instance of highest order priority | |||
*/ | |||
public S loadHighestPriorityInstance() { | |||
load(); | |||
if (sortedClassList.size() == 0) { | |||
return null; | |||
} | |||
Class<? extends S> highestClass = sortedClassList.get(0); | |||
return createInstance(highestClass); | |||
} | |||
/** | |||
* Load lowest order priority instance, order value is defined in class's {@link Spi} annotation | |||
* | |||
* @return Provider instance of lowest order priority | |||
*/ | |||
public S loadLowestPriorityInstance() { | |||
load(); | |||
if (sortedClassList.size() == 0) { | |||
return null; | |||
} | |||
Class<? extends S> lowestClass = sortedClassList.get(sortedClassList.size() - 1); | |||
return createInstance(lowestClass); | |||
} | |||
/** | |||
* Load the first-found Provider instance | |||
* | |||
* @return Provider instance of first-found specific | |||
*/ | |||
public S loadFirstInstance() { | |||
load(); | |||
if (classList.size() == 0) { | |||
return null; | |||
} | |||
Class<? extends S> serviceClass = classList.get(0); | |||
S instance = createInstance(serviceClass); | |||
return instance; | |||
} | |||
/** | |||
* Load the first-found Provider instance,if not found, return default Provider instance | |||
* | |||
* @return Provider instance | |||
*/ | |||
public S loadFirstInstanceOrDefault() { | |||
load(); | |||
for (Class<? extends S> clazz : classList) { | |||
if (defaultClass == null || clazz != defaultClass) { | |||
return createInstance(clazz); | |||
} | |||
} | |||
return loadDefaultInstance(); | |||
} | |||
/** | |||
* Load default Provider instance | |||
* Provider class with @Spi(isDefault = true) | |||
* | |||
* @return default Provider instance | |||
*/ | |||
public S loadDefaultInstance() { | |||
load(); | |||
if (defaultClass == null) { | |||
return null; | |||
} | |||
return createInstance(defaultClass); | |||
} | |||
/** | |||
* Load instance by specific class type | |||
* | |||
* @param clazz class type | |||
* @return Provider instance | |||
*/ | |||
public S loadInstance(Class<? extends S> clazz) { | |||
AssertUtil.notNull(clazz, "SPI class cannot be null"); | |||
if (clazz.equals(service)) { | |||
fail(clazz.getName() + " is not subtype of " + service.getName()); | |||
} | |||
load(); | |||
if (!classMap.containsValue(clazz)) { | |||
fail(clazz.getName() + " is not Provider class of " + service.getName() + ",check if it is in the SPI configuration file?"); | |||
} | |||
return createInstance(clazz); | |||
} | |||
/** | |||
* Load instance by aliasName of Provider class | |||
* | |||
* @param aliasName aliasName of Provider class | |||
* @return Provider instance | |||
*/ | |||
public S loadInstance(String aliasName) { | |||
AssertUtil.notEmpty(aliasName, "aliasName cannot be empty"); | |||
load(); | |||
Class<? extends S> clazz = classMap.get(aliasName); | |||
if (clazz == null) { | |||
fail("no Provider class's aliasName is " + aliasName); | |||
} | |||
return createInstance(clazz); | |||
} | |||
/** | |||
* Reset and clear all fields of current SpiLoader instance and remove instance in SPI_LOADER_MAP | |||
*/ | |||
synchronized void resetAndClear() { | |||
SPI_LOADER_MAP.remove(service.getName()); | |||
classList.clear(); | |||
sortedClassList.clear(); | |||
classMap.clear(); | |||
singletonMap.clear(); | |||
defaultClass = null; | |||
loaded.set(false); | |||
} | |||
/** | |||
* Load the Provider class from Provider configuration file | |||
*/ | |||
public void load() { | |||
if (!loaded.compareAndSet(false, true)) { | |||
return; | |||
} | |||
String fullFileName = SPI_FILE_PREFIX + service.getName(); | |||
ClassLoader classLoader; | |||
if (SentinelConfig.shouldUseContextClassloader()) { | |||
classLoader = Thread.currentThread().getContextClassLoader(); | |||
} else { | |||
classLoader = service.getClassLoader(); | |||
} | |||
if (classLoader == null) { | |||
classLoader = ClassLoader.getSystemClassLoader(); | |||
} | |||
Enumeration<URL> urls = null; | |||
try { | |||
urls = classLoader.getResources(fullFileName); | |||
} catch (IOException e) { | |||
fail("Error locating SPI configuration file,filename=" + fullFileName + ",classloader=" + classLoader, e); | |||
} | |||
if (urls == null || !urls.hasMoreElements()) { | |||
RecordLog.warn("No SPI configuration file,filename=" + fullFileName + ",classloader=" + classLoader); | |||
return; | |||
} | |||
while (urls.hasMoreElements()) { | |||
URL url = urls.nextElement(); | |||
InputStream in = null; | |||
BufferedReader br = null; | |||
try { | |||
in = url.openStream(); | |||
br = new BufferedReader(new InputStreamReader(in, "utf-8")); | |||
String line; | |||
while ((line = br.readLine()) != null) { | |||
if (StringUtil.isBlank(line)) { | |||
// Skip blank line | |||
continue; | |||
} | |||
line = line.trim(); | |||
int commentIndex = line.indexOf("#"); | |||
if (commentIndex == 0) { | |||
// Skip comment line | |||
continue; | |||
} | |||
if (commentIndex > 0) { | |||
line = line.substring(0, commentIndex); | |||
} | |||
line = line.trim(); | |||
Class<S> clazz = null; | |||
try { | |||
clazz = (Class<S>) Class.forName(line, false, classLoader); | |||
} catch (ClassNotFoundException e) { | |||
fail("class " + line + " not found", e); | |||
} | |||
if (!service.isAssignableFrom(clazz)) { | |||
fail("class " + clazz.getName() + "is not subtype of " + service.getName() + ",SPI configuration file=" + fullFileName); | |||
} | |||
classList.add(clazz); | |||
Spi spi = clazz.getAnnotation(Spi.class); | |||
String aliasName = spi == null || "".equals(spi.value()) ? clazz.getName() : spi.value(); | |||
if (classMap.containsKey(aliasName)) { | |||
Class<? extends S> existClass = classMap.get(aliasName); | |||
fail("Found repeat aliasname for " + clazz.getName() + " and " | |||
+ existClass.getName() + ",SPI configuration file=" + fullFileName); | |||
} | |||
classMap.put(aliasName, clazz); | |||
if (spi != null && spi.isDefault()) { | |||
if (defaultClass != null) { | |||
fail("Found more than one default Provider,SPI configuration file=" + fullFileName); | |||
} | |||
defaultClass = clazz; | |||
} | |||
RecordLog.info("[SpiLoader]Found SPI,Service={},Provider={},aliasname={},isSingleton={},isDefault={},order={}", | |||
service.getName(), line, aliasName | |||
, spi == null ? true : spi.isSingleton() | |||
, spi == null ? false : spi.isDefault() | |||
, spi == null ? 0 : spi.order()); | |||
} | |||
} catch (IOException e) { | |||
fail("error reading SPI configuration file", e); | |||
} finally { | |||
closeResources(in, br); | |||
} | |||
} | |||
sortedClassList.addAll(classList); | |||
Collections.sort(sortedClassList, new Comparator<Class<? extends S>>() { | |||
@Override | |||
public int compare(Class<? extends S> o1, Class<? extends S> o2) { | |||
Spi spi1 = o1.getAnnotation(Spi.class); | |||
int order1 = spi1 == null ? 0 : spi1.order(); | |||
Spi spi2 = o2.getAnnotation(Spi.class); | |||
int order2 = spi2 == null ? 0 : spi2.order(); | |||
return Integer.compare(order1, order2); | |||
} | |||
}); | |||
} | |||
@Override | |||
public String toString() { | |||
return "com.alibaba.csp.sentinel.spi.SpiLoader[" + service.getName() + "]"; | |||
} | |||
/** | |||
* Create Provider instance list | |||
* | |||
* @param clazzList class types of Providers | |||
* @return Provider instance list | |||
*/ | |||
private List<S> createInstanceList(List<Class<? extends S>> clazzList) { | |||
if (clazzList == null || clazzList.size() == 0) { | |||
return Collections.emptyList(); | |||
} | |||
List<S> instances = new ArrayList<>(clazzList.size()); | |||
for (Class<? extends S> clazz : clazzList) { | |||
S instance = createInstance(clazz); | |||
instances.add(instance); | |||
} | |||
return instances; | |||
} | |||
/** | |||
* Create Provider instance | |||
* | |||
* @param clazz class type of Provider | |||
* @return Provider class | |||
*/ | |||
private S createInstance(Class<? extends S> clazz) { | |||
Spi spi = clazz.getAnnotation(Spi.class); | |||
boolean singleton = true; | |||
if (spi != null) { | |||
singleton = spi.isSingleton(); | |||
} | |||
return createInstance(clazz, singleton); | |||
} | |||
/** | |||
* Create Provider instance | |||
* | |||
* @param clazz class type of Provider | |||
* @param singleton if instance is singleton or prototype | |||
* @return Provider instance | |||
*/ | |||
private S createInstance(Class<? extends S> clazz, boolean singleton) { | |||
S instance = null; | |||
try { | |||
if (singleton) { | |||
instance = singletonMap.get(clazz.getName()); | |||
if (instance == null) { | |||
synchronized (this) { | |||
instance = singletonMap.get(clazz.getName()); | |||
if (instance == null) { | |||
instance = service.cast(clazz.newInstance()); | |||
singletonMap.put(clazz.getName(), instance); | |||
} | |||
} | |||
} | |||
} else { | |||
instance = service.cast(clazz.newInstance()); | |||
} | |||
} catch (Throwable e) { | |||
fail(clazz.getName() + " could not be instantiated"); | |||
} | |||
return instance; | |||
} | |||
/** | |||
* Close all resources | |||
* | |||
* @param closeables {@link Closeable} resources | |||
*/ | |||
private void closeResources(Closeable... closeables) { | |||
if (closeables == null || closeables.length == 0) { | |||
return; | |||
} | |||
Exception firstException = null; | |||
for (Closeable closeable : closeables) { | |||
try { | |||
closeable.close(); | |||
} catch (Exception e) { | |||
if (firstException != null) { | |||
firstException = e; | |||
} | |||
} | |||
} | |||
if (firstException != null) { | |||
fail("error closing resources", firstException); | |||
} | |||
} | |||
/** | |||
* Throw {@link SpiLoaderException} with message | |||
* | |||
* @param msg error message | |||
*/ | |||
private void fail(String msg) { | |||
RecordLog.error(msg); | |||
throw new SpiLoaderException("[" + service.getName() + "]" + msg); | |||
} | |||
/** | |||
* Throw {@link SpiLoaderException} with message and Throwable | |||
* | |||
* @param msg error message | |||
*/ | |||
private void fail(String msg, Throwable e) { | |||
RecordLog.error(msg, e); | |||
throw new SpiLoaderException("[" + service.getName() + "]" + msg, e); | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
/* | |||
* Copyright 1999-2018 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 | |||
* | |||
* http://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; | |||
/** | |||
* Error thrown when something goes wrong while loading Provider via {@link SpiLoader}. | |||
* | |||
* @author cdfive | |||
*/ | |||
public class SpiLoaderException extends RuntimeException { | |||
public SpiLoaderException() { | |||
super(); | |||
} | |||
public SpiLoaderException(String message) { | |||
super(message); | |||
} | |||
public SpiLoaderException(String message, Throwable cause) { | |||
super(message, cause); | |||
} | |||
} |
@@ -1,48 +0,0 @@ | |||
/* | |||
* 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; | |||
} |
@@ -1,291 +0,0 @@ | |||
/* | |||
* Copyright 1999-2018 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 | |||
* | |||
* http://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.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.ServiceLoaderUtil; | |||
import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public final class SpiLoader { | |||
private static final Map<String, ServiceLoader> SERVICE_LOADER_MAP = new ConcurrentHashMap<String, ServiceLoader>(); | |||
/** | |||
* Load the first-found specific SPI instance | |||
* | |||
* @param clazz class of the SPI interface | |||
* @param <T> SPI type | |||
* @return the first specific SPI instance if exists, or else return null | |||
* @since 1.7.0 | |||
*/ | |||
public static <T> T loadFirstInstance(Class<T> clazz) { | |||
AssertUtil.notNull(clazz, "SPI class cannot be null"); | |||
try { | |||
String key = clazz.getName(); | |||
// Not thread-safe, as it's expected to be resolved in a thread-safe context. | |||
ServiceLoader<T> serviceLoader = SERVICE_LOADER_MAP.get(key); | |||
if (serviceLoader == null) { | |||
serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz); | |||
SERVICE_LOADER_MAP.put(key, serviceLoader); | |||
} | |||
Iterator<T> iterator = serviceLoader.iterator(); | |||
if (iterator.hasNext()) { | |||
return iterator.next(); | |||
} else { | |||
return null; | |||
} | |||
} catch (Throwable t) { | |||
RecordLog.error("[SpiLoader] ERROR: loadFirstInstance failed", t); | |||
t.printStackTrace(); | |||
return null; | |||
} | |||
} | |||
/** | |||
* Load the first-found specific SPI instance (excluding provided default SPI class). | |||
* If no other SPI implementation found, then create a default SPI instance. | |||
* | |||
* @param clazz class of the SPI interface | |||
* @param defaultClass class of the default SPI implementation (if no other implementation found) | |||
* @param <T> SPI type | |||
* @return the first specific SPI instance if exists, or else the default SPI instance | |||
* @since 1.7.0 | |||
*/ | |||
public static <T> T loadFirstInstanceOrDefault(Class<T> clazz, Class<? extends T> defaultClass) { | |||
AssertUtil.notNull(clazz, "SPI class cannot be null"); | |||
AssertUtil.notNull(defaultClass, "default SPI class cannot be null"); | |||
try { | |||
String key = clazz.getName(); | |||
// Not thread-safe, as it's expected to be resolved in a thread-safe context. | |||
ServiceLoader<T> serviceLoader = SERVICE_LOADER_MAP.get(key); | |||
if (serviceLoader == null) { | |||
serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz); | |||
SERVICE_LOADER_MAP.put(key, serviceLoader); | |||
} | |||
for (T instance : serviceLoader) { | |||
if (instance.getClass() != defaultClass) { | |||
return instance; | |||
} | |||
} | |||
return defaultClass.newInstance(); | |||
} catch (Throwable t) { | |||
RecordLog.error("[SpiLoader] ERROR: loadFirstInstanceOrDefault failed", t); | |||
t.printStackTrace(); | |||
return null; | |||
} | |||
} | |||
/** | |||
* Load the SPI instance with highest priority. | |||
* | |||
* Note: each call return same instances. | |||
* | |||
* @param clazz class of the SPI | |||
* @param <T> SPI type | |||
* @return the SPI instance with highest priority if exists, or else false | |||
* @since 1.6.0 | |||
*/ | |||
public static <T> T loadHighestPriorityInstance(Class<T> clazz) { | |||
try { | |||
String key = clazz.getName(); | |||
// Not thread-safe, as it's expected to be resolved in a thread-safe context. | |||
ServiceLoader<T> serviceLoader = SERVICE_LOADER_MAP.get(key); | |||
if (serviceLoader == null) { | |||
serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz); | |||
SERVICE_LOADER_MAP.put(key, serviceLoader); | |||
} | |||
SpiOrderWrapper<T> w = null; | |||
for (T spi : serviceLoader) { | |||
int order = SpiOrderResolver.resolveOrder(spi); | |||
RecordLog.info("[SpiLoader] Found {} SPI: {} with order {}", clazz.getSimpleName(), | |||
spi.getClass().getCanonicalName(), order); | |||
if (w == null || order < w.order) { | |||
w = new SpiOrderWrapper<>(order, spi); | |||
} | |||
} | |||
return w == null ? null : w.spi; | |||
} catch (Throwable t) { | |||
RecordLog.error("[SpiLoader] ERROR: loadHighestPriorityInstance failed", t); | |||
t.printStackTrace(); | |||
return null; | |||
} | |||
} | |||
/** | |||
* Load and sorted SPI instance list. | |||
* Load the SPI instance list for provided SPI interface. | |||
* | |||
* Note: each call return same instances. | |||
* | |||
* @param clazz class of the SPI | |||
* @param <T> SPI type | |||
* @return sorted SPI instance list | |||
* @since 1.6.0 | |||
*/ | |||
public static <T> List<T> loadInstanceList(Class<T> clazz) { | |||
try { | |||
String key = clazz.getName(); | |||
// Not thread-safe, as it's expected to be resolved in a thread-safe context. | |||
ServiceLoader<T> serviceLoader = SERVICE_LOADER_MAP.get(key); | |||
if (serviceLoader == null) { | |||
serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz); | |||
SERVICE_LOADER_MAP.put(key, serviceLoader); | |||
} | |||
List<T> list = new ArrayList<>(); | |||
for (T spi : serviceLoader) { | |||
RecordLog.info("[SpiLoader] Found {} SPI: {}", clazz.getSimpleName(), | |||
spi.getClass().getCanonicalName()); | |||
list.add(spi); | |||
} | |||
return list; | |||
} catch (Throwable t) { | |||
RecordLog.error("[SpiLoader] ERROR: loadInstanceList failed", t); | |||
t.printStackTrace(); | |||
return new ArrayList<>(); | |||
} | |||
} | |||
/** | |||
* Load the sorted SPI instance list for provided SPI interface. | |||
* | |||
* Note: each call return same instances. | |||
* | |||
* @param clazz class of the SPI | |||
* @param <T> SPI type | |||
* @return sorted SPI instance list | |||
* @since 1.6.0 | |||
*/ | |||
public static <T> List<T> loadInstanceListSorted(Class<T> clazz) { | |||
try { | |||
String key = clazz.getName(); | |||
// Not thread-safe, as it's expected to be resolved in a thread-safe context. | |||
ServiceLoader<T> serviceLoader = SERVICE_LOADER_MAP.get(key); | |||
if (serviceLoader == null) { | |||
serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz); | |||
SERVICE_LOADER_MAP.put(key, serviceLoader); | |||
} | |||
List<SpiOrderWrapper<T>> 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 {} SPI: {} with order {}", clazz.getSimpleName(), | |||
spi.getClass().getCanonicalName(), order); | |||
} | |||
List<T> list = new ArrayList<>(orderWrappers.size()); | |||
for (int i = 0; i < orderWrappers.size(); i++) { | |||
list.add(orderWrappers.get(i).spi); | |||
} | |||
return list; | |||
} catch (Throwable t) { | |||
RecordLog.error("[SpiLoader] ERROR: loadInstanceListSorted failed", t); | |||
t.printStackTrace(); | |||
return new ArrayList<>(); | |||
} | |||
} | |||
/** | |||
* Load the sorted and prototype SPI instance list for provided SPI interface. | |||
* | |||
* Note: each call return different instances, i.e. prototype instance, not singleton instance. | |||
* | |||
* @param clazz class of the SPI | |||
* @param <T> SPI type | |||
* @return sorted and different SPI instance list | |||
* @since 1.7.2 | |||
*/ | |||
public static <T> List<T> loadPrototypeInstanceListSorted(Class<T> clazz) { | |||
try { | |||
// Not use SERVICE_LOADER_MAP, to make sure the instances loaded are different. | |||
ServiceLoader<T> serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz); | |||
List<SpiOrderWrapper<T>> 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.debug("[SpiLoader] Found {} SPI: {} with order {}", clazz.getSimpleName(), | |||
spi.getClass().getCanonicalName(), order); | |||
} | |||
List<T> list = new ArrayList<>(orderWrappers.size()); | |||
for (int i = 0; i < orderWrappers.size(); i++) { | |||
list.add(orderWrappers.get(i).spi); | |||
} | |||
return list; | |||
} catch (Throwable t) { | |||
RecordLog.error("[SpiLoader] ERROR: loadPrototypeInstanceListSorted failed", t); | |||
t.printStackTrace(); | |||
return new ArrayList<>(); | |||
} | |||
} | |||
private static class SpiOrderResolver { | |||
private static <T> void insertSorted(List<SpiOrderWrapper<T>> 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 <T> 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<T> { | |||
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() {} | |||
} |
@@ -0,0 +1,321 @@ | |||
/* | |||
* Copyright 1999-2018 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 | |||
* | |||
* http://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 com.alibaba.csp.sentinel.SphU; | |||
import com.alibaba.csp.sentinel.init.InitFunc; | |||
import com.alibaba.csp.sentinel.metric.extension.MetricCallbackInit; | |||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder; | |||
import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder; | |||
import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot; | |||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot; | |||
import com.alibaba.csp.sentinel.slots.block.flow.FlowSlot; | |||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; | |||
import com.alibaba.csp.sentinel.slots.logger.LogSlot; | |||
import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; | |||
import com.alibaba.csp.sentinel.slots.statistic.StatisticSlot; | |||
import com.alibaba.csp.sentinel.slots.system.SystemSlot; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import static org.junit.Assert.*; | |||
import static org.hamcrest.Matchers.*; | |||
/** | |||
* Test cases for {@link SpiLoader}. | |||
* | |||
* @author cdfive | |||
*/ | |||
public class SpiLoaderTest { | |||
@Before | |||
public void setUp() { | |||
SpiLoader.resetAndClearAll(); | |||
} | |||
@Before | |||
public void tearDown() { | |||
SpiLoader.resetAndClearAll(); | |||
} | |||
@Test | |||
public void testCreateSpiLoader() { | |||
SpiLoader slotLoader1 = SpiLoader.of(ProcessorSlot.class); | |||
assertNotNull(slotLoader1); | |||
SpiLoader slotLoader2 = SpiLoader.of(ProcessorSlot.class); | |||
assertNotNull(slotLoader2); | |||
assertSame(slotLoader1, slotLoader2); | |||
SpiLoader initFuncLoader1 = SpiLoader.of(InitFunc.class); | |||
assertNotNull(initFuncLoader1); | |||
assertNotSame(slotLoader1, initFuncLoader1); | |||
assertNotEquals(slotLoader1, initFuncLoader1); | |||
SpiLoader<InitFunc> initFuncLoader2 = SpiLoader.of(InitFunc.class); | |||
assertNotNull(initFuncLoader2); | |||
assertSame(initFuncLoader1, initFuncLoader2); | |||
} | |||
@Test | |||
public void testCreateSpiLoaderNotInterface() { | |||
try { | |||
SpiLoader.of(SphU.class); | |||
fail(); | |||
} catch (Exception e) { | |||
assertTrue(e instanceof IllegalArgumentException); | |||
assertThat(e.getMessage(), containsString("must be interface or abstract class")); | |||
} | |||
} | |||
@Test | |||
public void testLoadInstanceList() { | |||
SpiLoader spiLoader = SpiLoader.of(ProcessorSlot.class); | |||
List<ProcessorSlot> slots1 = spiLoader.loadInstanceList(); | |||
List<ProcessorSlot> slots2 = spiLoader.loadInstanceList(); | |||
assertNotSame(slots1, slots2); | |||
List<Class<? extends ProcessorSlot>> prototypeSlotClasses = new ArrayList<>(2); | |||
prototypeSlotClasses.add(NodeSelectorSlot.class); | |||
prototypeSlotClasses.add(ClusterBuilderSlot.class); | |||
List<Class<? extends ProcessorSlot>> singletonSlotClasses = new ArrayList<>(6); | |||
singletonSlotClasses.add(LogSlot.class); | |||
singletonSlotClasses.add(StatisticSlot.class); | |||
singletonSlotClasses.add(AuthoritySlot.class); | |||
singletonSlotClasses.add(SystemSlot.class); | |||
singletonSlotClasses.add(FlowSlot.class); | |||
singletonSlotClasses.add(DegradeSlot.class); | |||
for (int i = 0; i < slots1.size(); i++) { | |||
ProcessorSlot slot1 = slots1.get(i); | |||
ProcessorSlot slot2 = slots2.get(i); | |||
assertSame(slot1.getClass(), slot2.getClass()); | |||
boolean found = false; | |||
for (Class<? extends ProcessorSlot> prototypeSlotClass : prototypeSlotClasses) { | |||
if (prototypeSlotClass.equals(slot1.getClass())) { | |||
found = true; | |||
assertTrue(prototypeSlotClass.equals(slot2.getClass())); | |||
// Verify prototype function | |||
assertNotSame(slot1, slot2); | |||
break; | |||
} | |||
} | |||
if (found) { | |||
continue; | |||
} | |||
for (Class<? extends ProcessorSlot> singletonSlotClass : singletonSlotClasses) { | |||
if (singletonSlotClass.equals(slot1.getClass())) { | |||
found = true; | |||
assertTrue(singletonSlotClass.equals(slot2.getClass())); | |||
// Verify single function | |||
assertSame(slot1, slot2); | |||
break; | |||
} | |||
} | |||
if (!found) { | |||
fail("Should found and not go through here"); | |||
} | |||
} | |||
} | |||
@Test | |||
public void testLoadInstanceListSorted() { | |||
List<ProcessorSlot> sortedSlots = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted(); | |||
assertNotNull(sortedSlots); | |||
// Total 8 default slot in sentinel-core | |||
assertEquals(8, sortedSlots.size()); | |||
// Verify the order of slot | |||
int index = 0; | |||
assertTrue(sortedSlots.get(index++) instanceof NodeSelectorSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof ClusterBuilderSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof LogSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof StatisticSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof AuthoritySlot); | |||
assertTrue(sortedSlots.get(index++) instanceof SystemSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof FlowSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof DegradeSlot); | |||
} | |||
@Test | |||
public void testLoadHighestPriorityInstance() { | |||
ProcessorSlot slot = SpiLoader.of(ProcessorSlot.class).loadHighestPriorityInstance(); | |||
assertNotNull(slot); | |||
// NodeSelectorSlot is highest order priority with @Spi(order = -10000) among all slots | |||
assertTrue(slot instanceof NodeSelectorSlot); | |||
} | |||
@Test | |||
public void testLoadLowestPriorityInstance() { | |||
ProcessorSlot slot = SpiLoader.of(ProcessorSlot.class).loadLowestPriorityInstance(); | |||
assertNotNull(slot); | |||
// NodeSelectorSlot is lowest order priority with @Spi(order = -1000) among all slots | |||
assertTrue(slot instanceof DegradeSlot); | |||
} | |||
@Test | |||
public void testLoadFirstInstance() { | |||
ProcessorSlot slot = SpiLoader.of(ProcessorSlot.class).loadFirstInstance(); | |||
assertNotNull(slot); | |||
assertTrue(slot instanceof NodeSelectorSlot); | |||
SlotChainBuilder chainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstance(); | |||
assertNotNull(chainBuilder); | |||
assertTrue(chainBuilder instanceof SlotChainBuilder); | |||
InitFunc initFunc = SpiLoader.of(InitFunc.class).loadFirstInstance(); | |||
assertNotNull(initFunc); | |||
assertTrue(initFunc instanceof MetricCallbackInit); | |||
} | |||
@Test | |||
public void testLoadFirstInstanceOrDefault() { | |||
SlotChainBuilder slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault(); | |||
assertNotNull(slotChainBuilder); | |||
assertTrue(slotChainBuilder instanceof DefaultSlotChainBuilder); | |||
} | |||
@Test | |||
public void testLoadDefaultInstance() { | |||
SlotChainBuilder slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadDefaultInstance(); | |||
assertNotNull(slotChainBuilder); | |||
assertTrue(slotChainBuilder instanceof DefaultSlotChainBuilder); | |||
} | |||
@Test | |||
public void testLoadInstanceByClass() { | |||
ProcessorSlot slot = SpiLoader.of(ProcessorSlot.class).loadInstance(StatisticSlot.class); | |||
assertNotNull(slot); | |||
assertTrue(slot instanceof StatisticSlot); | |||
} | |||
@Test | |||
public void testLoadInstanceByAliasName() { | |||
ProcessorSlot slot = SpiLoader.of(ProcessorSlot.class).loadInstance("com.alibaba.csp.sentinel.slots.statistic.StatisticSlot"); | |||
assertNotNull(slot); | |||
assertTrue(slot instanceof StatisticSlot); | |||
} | |||
@Test | |||
public void testToString() { | |||
SpiLoader spiLoader = SpiLoader.of(ProcessorSlot.class); | |||
assertEquals("com.alibaba.csp.sentinel.spi.SpiLoader[com.alibaba.csp.sentinel.slotchain.ProcessorSlot]" | |||
, spiLoader.toString()); | |||
} | |||
/** | |||
* Following test cases are for some test Interfaces. | |||
*/ | |||
@Test | |||
public void test_TestNoSpiFileInterface() { | |||
SpiLoader<TestNoSpiFileInterface> loader = SpiLoader.of(TestNoSpiFileInterface.class); | |||
List<TestNoSpiFileInterface> providers = loader.loadInstanceList(); | |||
assertTrue(providers.size() == 0); | |||
List<TestNoSpiFileInterface> sortedProviders = loader.loadInstanceListSorted(); | |||
assertTrue(sortedProviders.size() == 0); | |||
TestNoSpiFileInterface firstProvider = loader.loadFirstInstance(); | |||
assertNull(firstProvider); | |||
TestNoSpiFileInterface defaultProvider = loader.loadDefaultInstance(); | |||
assertNull(defaultProvider); | |||
} | |||
@Test | |||
public void test_TestNoProviderInterface() { | |||
List<TestNoProviderInterface> providers = SpiLoader.of(TestNoProviderInterface.class).loadInstanceList(); | |||
assertTrue(providers.size() == 0); | |||
} | |||
@Test | |||
public void test_TestInterface() { | |||
SpiLoader<TestInterface> loader = SpiLoader.of(TestInterface.class); | |||
List<TestInterface> providers = loader.loadInstanceList(); | |||
assertTrue(providers.size() == 4); | |||
assertTrue(providers.get(0) instanceof TestOneProvider); | |||
assertTrue(providers.get(1) instanceof TestTwoProvider); | |||
assertTrue(providers.get(2) instanceof TestThreeProvider); | |||
assertTrue(providers.get(3) instanceof TestFiveProvider); | |||
List<TestInterface> sortedProviders = loader.loadInstanceListSorted(); | |||
assertEquals(sortedProviders.size(), 4); | |||
assertTrue(sortedProviders.get(0) instanceof TestThreeProvider); | |||
assertTrue(sortedProviders.get(1) instanceof TestFiveProvider); | |||
assertTrue(sortedProviders.get(2) instanceof TestTwoProvider); | |||
assertTrue(sortedProviders.get(3) instanceof TestOneProvider); | |||
assertSame(providers.get(0), sortedProviders.get(3)); | |||
assertSame(providers.get(1), sortedProviders.get(2)); | |||
assertNotSame(providers.get(2), sortedProviders.get(0)); | |||
assertSame(providers.get(3), sortedProviders.get(1)); | |||
assertTrue(loader.loadDefaultInstance() instanceof TestFiveProvider); | |||
assertTrue(loader.loadHighestPriorityInstance() instanceof TestThreeProvider); | |||
assertTrue(loader.loadLowestPriorityInstance() instanceof TestOneProvider); | |||
assertTrue(loader.loadInstance("two") instanceof TestTwoProvider); | |||
assertSame(loader.loadInstance("two"), loader.loadInstance("two")); | |||
try { | |||
loader.loadInstance("one"); | |||
fail(); | |||
} catch (Exception e) { | |||
assertTrue(e instanceof SpiLoaderException); | |||
assertThat(e.getMessage(), containsString("no Provider class's aliasName is one")); | |||
} | |||
TestInterface oneProvider1 = loader.loadInstance(TestOneProvider.class); | |||
assertNotNull(oneProvider1); | |||
TestInterface oneProvider2 = loader.loadInstance(TestOneProvider.class); | |||
assertNotNull(oneProvider2); | |||
assertSame(oneProvider1, oneProvider2); | |||
try { | |||
loader.loadInstance(TestInterface.class); | |||
fail(); | |||
} catch (Exception e) { | |||
assertTrue(e instanceof SpiLoaderException); | |||
assertThat(e.getMessage(), containsString("is not subtype of")); | |||
} | |||
try { | |||
loader.loadInstance(TestFourProvider.class); | |||
fail(); | |||
} catch (Exception e) { | |||
assertTrue(e instanceof SpiLoaderException); | |||
assertThat(e.getMessage(), allOf(containsString("is not Provider class of") | |||
, containsString("check if it is in the SPI configuration file?"))); | |||
} | |||
} | |||
} |
@@ -0,0 +1,9 @@ | |||
package com.alibaba.csp.sentinel.spi; | |||
/** | |||
* @author cdfive | |||
*/ | |||
@Spi(value = "five", isDefault = true, order = -270) | |||
public class TestFiveProvider implements TestInterface { | |||
} |
@@ -0,0 +1,11 @@ | |||
package com.alibaba.csp.sentinel.spi; | |||
/** | |||
* This Provider class isn't configured in SPI file. | |||
* | |||
* @author cdfive | |||
*/ | |||
@Spi(value = "four", isSingleton = true, order = -400) | |||
public class TestFourProvider implements TestInterface { | |||
} |
@@ -0,0 +1,8 @@ | |||
package com.alibaba.csp.sentinel.spi; | |||
/** | |||
* @author cdfive | |||
*/ | |||
public interface TestInterface { | |||
} |
@@ -0,0 +1,8 @@ | |||
package com.alibaba.csp.sentinel.spi; | |||
/** | |||
* @author cdfive | |||
*/ | |||
public interface TestNoProviderInterface { | |||
} |
@@ -0,0 +1,8 @@ | |||
package com.alibaba.csp.sentinel.spi; | |||
/** | |||
* @author cdfive | |||
*/ | |||
public interface TestNoSpiFileInterface { | |||
} |
@@ -0,0 +1,8 @@ | |||
package com.alibaba.csp.sentinel.spi; | |||
/** | |||
* @author cdfive | |||
*/ | |||
public class TestOneProvider implements TestInterface { | |||
} |
@@ -0,0 +1,9 @@ | |||
package com.alibaba.csp.sentinel.spi; | |||
/** | |||
* @author cdfive | |||
*/ | |||
@Spi(order = -300, isSingleton = false) | |||
public class TestThreeProvider implements TestInterface { | |||
} |
@@ -0,0 +1,9 @@ | |||
package com.alibaba.csp.sentinel.spi; | |||
/** | |||
* @author cdfive | |||
*/ | |||
@Spi(value = "two", isSingleton = true, order = -200) | |||
public class TestTwoProvider implements TestInterface { | |||
} |
@@ -1,163 +0,0 @@ | |||
/* | |||
* Copyright 1999-2018 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 | |||
* | |||
* http://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.util; | |||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder; | |||
import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder; | |||
import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot; | |||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot; | |||
import com.alibaba.csp.sentinel.slots.block.flow.FlowSlot; | |||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; | |||
import com.alibaba.csp.sentinel.slots.logger.LogSlot; | |||
import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; | |||
import com.alibaba.csp.sentinel.slots.statistic.StatisticSlot; | |||
import com.alibaba.csp.sentinel.slots.system.SystemSlot; | |||
import org.junit.Test; | |||
import java.util.List; | |||
import static org.junit.Assert.*; | |||
/** | |||
* Test cases for {@link SpiLoader}. | |||
* | |||
* @author cdfive | |||
*/ | |||
public class SpiLoaderTest { | |||
@Test | |||
public void testLoadFirstInstance() { | |||
ProcessorSlot processorSlot = SpiLoader.loadFirstInstance(ProcessorSlot.class); | |||
assertNotNull(processorSlot); | |||
ProcessorSlot processorSlot2 = SpiLoader.loadFirstInstance(ProcessorSlot.class); | |||
// As SERVICE_LOADER_MAP in SpiLoader cached the instance, so they're same instances | |||
assertSame(processorSlot, processorSlot2); | |||
SlotChainBuilder slotChainBuilder = SpiLoader.loadFirstInstance(SlotChainBuilder.class); | |||
assertNotNull(slotChainBuilder); | |||
assertTrue(slotChainBuilder instanceof DefaultSlotChainBuilder); | |||
SlotChainBuilder slotChainBuilder2 = SpiLoader.loadFirstInstance(SlotChainBuilder.class); | |||
// As SERVICE_LOADER_MAP in SpiLoader cached the instance, so they're same instances | |||
assertSame(slotChainBuilder, slotChainBuilder2); | |||
} | |||
@Test | |||
public void testLoadHighestPriorityInstance() { | |||
ProcessorSlot processorSlot = SpiLoader.loadHighestPriorityInstance(ProcessorSlot.class); | |||
assertNotNull(processorSlot); | |||
// NodeSelectorSlot is highest order with @SpiOrder(-10000), among all slots | |||
assertTrue(processorSlot instanceof NodeSelectorSlot); | |||
ProcessorSlot processorSlot2 = SpiLoader.loadHighestPriorityInstance(ProcessorSlot.class); | |||
// As SERVICE_LOADER_MAP in SpiLoader cached the instance, so they're same instances | |||
assertSame(processorSlot, processorSlot2); | |||
} | |||
@Test | |||
public void testLoadInstanceList() { | |||
List<ProcessorSlot> slots = SpiLoader.loadInstanceList(ProcessorSlot.class); | |||
assertNotNull(slots); | |||
// Total 8 default slot in sentinel-core | |||
assertEquals(8, slots.size()); | |||
// Get the first slot of slots | |||
ProcessorSlot firstSlot = slots.get(0); | |||
// Call loadInstanceList again | |||
List<ProcessorSlot> slots2 = SpiLoader.loadInstanceList(ProcessorSlot.class); | |||
// Note: the return list are different, and the item instances in list are same | |||
assertNotSame(slots, slots2); | |||
// Get the first slot of slots2 | |||
ProcessorSlot firstSlot2 = slots2.get(0); | |||
// As SERVICE_LOADER_MAP in SpiLoader cached the instance, so they're same instances | |||
assertSame(firstSlot, firstSlot2); | |||
} | |||
@Test | |||
public void testLoadInstanceListSorted() { | |||
List<ProcessorSlot> sortedSlots = SpiLoader.loadInstanceListSorted(ProcessorSlot.class); | |||
assertNotNull(sortedSlots); | |||
// Total 8 default slot in sentinel-core | |||
assertEquals(8, sortedSlots.size()); | |||
// Verify the order of slot | |||
int index = 0; | |||
assertTrue(sortedSlots.get(index++) instanceof NodeSelectorSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof ClusterBuilderSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof LogSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof StatisticSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof AuthoritySlot); | |||
assertTrue(sortedSlots.get(index++) instanceof SystemSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof FlowSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof DegradeSlot); | |||
// Verify each call return different instances | |||
// Note: the return list are different, and the item instances in list are same | |||
List<ProcessorSlot> sortedSlots2 = SpiLoader.loadInstanceListSorted(ProcessorSlot.class); | |||
assertNotSame(sortedSlots, sortedSlots2); | |||
assertEquals(sortedSlots.size(), sortedSlots2.size()); | |||
for (int i = 0; i < sortedSlots.size(); i++) { | |||
ProcessorSlot slot = sortedSlots.get(i); | |||
ProcessorSlot slot2 = sortedSlots2.get(i); | |||
assertEquals(slot.getClass(), slot2.getClass()); | |||
// As SERVICE_LOADER_MAP in SpiLoader cached the instance, so they're same instances | |||
assertSame(slot, slot2); | |||
} | |||
} | |||
@Test | |||
public void testLoadPrototypeInstanceListSorted() { | |||
List<ProcessorSlot> sortedSlots = SpiLoader.loadInstanceListSorted(ProcessorSlot.class); | |||
assertNotNull(sortedSlots); | |||
// Total 8 default slot in sentinel-core | |||
assertEquals(8, sortedSlots.size()); | |||
// Verify the order of slot | |||
int index = 0; | |||
assertTrue(sortedSlots.get(index++) instanceof NodeSelectorSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof ClusterBuilderSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof LogSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof StatisticSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof AuthoritySlot); | |||
assertTrue(sortedSlots.get(index++) instanceof SystemSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof FlowSlot); | |||
assertTrue(sortedSlots.get(index++) instanceof DegradeSlot); | |||
// Verify each call return new instances | |||
List<ProcessorSlot> sortedSlots2 = SpiLoader.loadPrototypeInstanceListSorted(ProcessorSlot.class); | |||
assertNotSame(sortedSlots, sortedSlots2); | |||
assertEquals(sortedSlots.size(), sortedSlots2.size()); | |||
for (int i = 0; i < sortedSlots.size(); i++) { | |||
ProcessorSlot slot = sortedSlots.get(i); | |||
ProcessorSlot slot2 = sortedSlots2.get(i); | |||
assertEquals(slot.getClass(), slot2.getClass()); | |||
// Verify the instances are different | |||
assertNotSame(slot, slot2); | |||
assertNotEquals(slot, slot2); | |||
} | |||
} | |||
} |
@@ -0,0 +1,5 @@ | |||
# One | |||
com.alibaba.csp.sentinel.spi.TestOneProvider | |||
com.alibaba.csp.sentinel.spi.TestTwoProvider # Two | |||
com.alibaba.csp.sentinel.spi.TestThreeProvider | |||
com.alibaba.csp.sentinel.spi.TestFiveProvider |
@@ -19,14 +19,14 @@ import com.alibaba.csp.sentinel.context.Context; | |||
import com.alibaba.csp.sentinel.node.DefaultNode; | |||
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||
import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
/** | |||
* An example slot that records current context and entry resource. | |||
* | |||
* @author Eric Zhao | |||
*/ | |||
@SpiOrder(-3500) | |||
@Spi(order = -3500) | |||
public class DemoSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||
@Override | |||
@@ -21,8 +21,9 @@ import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot; | |||
* @author Eric Zhao | |||
* @since 0.2.0 | |||
* | |||
* @deprecated since 1.7.2, we can use @SpiOrder(-3000) to adjust the order of {@link ParamFlowSlot}, | |||
* @deprecated since 1.7.2, we can use @Spi(order = -3000) to adjust the order of {@link ParamFlowSlot}, | |||
* this class is reserved for compatibility with older versions. | |||
* | |||
* @see ParamFlowSlot | |||
* @see DefaultSlotChainBuilder | |||
*/ | |||
@@ -20,7 +20,7 @@ import com.alibaba.csp.sentinel.node.DefaultNode; | |||
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||
import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
import java.util.List; | |||
@@ -31,7 +31,7 @@ import java.util.List; | |||
* @author Eric Zhao | |||
* @since 0.2.0 | |||
*/ | |||
@SpiOrder(-3000) | |||
@Spi(order = -3000) | |||
public class ParamFlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||
@Override | |||
@@ -17,7 +17,7 @@ package com.alibaba.csp.sentinel.command; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.transport.CommandCenter; | |||
import com.alibaba.csp.sentinel.util.SpiLoader; | |||
import com.alibaba.csp.sentinel.spi.SpiLoader; | |||
/** | |||
* Provider for a universal {@link CommandCenter} instance. | |||
@@ -34,7 +34,7 @@ public final class CommandCenterProvider { | |||
} | |||
private static void resolveInstance() { | |||
CommandCenter resolveCommandCenter = SpiLoader.loadHighestPriorityInstance(CommandCenter.class); | |||
CommandCenter resolveCommandCenter = SpiLoader.of(CommandCenter.class).loadHighestPriorityInstance(); | |||
if (resolveCommandCenter == null) { | |||
RecordLog.warn("[CommandCenterProvider] WARN: No existing CommandCenter found"); | |||
@@ -15,13 +15,10 @@ | |||
*/ | |||
package com.alibaba.csp.sentinel.command; | |||
import java.util.HashMap; | |||
import java.util.Iterator; | |||
import java.util.Map; | |||
import java.util.ServiceLoader; | |||
import java.util.*; | |||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | |||
import com.alibaba.csp.sentinel.spi.ServiceLoaderUtil; | |||
import com.alibaba.csp.sentinel.spi.SpiLoader; | |||
import com.alibaba.csp.sentinel.util.StringUtil; | |||
/** | |||
@@ -31,8 +28,7 @@ import com.alibaba.csp.sentinel.util.StringUtil; | |||
*/ | |||
public class CommandHandlerProvider implements Iterable<CommandHandler> { | |||
private final ServiceLoader<CommandHandler> serviceLoader = ServiceLoaderUtil.getServiceLoader( | |||
CommandHandler.class); | |||
private final SpiLoader<CommandHandler> spiLoader = SpiLoader.of(CommandHandler.class); | |||
/** | |||
* Get all command handlers annotated with {@link CommandMapping} with command name. | |||
@@ -41,7 +37,8 @@ public class CommandHandlerProvider implements Iterable<CommandHandler> { | |||
*/ | |||
public Map<String, CommandHandler> namedHandlers() { | |||
Map<String, CommandHandler> map = new HashMap<String, CommandHandler>(); | |||
for (CommandHandler handler : serviceLoader) { | |||
List<CommandHandler> handlers = spiLoader.loadInstanceList(); | |||
for (CommandHandler handler : handlers) { | |||
String name = parseCommandName(handler); | |||
if (!StringUtil.isEmpty(name)) { | |||
map.put(name, handler); | |||
@@ -61,7 +58,7 @@ public class CommandHandlerProvider implements Iterable<CommandHandler> { | |||
@Override | |||
public Iterator<CommandHandler> iterator() { | |||
return serviceLoader.iterator(); | |||
return spiLoader.loadInstanceList().iterator(); | |||
} | |||
private static final CommandHandlerProvider INSTANCE = new CommandHandlerProvider(); | |||
@@ -17,7 +17,7 @@ package com.alibaba.csp.sentinel.heartbeat; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.transport.HeartbeatSender; | |||
import com.alibaba.csp.sentinel.util.SpiLoader; | |||
import com.alibaba.csp.sentinel.spi.SpiLoader; | |||
/** | |||
* @author Eric Zhao | |||
@@ -32,7 +32,7 @@ public final class HeartbeatSenderProvider { | |||
} | |||
private static void resolveInstance() { | |||
HeartbeatSender resolved = SpiLoader.loadHighestPriorityInstance(HeartbeatSender.class); | |||
HeartbeatSender resolved = SpiLoader.of(HeartbeatSender.class).loadHighestPriorityInstance(); | |||
if (resolved == null) { | |||
RecordLog.warn("[HeartbeatSenderProvider] WARN: No existing HeartbeatSender found"); | |||
} else { | |||
@@ -22,7 +22,7 @@ import java.util.concurrent.Executors; | |||
import com.alibaba.csp.sentinel.command.CommandHandler; | |||
import com.alibaba.csp.sentinel.command.CommandHandlerProvider; | |||
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; | |||
import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
import com.alibaba.csp.sentinel.transport.command.netty.HttpServer; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.transport.CommandCenter; | |||
@@ -32,7 +32,7 @@ import com.alibaba.csp.sentinel.transport.CommandCenter; | |||
* | |||
* @author Eric Zhao | |||
*/ | |||
@SpiOrder(SpiOrder.LOWEST_PRECEDENCE - 100) | |||
@Spi(order = Spi.ORDER_LOWEST - 100) | |||
public class NettyHttpCommandCenter implements CommandCenter { | |||
private final HttpServer server = new HttpServer(); | |||
@@ -18,7 +18,7 @@ package com.alibaba.csp.sentinel.transport.heartbeat; | |||
import com.alibaba.csp.sentinel.Constants; | |||
import com.alibaba.csp.sentinel.config.SentinelConfig; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.spi.SpiOrder; | |||
import com.alibaba.csp.sentinel.spi.Spi; | |||
import com.alibaba.csp.sentinel.transport.HeartbeatSender; | |||
import com.alibaba.csp.sentinel.transport.config.TransportConfig; | |||
import com.alibaba.csp.sentinel.transport.endpoint.Protocol; | |||
@@ -42,7 +42,7 @@ import java.util.List; | |||
* @author Carpenter Lee | |||
* @author Leo Li | |||
*/ | |||
@SpiOrder(SpiOrder.LOWEST_PRECEDENCE - 100) | |||
@Spi(order = Spi.ORDER_LOWEST - 100) | |||
public class HttpHeartbeatSender implements HeartbeatSender { | |||
private final CloseableHttpClient client; | |||