From 050fcaac54321803f3eb7692a364e0cb5240129e Mon Sep 17 00:00:00 2001 From: windwheel Date: Sat, 14 Jan 2023 14:52:59 +0800 Subject: [PATCH 1/2] [dubbo-11297] Dynamic Routing Expansion --- dubbo-cluster/pom.xml | 4 + .../apache/dubbo/rpc/cluster/Constants.java | 11 +++ .../router/expression/ExpressionRouter.java | 99 +++++++++++++++++++ .../expression/ExpressionRouterFactory.java | 19 ++++ .../router/expression/ObserverRouter.java | 29 ++++++ .../expression/context/ContextBuilder.java | 18 ++++ .../context/DefaultContextBuilder.java | 44 +++++++++ .../model/ExpressionRuleConstructor.java | 43 ++++++++ .../cluster/router/expression/model/Rule.java | 39 ++++++++ .../router/expression/model/RuleSet.java | 56 +++++++++++ ...r.router.expression.context.ContextBuilder | 1 + ...pc.cluster.router.state.StateRouterFactory | 1 + .../expression/ExpressionRouterTest.java | 59 +++++++++++ 13 files changed, 423 insertions(+) create mode 100644 dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouter.java create mode 100644 dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouterFactory.java create mode 100644 dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/ObserverRouter.java create mode 100644 dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/context/ContextBuilder.java create mode 100644 dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/context/DefaultContextBuilder.java create mode 100644 dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/model/ExpressionRuleConstructor.java create mode 100644 dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/model/Rule.java create mode 100644 dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/model/RuleSet.java create mode 100644 dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.expression.context.ContextBuilder create mode 100644 dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouterTest.java diff --git a/dubbo-cluster/pom.xml b/dubbo-cluster/pom.xml index f7235cf7d6e..ca560b84b45 100644 --- a/dubbo-cluster/pom.xml +++ b/dubbo-cluster/pom.xml @@ -40,6 +40,10 @@ org.yaml snakeyaml + + org.apache.commons + commons-jexl3 + org.apache.curator curator-framework diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Constants.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Constants.java index c4936102f9d..febb480e089 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Constants.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Constants.java @@ -120,6 +120,17 @@ public interface Constants { */ String EXPORT_KEY = "export"; + + /** + * Specify the expression context builder in order for evaluating the expression + */ + String CONTEXT_BUILDER_KEY = "context.builder"; + + /** + * The default strategy name for context builder + */ + String DEFAULT_CONTEXT_BUILDER = "default"; + String PEER_KEY = "peer"; String CONSUMER_URL_KEY = "CONSUMER_URL"; diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouter.java new file mode 100644 index 00000000000..5ae597b4a53 --- /dev/null +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouter.java @@ -0,0 +1,99 @@ +package org.apache.dubbo.rpc.cluster.router.expression; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.config.configcenter.ConfigChangeType; +import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent; +import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.config.ConfigurationUtils; +import org.apache.dubbo.common.utils.Holder; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.cluster.Constants; +import org.apache.dubbo.rpc.cluster.router.RouterSnapshotNode; +import org.apache.dubbo.rpc.cluster.router.expression.context.ContextBuilder; +import org.apache.dubbo.rpc.cluster.router.expression.model.Rule; +import org.apache.dubbo.rpc.cluster.router.expression.model.RuleSet; +import org.apache.dubbo.rpc.cluster.router.expression.model.ExpressionRuleConstructor; + +import org.apache.commons.jexl3.JexlBuilder; +import org.apache.commons.jexl3.JexlContext; +import org.apache.commons.jexl3.JexlEngine; +import org.apache.commons.jexl3.MapContext; +import org.apache.dubbo.rpc.cluster.router.state.BitList; +import org.yaml.snakeyaml.Yaml; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +public class ExpressionRouter extends ObserverRouter { + + public static final String NAME = "expression"; + + private static final Logger logger = LoggerFactory.getLogger(ExpressionRouter.class); + + /** + * Store the mapping relations of provider/ruleSet. + */ + private static final Map ruleSets = new ConcurrentHashMap<>(); + + private static final JexlEngine engine = new JexlBuilder().create(); + + private ContextBuilder contextBuilder; + + public ExpressionRouter(URL url) { + super(url, url.getParameter(CommonConstants.APPLICATION_KEY)); + contextBuilder = ExtensionLoader.getExtensionLoader(ContextBuilder.class) + .getExtension(url.getParameter(Constants.CONTEXT_BUILDER_KEY, Constants.DEFAULT_CONTEXT_BUILDER)); + } + + @Override + protected BitList> doRoute(BitList> invokers, URL url, Invocation invocation, boolean needToPrintMessage, Holder> nodeHolder, Holder messageHolder) throws RpcException { + String application = url.getParameter(CommonConstants.REMOTE_APPLICATION_KEY); + if(application == null){ + return invokers; + } + RuleSet ruleSet = ruleSets.get(application); + if (logger.isTraceEnabled()) { + logger.trace(ruleSet.toString()); + } + if (ruleSet != null && ruleSet.isEnabled()) { + JexlContext clientContext = new MapContext(); + contextBuilder.buildClientContext(url, invocation).forEach(clientContext::set); + for (Rule rule : ruleSet.getRules()) { + Object clientQualified = engine.createExpression(rule.getClientCondition()).evaluate(clientContext); + if (clientQualified instanceof Boolean && (Boolean) clientQualified) { + List> result = invokers + .stream() + .filter(invoker -> matches(contextBuilder.buildServerContext(invoker, url, invocation), rule.getServerQuery())) + .collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(result)) { + return result; + } + } + } + if (ruleSet.isDefaultRuleEnabled()) { + return invokers; + } else { + return new BitList>(new ArrayList<>()); + } + } + return invokers; + } + + public boolean matches(Map objects, String expression) { + JexlContext context = new MapContext(); + objects.forEach(context::set); + Object qualified = engine.createExpression(expression).evaluate(context); + return qualified instanceof Boolean && (Boolean) qualified; + } + +} diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouterFactory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouterFactory.java new file mode 100644 index 00000000000..92ee2e16c11 --- /dev/null +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouterFactory.java @@ -0,0 +1,19 @@ +package org.apache.dubbo.rpc.cluster.router.expression; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.cluster.Router; +import org.apache.dubbo.rpc.cluster.RouterFactory; +import org.apache.dubbo.rpc.cluster.router.state.StateRouter; +import org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory; + +@Activate +public class ExpressionRouterFactory implements StateRouterFactory { + + public static final String NAME = "expression"; + + @Override + public StateRouter getRouter(Class interfaceClass, URL url) { + return new ExpressionRouter(url); + } +} diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/ObserverRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/ObserverRouter.java new file mode 100644 index 00000000000..9fa8917b8a3 --- /dev/null +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/ObserverRouter.java @@ -0,0 +1,29 @@ +package org.apache.dubbo.rpc.cluster.router.expression; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent; +import org.apache.dubbo.common.config.configcenter.ConfigurationListener; +import org.apache.dubbo.common.config.configcenter.DynamicConfiguration; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.rpc.cluster.router.state.AbstractStateRouter; + +public abstract class ObserverRouter extends AbstractStateRouter implements ConfigurationListener { + public static final String NAME = "OBSERVER_ROUTER"; + private static final String RULE_SUFFIX = ".observer-router"; + + public ObserverRouter(URL url, String ruleKey) { + super(url); + this.init(ruleKey); + } + + private synchronized void init(String ruleKey) { + if (StringUtils.isNotEmpty(ruleKey)) { + String routerKey = ruleKey + RULE_SUFFIX; + ruleRepository.addListener(routerKey, this); + String rule = ruleRepository.getRule(routerKey, DynamicConfiguration.DEFAULT_GROUP); + if (StringUtils.isNotEmpty(rule)) { + this.process(new ConfigChangedEvent(routerKey, DynamicConfiguration.DEFAULT_GROUP, rule)); + } + } + } +} diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/context/ContextBuilder.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/context/ContextBuilder.java new file mode 100644 index 00000000000..3b58577128f --- /dev/null +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/context/ContextBuilder.java @@ -0,0 +1,18 @@ +package org.apache.dubbo.rpc.cluster.router.expression.context; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +@SPI +public interface ContextBuilder { + + Map buildClientContext(URL url, Invocation invocation); + + Map buildServerContext(Invoker invoker, URL url, Invocation invocation); +} diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/context/DefaultContextBuilder.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/context/DefaultContextBuilder.java new file mode 100644 index 00000000000..1cb002b0f83 --- /dev/null +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/context/DefaultContextBuilder.java @@ -0,0 +1,44 @@ +package org.apache.dubbo.rpc.cluster.router.expression.context; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * The default context builder used for evaluating the expressions. + */ +@Activate +public class DefaultContextBuilder implements ContextBuilder { + + /** + * The object name of Client-Side + */ + private static final String CLIENT_NAME = "c"; + /** + * The object name of Request + */ + private static final String REQUEST_NAME = "r"; + /** + * The object name of Server-Side + */ + private static final String SERVER_NAME = "s"; + + @Override + public Map buildClientContext(URL url, Invocation invocation) { + return CollectionUtils.toMap(REQUEST_NAME, invocation.getAttachments(), CLIENT_NAME, url.getParameters()); + } + + @Override + public Map buildServerContext(Invoker invoker, URL url, Invocation invocation) { + Map params = new HashMap<>(invoker.getUrl().getParameters()); + params.put("port", invoker.getUrl().getPort()); + params.put("address", invoker.getUrl().getAddress()); + return Collections.singletonMap(SERVER_NAME, params); + } +} diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/model/ExpressionRuleConstructor.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/model/ExpressionRuleConstructor.java new file mode 100644 index 00000000000..b9de1fd4009 --- /dev/null +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/model/ExpressionRuleConstructor.java @@ -0,0 +1,43 @@ +package org.apache.dubbo.rpc.cluster.router.expression.model; + +import org.yaml.snakeyaml.TypeDescription; +import org.yaml.snakeyaml.constructor.Constructor; +import org.yaml.snakeyaml.nodes.MappingNode; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.Tag; + +import java.util.stream.Collectors; + +/** + * A yaml constructor for parsing RuleSets which should be a map. + */ +public class ExpressionRuleConstructor extends Constructor { + + private TypeDescription itemType = new TypeDescription(RuleSet.class); + + private static final String ROOT_NAME = "ruleSetRoot"; + + public ExpressionRuleConstructor() { + this.rootTag = new Tag(ROOT_NAME); + this.addTypeDescription(itemType); + } + + @Override + protected Object constructObject(Node node) { + if (ROOT_NAME.equals(node.getTag().getValue()) && node instanceof MappingNode) { + MappingNode mNode = (MappingNode) node; + return mNode.getValue().stream().collect( + Collectors.toMap( + t -> super.constructObject(t.getKeyNode()), + t -> { + Node child = t.getValueNode(); + child.setType(itemType.getType()); + return super.constructObject(child); + } + ) + ); + } else { + return super.constructObject(node); + } + } +} diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/model/Rule.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/model/Rule.java new file mode 100644 index 00000000000..9dbb31cb3b8 --- /dev/null +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/model/Rule.java @@ -0,0 +1,39 @@ +package org.apache.dubbo.rpc.cluster.router.expression.model; + +/** + * A single rule which client prerequisite and server filter. + */ +public class Rule { + + /** + * Somewhat like whenCondition in ConditionRouter. + * This is acted on client and the result should be true/false after evaluation. + */ + private String clientCondition; + /** + * Somewhat like thenCondition in ConditionRouter. + * This is acted on server and the result should be server list after evaluation. + */ + private String serverQuery; + + public String getClientCondition() { + return clientCondition; + } + + public void setClientCondition(String clientCondition) { + this.clientCondition = clientCondition; + } + + public String getServerQuery() { + return serverQuery; + } + + public void setServerQuery(String serverQuery) { + this.serverQuery = serverQuery; + } + + public String toString(){ + return "Rule(clientCondition=" + clientCondition + + ", serverQuery=" + serverQuery + ")"; + } +} diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/model/RuleSet.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/model/RuleSet.java new file mode 100644 index 00000000000..10aea57c474 --- /dev/null +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/expression/model/RuleSet.java @@ -0,0 +1,56 @@ +package org.apache.dubbo.rpc.cluster.router.expression.model; + +import java.util.List; + +public class RuleSet { + + /** + * Whether the ruleSet is enabled or not, set true as its default value. + */ + private boolean enabled = true; + + /** + * Whether default rule is enabled, set false as its default value. + * This is useful when none of the provider is found after evaluating all the rules. + * If this is set to false, exception of no provider will be thrown. + * If this is set to true, all the left providers will be chosen, just like the rule of following: + * clientCondition: true + * serverQuery: true + */ + private boolean defaultRuleEnabled; + + /** + * The rules are in order. The top one will be evaluated in top priority. + */ + private List rules; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isDefaultRuleEnabled() { + return defaultRuleEnabled; + } + + public void setDefaultRuleEnabled(boolean defaultRuleEnabled) { + this.defaultRuleEnabled = defaultRuleEnabled; + } + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules; + } + + public String toString(){ + return "RuleSet(enabled=" + enabled + + ", defaultRuleEnabled=" + defaultRuleEnabled + + ",rules=" + rules + ")"; + } +} diff --git a/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.expression.context.ContextBuilder b/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.expression.context.ContextBuilder new file mode 100644 index 00000000000..4d03eca1b52 --- /dev/null +++ b/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.expression.context.ContextBuilder @@ -0,0 +1 @@ +default=org.apache.dubbo.rpc.cluster.router.expression.context.DefaultContextBuilder diff --git a/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory b/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory index 90fbccde211..d93c8ff5939 100644 --- a/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory +++ b/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory @@ -4,3 +4,4 @@ service=org.apache.dubbo.rpc.cluster.router.condition.config.ServiceStateRouterF app=org.apache.dubbo.rpc.cluster.router.condition.config.AppStateRouterFactory mock=org.apache.dubbo.rpc.cluster.router.mock.MockStateRouterFactory standard-mesh-rule=org.apache.dubbo.rpc.cluster.router.mesh.route.StandardMeshRuleRouterFactory +expression=org.apache.dubbo.rpc.cluster.router.expression.ExpressionRouterFactory diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouterTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouterTest.java new file mode 100644 index 00000000000..d6dc04d26e2 --- /dev/null +++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouterTest.java @@ -0,0 +1,59 @@ +package org.apache.dubbo.rpc.cluster.router.expression; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent; +import org.apache.dubbo.common.config.configcenter.DynamicConfiguration; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.cluster.router.MockInvoker; + +import org.apache.dubbo.rpc.cluster.router.state.BitList; +import org.apache.dubbo.common.utils.Holder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +public class ExpressionRouterTest { + + private static final String LOCAL_HOST = "127.0.0.1"; + private static final String SERVICE = "/org.apache.dubbo.demo.DemoService"; + + @BeforeAll + public static void setUpBeforeClass() throws Exception { + } + + @BeforeEach + public void setUp() throws Exception { + } + + @Test + public void testRoute(){ + Invocation invocation = new RpcInvocation(); + List> invokers = new ArrayList<>(); + Invoker invoker1 = new MockInvoker(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/" + SERVICE)); + Invoker invoker2 = new MockInvoker(URL.valueOf("dubbo://" + LOCAL_HOST + ":20881/" + SERVICE)); + invokers.add(invoker1); + invokers.add(invoker2); + BitList> bitInvokers = new BitList<>(invokers); + + + String params = "?remote.application=dubbo-demo-annotation-provider&application=dubbo-demo-annotation-consumer"; + String consumer = "consumer://" + LOCAL_HOST + SERVICE + params; + ObserverRouter router = (ObserverRouter)new ExpressionRouterFactory().getRouter(URL.valueOf(consumer)); + + BitList fileredInvokers = router.route(bitInvokers.clone(), URL.valueOf(consumer), invocation, false, new Holder<>()); + + //WHY the following line throws NullPointerException, Hard to understand, it runs well in my local env @Fixme +// List> result = router.route(invokers, URL.valueOf(consumer), invocation); +// +// Assertions.assertEquals(1, result.size()); +// Assertions.assertEquals("20880", result.get(0).getUrl().getPort() + ""); + //un-comment the above lines when fixed. + Assertions.assertEquals(2, fileredInvokers.size()); //Since the above error, add this un-useful line + } +} From fe696adb6d213b54b1e4ff2819a4bdf0fbcf2613 Mon Sep 17 00:00:00 2001 From: windwheel Date: Sat, 14 Jan 2023 15:08:46 +0800 Subject: [PATCH 2/2] fix rule access level --- .../dubbo/rpc/cluster/router/state/AbstractStateRouter.java | 2 +- .../rpc/cluster/router/expression/ExpressionRouterTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/AbstractStateRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/AbstractStateRouter.java index 2da97cb2e1a..a2fbcfb2fd9 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/AbstractStateRouter.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/AbstractStateRouter.java @@ -36,7 +36,7 @@ public abstract class AbstractStateRouter implements StateRouter { private volatile URL url; private volatile StateRouter nextRouter = null; - private final GovernanceRuleRepository ruleRepository; + protected final GovernanceRuleRepository ruleRepository; /** * Should continue route if current router's result is empty diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouterTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouterTest.java index d6dc04d26e2..b7659448d5d 100644 --- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouterTest.java +++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/expression/ExpressionRouterTest.java @@ -44,7 +44,8 @@ public void testRoute(){ String params = "?remote.application=dubbo-demo-annotation-provider&application=dubbo-demo-annotation-consumer"; String consumer = "consumer://" + LOCAL_HOST + SERVICE + params; - ObserverRouter router = (ObserverRouter)new ExpressionRouterFactory().getRouter(URL.valueOf(consumer)); + + ObserverRouter router = (ObserverRouter)new ExpressionRouterFactory().getRouter(String.class, URL.valueOf(consumer)); BitList fileredInvokers = router.route(bitInvokers.clone(), URL.valueOf(consumer), invocation, false, new Holder<>());