You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by li...@apache.org on 2023/04/18 09:21:07 UTC

[dubbo-go] branch main updated: Ftr: Implement condition routing basic functions and complete related tests (#2299)

This is an automated email from the ASF dual-hosted git repository.

liujun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 4b13d2b86 Ftr: Implement condition routing basic functions and complete related tests (#2299)
4b13d2b86 is described below

commit 4b13d2b8637a4356b33f992c8208ec7475ef80ec
Author: finalt <fi...@163.com>
AuthorDate: Tue Apr 18 17:20:59 2023 +0800

    Ftr: Implement condition routing basic functions and complete related tests (#2299)
---
 cluster/router/condition/matcher/argument.go       |  77 ++++
 cluster/router/condition/matcher/base.go           | 159 +++++++
 cluster/router/condition/matcher/extension.go      |  40 ++
 cluster/router/condition/matcher/factory.go        |  78 ++++
 cluster/router/condition/matcher/matcher.go        |  52 +++
 cluster/router/condition/matcher/param.go          |  37 ++
 .../condition/matcher/pattern_value/extension.go   |  40 ++
 .../condition/matcher/pattern_value/pattern.go     |  33 ++
 .../condition/matcher/pattern_value/wildcard.go    |  82 ++++
 cluster/router/condition/route.go                  | 329 ++++++++++++++
 cluster/router/condition/router_test.go            | 479 +++++++++++++++++++++
 common/constant/key.go                             |  23 +-
 12 files changed, 1421 insertions(+), 8 deletions(-)

diff --git a/cluster/router/condition/matcher/argument.go b/cluster/router/condition/matcher/argument.go
new file mode 100644
index 000000000..f0115f4a7
--- /dev/null
+++ b/cluster/router/condition/matcher/argument.go
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 matcher
+
+import (
+	"fmt"
+	"regexp"
+	"strconv"
+	"strings"
+)
+
+import (
+	"github.com/dubbogo/gost/log/logger"
+)
+
+import (
+	"dubbo.apache.org/dubbo-go/v3/common"
+	"dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+var (
+	argumentsPattern      = regexp.MustCompile("arguments\\[([0-9]+)\\]")
+	notFoundArgumentValue = "dubbo internal not found argument condition value"
+)
+
+// ArgumentConditionMatcher analysis the arguments in the rule.
+// Examples would be like this:
+// "arguments[0]=1", whenCondition is that the first argument is equal to '1'.
+// "arguments[1]=a", whenCondition is that the second argument is equal to 'a'.
+type ArgumentConditionMatcher struct {
+	BaseConditionMatcher
+}
+
+func NewArgumentConditionMatcher(key string) *ArgumentConditionMatcher {
+	return &ArgumentConditionMatcher{
+		*NewBaseConditionMatcher(key),
+	}
+}
+
+func (a *ArgumentConditionMatcher) GetValue(sample map[string]string, url *common.URL, invocation protocol.Invocation) string {
+	// split the rule
+	expressArray := strings.Split(a.key, "\\.")
+	argumentExpress := expressArray[0]
+	matcher := argumentsPattern.FindStringSubmatch(argumentExpress)
+	if len(matcher) == 0 {
+		logger.Warn(notFoundArgumentValue)
+		return ""
+	}
+
+	// extract the argument index
+	index, err := strconv.Atoi(matcher[1])
+	if err != nil {
+		logger.Warn(notFoundArgumentValue)
+		return ""
+	}
+
+	if index < 0 || index > len(invocation.Arguments()) {
+		logger.Warn(notFoundArgumentValue)
+		return ""
+	}
+	return fmt.Sprint(invocation.Arguments()[index])
+}
diff --git a/cluster/router/condition/matcher/base.go b/cluster/router/condition/matcher/base.go
new file mode 100644
index 000000000..f3858070b
--- /dev/null
+++ b/cluster/router/condition/matcher/base.go
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 matcher
+
+import (
+	"sort"
+	"sync"
+)
+
+import (
+	"github.com/dubbogo/gost/log/logger"
+)
+
+import (
+	"dubbo.apache.org/dubbo-go/v3/cluster/router/condition/matcher/pattern_value"
+	"dubbo.apache.org/dubbo-go/v3/common"
+	"dubbo.apache.org/dubbo-go/v3/common/constant"
+	"dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+var (
+	valueMatchers = make([]pattern_value.ValuePattern, 0, 8)
+
+	once sync.Once
+)
+
+// BaseConditionMatcher records the match and mismatch patterns of this matcher while at the same time
+// provides the common match logics.
+type BaseConditionMatcher struct {
+	key        string
+	matches    map[string]struct{}
+	misMatches map[string]struct{}
+}
+
+func NewBaseConditionMatcher(key string) *BaseConditionMatcher {
+	return &BaseConditionMatcher{
+		key:        key,
+		matches:    map[string]struct{}{},
+		misMatches: map[string]struct{}{},
+	}
+}
+
+// GetValue returns a value from different places of the request context.
+func (b *BaseConditionMatcher) GetValue(sample map[string]string, url *common.URL, invocation protocol.Invocation) string {
+	return ""
+}
+
+// IsMatch indicates whether this matcher matches the patterns with request context.
+func (b *BaseConditionMatcher) IsMatch(value string, param *common.URL, invocation protocol.Invocation, isWhenCondition bool) bool {
+	if value == "" {
+		// if key does not present in whichever of url, invocation or attachment based on the matcher type, then return false.
+		return false
+	}
+
+	if len(b.matches) != 0 && len(b.misMatches) == 0 {
+		return b.patternMatches(value, param, invocation, isWhenCondition)
+	}
+
+	if len(b.misMatches) != 0 && len(b.matches) == 0 {
+		return b.patternMisMatches(value, param, invocation, isWhenCondition)
+	}
+
+	if len(b.matches) != 0 && len(b.misMatches) != 0 {
+		// when both mismatches and matches contain the same value, then using mismatches first
+		return b.patternMatches(value, param, invocation, isWhenCondition) && b.patternMisMatches(value, param, invocation, isWhenCondition)
+	}
+	return false
+}
+
+// GetMatches returns matches.
+func (b *BaseConditionMatcher) GetMatches() map[string]struct{} {
+	return b.matches
+}
+
+// GetMismatches returns misMatches.
+func (b *BaseConditionMatcher) GetMismatches() map[string]struct{} {
+	return b.misMatches
+}
+
+func (b *BaseConditionMatcher) patternMatches(value string, param *common.URL, invocation protocol.Invocation, isWhenCondition bool) bool {
+	for match := range b.matches {
+		if doPatternMatch(match, value, param, invocation, isWhenCondition) {
+			return true
+		}
+	}
+	return false
+}
+
+func (b *BaseConditionMatcher) patternMisMatches(value string, param *common.URL, invocation protocol.Invocation, isWhenCondition bool) bool {
+	for mismatch := range b.misMatches {
+		if doPatternMatch(mismatch, value, param, invocation, isWhenCondition) {
+			return false
+		}
+	}
+	return true
+}
+
+func doPatternMatch(pattern string, value string, url *common.URL, invocation protocol.Invocation, isWhenCondition bool) bool {
+	once.Do(initValueMatchers)
+	for _, valueMatcher := range valueMatchers {
+		if valueMatcher.ShouldMatch(pattern) {
+			return valueMatcher.Match(pattern, value, url, invocation, isWhenCondition)
+		}
+	}
+	// If no value matcher is available, will force to use wildcard value matcher
+	logger.Error("Executing condition rule value match expression error, will force to use wildcard value matcher")
+
+	valuePattern := pattern_value.GetValuePattern("wildcard")
+	return valuePattern.Match(pattern, value, url, invocation, isWhenCondition)
+}
+
+// GetSampleValueFromURL returns the value of the conditionKey in the URL
+func GetSampleValueFromURL(conditionKey string, sample map[string]string, param *common.URL, invocation protocol.Invocation) string {
+	var sampleValue string
+	// get real invoked method name from invocation
+	if invocation != nil && (constant.MethodKey == conditionKey || constant.MethodsKey == conditionKey) {
+		sampleValue = invocation.MethodName()
+	} else {
+		sampleValue = sample[conditionKey]
+	}
+	return sampleValue
+}
+
+func Match(condition Matcher, sample map[string]string, param *common.URL, invocation protocol.Invocation, isWhenCondition bool) bool {
+	return condition.IsMatch(condition.GetValue(sample, param, invocation), param, invocation, isWhenCondition)
+}
+
+func initValueMatchers() {
+	valuePatterns := pattern_value.GetValuePatterns()
+	for _, valuePattern := range valuePatterns {
+		valueMatchers = append(valueMatchers, valuePattern())
+	}
+	sortValuePattern(valueMatchers)
+}
+
+func sortValuePattern(valuePatterns []pattern_value.ValuePattern) {
+	sort.Stable(byPriority(valuePatterns))
+}
+
+type byPriority []pattern_value.ValuePattern
+
+func (a byPriority) Len() int           { return len(a) }
+func (a byPriority) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a byPriority) Less(i, j int) bool { return a[i].Priority() < a[j].Priority() }
diff --git a/cluster/router/condition/matcher/extension.go b/cluster/router/condition/matcher/extension.go
new file mode 100644
index 000000000..7bccd30a3
--- /dev/null
+++ b/cluster/router/condition/matcher/extension.go
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 matcher
+
+var (
+	matchers = make(map[string]func() ConditionMatcherFactory)
+)
+
+// SetMatcherFactory sets create matcherFactory function with @name
+func SetMatcherFactory(name string, fun func() ConditionMatcherFactory) {
+	matchers[name] = fun
+}
+
+// GetMatcherFactory gets create matcherFactory function by name
+func GetMatcherFactory(name string) ConditionMatcherFactory {
+	if matchers[name] == nil {
+		panic("matcher_factory for " + name + " is not existing, make sure you have imported the package.")
+	}
+	return matchers[name]()
+}
+
+// GetMatcherFactories gets all create matcherFactory function
+func GetMatcherFactories() map[string]func() ConditionMatcherFactory {
+	return matchers
+}
diff --git a/cluster/router/condition/matcher/factory.go b/cluster/router/condition/matcher/factory.go
new file mode 100644
index 000000000..f5cc89476
--- /dev/null
+++ b/cluster/router/condition/matcher/factory.go
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 matcher
+
+import (
+	"math"
+	"strings"
+)
+
+import (
+	"dubbo.apache.org/dubbo-go/v3/common/constant"
+)
+
+func init() {
+	SetMatcherFactory("argument", NewArgumentMatcherFactory)
+	SetMatcherFactory("param", NewParamMatcherFactory)
+}
+
+// ArgumentMatcherFactory matcher factory
+type ArgumentMatcherFactory struct {
+}
+
+// NewArgumentMatcherFactory constructs a new argument.ArgumentMatcherFactory
+func NewArgumentMatcherFactory() ConditionMatcherFactory {
+	return &ArgumentMatcherFactory{}
+}
+
+func (a *ArgumentMatcherFactory) ShouldMatch(key string) bool {
+	return strings.HasPrefix(key, constant.Arguments)
+}
+
+// NewMatcher constructs a new matcher
+func (a *ArgumentMatcherFactory) NewMatcher(key string) Matcher {
+	return NewArgumentConditionMatcher(key)
+}
+
+func (a *ArgumentMatcherFactory) Priority() int64 {
+	return 300
+}
+
+// ParamMatcherFactory matcher factory
+type ParamMatcherFactory struct {
+}
+
+// NewParamMatcherFactory constructs a new paramMatcherFactory
+func NewParamMatcherFactory() ConditionMatcherFactory {
+	return &ParamMatcherFactory{}
+}
+
+func (p *ParamMatcherFactory) ShouldMatch(key string) bool {
+	return true
+}
+
+// NewMatcher constructs a new matcher
+func (p *ParamMatcherFactory) NewMatcher(key string) Matcher {
+	return NewParamConditionMatcher(key)
+}
+
+// Priority make sure this is the last matcher being executed.
+// This instance will be loaded separately to ensure it always gets executed as the last matcher.
+func (p *ParamMatcherFactory) Priority() int64 {
+	return math.MaxInt64
+}
diff --git a/cluster/router/condition/matcher/matcher.go b/cluster/router/condition/matcher/matcher.go
new file mode 100644
index 000000000..aeddb4547
--- /dev/null
+++ b/cluster/router/condition/matcher/matcher.go
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 matcher
+
+import (
+	"dubbo.apache.org/dubbo-go/v3/common"
+	"dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+type ConditionMatcherFactory interface {
+	// ShouldMatch indicates whether the key is of the form of the current matcher type which this factory instance represents.
+	ShouldMatch(key string) bool
+	// NewMatcher returns a matcher instance for the key.
+	NewMatcher(key string) Matcher
+	// Priority returns Priority in ConditionMatcherFactory
+	// 0 to ^int(0) is better, smaller value by better priority.
+	Priority() int64
+}
+
+// Matcher represents a specific match condition of a condition rule.
+// The following condition rule 'foo=bar&arguments[0]=hello* => region=hangzhou' consists of three ConditionMatchers:
+// 1. ParamConditionMatcher represented by 'foo=bar'
+// 2. ArgumentConditionMatcher represented by 'arguments[0]=hello*'
+// 3. ParamConditionMatcher represented by 'region=hangzhou'
+type Matcher interface {
+	// IsMatch indicates whether this matcher matches the patterns with request context.
+	IsMatch(value string, param *common.URL, invocation protocol.Invocation, isWhenCondition bool) bool
+	// GetMatches returns matches.
+	// match patterns extracted from when condition.
+	GetMatches() map[string]struct{}
+	// GetMismatches returns misMatches.
+	// mismatch patterns extracted from then condition.
+	GetMismatches() map[string]struct{}
+	// GetValue returns a value from different places of the request context, for example, url, attachment and invocation.
+	// This makes condition rule possible to check values in any place of a request.
+	GetValue(sample map[string]string, url *common.URL, invocation protocol.Invocation) string
+}
diff --git a/cluster/router/condition/matcher/param.go b/cluster/router/condition/matcher/param.go
new file mode 100644
index 000000000..bc4be3075
--- /dev/null
+++ b/cluster/router/condition/matcher/param.go
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 matcher
+
+import (
+	"dubbo.apache.org/dubbo-go/v3/common"
+	"dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+type ParamConditionMatcher struct {
+	BaseConditionMatcher
+}
+
+func NewParamConditionMatcher(key string) *ParamConditionMatcher {
+	return &ParamConditionMatcher{
+		*NewBaseConditionMatcher(key),
+	}
+}
+
+func (p *ParamConditionMatcher) GetValue(sample map[string]string, url *common.URL, invocation protocol.Invocation) string {
+	return GetSampleValueFromURL(p.key, sample, url, invocation)
+}
diff --git a/cluster/router/condition/matcher/pattern_value/extension.go b/cluster/router/condition/matcher/pattern_value/extension.go
new file mode 100644
index 000000000..a9116d2a6
--- /dev/null
+++ b/cluster/router/condition/matcher/pattern_value/extension.go
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 pattern_value
+
+var (
+	valuePatterns = make(map[string]func() ValuePattern)
+)
+
+// SetValuePattern sets create valuePattern function with @name
+func SetValuePattern(name string, fun func() ValuePattern) {
+	valuePatterns[name] = fun
+}
+
+// GetValuePattern gets create valuePattern function by name
+func GetValuePattern(name string) ValuePattern {
+	if valuePatterns[name] == nil {
+		panic("value_pattern for " + name + " is not existing, make sure you have imported the package.")
+	}
+	return valuePatterns[name]()
+}
+
+// GetValuePatterns gets all create valuePattern function
+func GetValuePatterns() map[string]func() ValuePattern {
+	return valuePatterns
+}
diff --git a/cluster/router/condition/matcher/pattern_value/pattern.go b/cluster/router/condition/matcher/pattern_value/pattern.go
new file mode 100644
index 000000000..743719c89
--- /dev/null
+++ b/cluster/router/condition/matcher/pattern_value/pattern.go
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 pattern_value
+
+import (
+	"dubbo.apache.org/dubbo-go/v3/common"
+	"dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+type ValuePattern interface {
+	// ShouldMatch indicates whether the input is a specific pattern, for example, range pattern '1~100', wildcard pattern 'hello*', etc.
+	ShouldMatch(pattern string) bool
+	// Match indicates whether a pattern is matched with the request context
+	Match(pattern string, value string, url *common.URL, invocation protocol.Invocation, isWhenCondition bool) bool
+	// Priority returns a priority for this valuePattern
+	// 0 to ^int(0) is better, smaller value by better priority
+	Priority() int64
+}
diff --git a/cluster/router/condition/matcher/pattern_value/wildcard.go b/cluster/router/condition/matcher/pattern_value/wildcard.go
new file mode 100644
index 000000000..dff572107
--- /dev/null
+++ b/cluster/router/condition/matcher/pattern_value/wildcard.go
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 pattern_value
+
+import (
+	"math"
+	"strings"
+)
+
+import (
+	"dubbo.apache.org/dubbo-go/v3/common"
+	"dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+// WildcardValuePattern evaluator must be the last one being executed.
+// matches with patterns like 'key=hello', 'key=hello*', 'key=*hello', 'key=h*o' or 'key=*'
+type WildcardValuePattern struct {
+}
+
+func init() {
+	SetValuePattern("wildcard", NewValuePattern)
+}
+
+func NewValuePattern() ValuePattern {
+	return &WildcardValuePattern{}
+}
+
+func (w *WildcardValuePattern) Priority() int64 {
+	return math.MaxInt64
+}
+
+func (w *WildcardValuePattern) ShouldMatch(pattern string) bool {
+	return true
+}
+
+func (w *WildcardValuePattern) Match(pattern string, value string, url *common.URL, invocation protocol.Invocation, isWhenCondition bool) bool {
+	if url != nil && strings.HasPrefix(pattern, "$") {
+		pattern = url.GetRawParam(pattern[1:])
+	}
+
+	if "*" == pattern {
+		return true
+	}
+	if pattern == "" && value == "" {
+		return true
+	}
+	if pattern == "" || value == "" {
+		return false
+	}
+
+	i := strings.LastIndex(pattern, "*")
+	// doesn't find "*"
+	if i == -1 {
+		return value == pattern
+	} else if i == len(pattern)-1 {
+		// "*" is at the end
+		return strings.HasPrefix(value, pattern[0:i])
+	} else if i == 0 {
+		// "*" is at the beginning
+		return strings.HasSuffix(value, pattern[i+1:])
+	} else {
+		// "*" is in the middle
+		prefix := pattern[0:i]
+		suffix := pattern[i+1:]
+		return strings.HasPrefix(value, prefix) && strings.HasSuffix(value, suffix)
+	}
+}
diff --git a/cluster/router/condition/route.go b/cluster/router/condition/route.go
new file mode 100644
index 000000000..ea046020d
--- /dev/null
+++ b/cluster/router/condition/route.go
@@ -0,0 +1,329 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 condition
+
+import (
+	"regexp"
+	"sort"
+	"strings"
+	"sync"
+)
+
+import (
+	"github.com/dubbogo/gost/log/logger"
+
+	"github.com/pkg/errors"
+
+	"gopkg.in/yaml.v2"
+)
+
+import (
+	"dubbo.apache.org/dubbo-go/v3/cluster/router/condition/matcher"
+	"dubbo.apache.org/dubbo-go/v3/common"
+	"dubbo.apache.org/dubbo-go/v3/common/constant"
+	"dubbo.apache.org/dubbo-go/v3/config"
+	"dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+var (
+	routePattern = regexp.MustCompile("([&!=,]*)\\s*([^&!=,\\s]+)")
+
+	illegalMsg = "Illegal route rule \"%s\", The error char '%s' before '%s'"
+
+	matcherFactories = make([]matcher.ConditionMatcherFactory, 0, 8)
+
+	once sync.Once
+)
+
+type StateRouter struct {
+	enable        bool
+	force         bool
+	url           *common.URL
+	whenCondition map[string]matcher.Matcher
+	thenCondition map[string]matcher.Matcher
+}
+
+func NewConditionStateRouter(url *common.URL) (*StateRouter, error) {
+	once.Do(initMatcherFactories)
+
+	if len(matcherFactories) == 0 {
+		return nil, errors.Errorf("No ConditionMatcherFactory exists")
+	}
+
+	force := url.GetParamBool(constant.ForceKey, false)
+	enable := url.GetParamBool(constant.EnabledKey, true)
+	c := &StateRouter{
+		url:    url,
+		force:  force,
+		enable: enable,
+	}
+
+	if enable {
+		when, then, err := generateMatcher(url)
+		if err != nil {
+			return nil, err
+		}
+		c.whenCondition = when
+		c.thenCondition = then
+	}
+	return c, nil
+}
+
+// Route Determine the target invokers list.
+func (c *StateRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker {
+	if !c.enable {
+		return invokers
+	}
+
+	if len(invokers) == 0 {
+		return invokers
+	}
+
+	if !c.matchWhen(url, invocation) {
+		return invokers
+	}
+
+	if len(c.thenCondition) == 0 {
+		logger.Warn("condition state router thenCondition is empty")
+		return []protocol.Invoker{}
+	}
+
+	var result = make([]protocol.Invoker, 0, len(invokers))
+	for _, invoker := range invokers {
+		if c.matchThen(invoker.GetURL(), url) {
+			result = append(result, invoker)
+		}
+	}
+
+	if len(result) != 0 {
+		return result
+	} else if c.force {
+		logger.Warn("execute condition state router result list is empty. and force=true")
+		return result
+	}
+
+	return invokers
+}
+
+func (c *StateRouter) URL() *common.URL {
+	return c.url
+}
+
+func (c *StateRouter) Priority() int64 {
+	return 0
+}
+
+func (c *StateRouter) matchWhen(url *common.URL, invocation protocol.Invocation) bool {
+	if len(c.whenCondition) == 0 {
+		return true
+	}
+	return doMatch(url, nil, invocation, c.whenCondition, true)
+}
+
+func (c *StateRouter) matchThen(url *common.URL, param *common.URL) bool {
+	if len(c.thenCondition) == 0 {
+		return false
+	}
+	return doMatch(url, param, nil, c.thenCondition, false)
+}
+
+func generateMatcher(url *common.URL) (when, then map[string]matcher.Matcher, err error) {
+	rule := url.GetParam(constant.RuleKey, "")
+	if rule == "" || len(strings.Trim(rule, " ")) == 0 {
+		return nil, nil, errors.Errorf("Illegal route rule!")
+	}
+	rule = strings.Replace(rule, "consumer.", "", -1)
+	rule = strings.Replace(rule, "provider.", "", -1)
+	i := strings.Index(rule, "=>")
+	// for the case of `{when rule} => {then rule}`
+	var whenRule string
+	var thenRule string
+	if i < 0 {
+		whenRule = ""
+		thenRule = strings.Trim(rule, " ")
+	} else {
+		whenRule = strings.Trim(rule[0:i], " ")
+		thenRule = strings.Trim(rule[i+2:], " ")
+	}
+
+	when, err = parseWhen(whenRule)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	then, err = parseThen(thenRule)
+	if err != nil {
+		return nil, nil, err
+	}
+	// NOTE: It should be determined on the business level whether the `When condition` can be empty or not.
+	return when, then, nil
+}
+
+func parseWhen(whenRule string) (map[string]matcher.Matcher, error) {
+	if whenRule == "" || whenRule == " " || whenRule == "true" {
+		return make(map[string]matcher.Matcher), nil
+	} else {
+		when, err := parseRule(whenRule)
+		if err != nil {
+			return nil, err
+		}
+		return when, nil
+	}
+}
+
+func parseThen(thenRule string) (map[string]matcher.Matcher, error) {
+	if thenRule == "" || thenRule == " " || thenRule == "false" {
+		return nil, nil
+	} else {
+		when, err := parseRule(thenRule)
+		if err != nil {
+			return nil, err
+		}
+		return when, nil
+	}
+}
+
+func parseRule(rule string) (map[string]matcher.Matcher, error) {
+	if isRuleEmpty(rule) {
+		return make(map[string]matcher.Matcher), nil
+	}
+
+	condition, err := processMatchers(rule)
+	if err != nil {
+		return nil, err
+	}
+
+	return condition, nil
+}
+
+func isRuleEmpty(rule string) bool {
+	return rule == "" || (len(rule) == 1 && rule[0] == ' ')
+}
+
+func processMatchers(rule string) (map[string]matcher.Matcher, error) {
+	condition := make(map[string]matcher.Matcher)
+	var currentMatcher matcher.Matcher
+	var err error
+	values := make(map[string]struct{})
+
+	for _, matchers := range routePattern.FindAllStringSubmatch(rule, -1) {
+		separator := matchers[1]
+		content := matchers[2]
+
+		switch separator {
+		case "":
+			currentMatcher = getMatcher(content)
+			condition[content] = currentMatcher
+		case "&":
+			currentMatcher, condition = processAndSeparator(content, condition)
+		case "=", "!=":
+			values, currentMatcher, err = processEqualNotEqualSeparator(separator, content, currentMatcher, rule)
+			if err != nil {
+				return nil, err
+			}
+		case ",":
+			values, err = processCommaSeparator(content, values, rule)
+			if err != nil {
+				return nil, err
+			}
+		default:
+			return nil, errors.Errorf(illegalMsg, rule, separator, content)
+		}
+	}
+
+	return condition, nil
+}
+
+func processAndSeparator(content string, condition map[string]matcher.Matcher) (matcher.Matcher, map[string]matcher.Matcher) {
+	currentMatcher := condition[content]
+	if currentMatcher == nil {
+		currentMatcher = getMatcher(content)
+		condition[content] = currentMatcher
+	}
+	return currentMatcher, condition
+}
+
+func processEqualNotEqualSeparator(separator, content string, currentMatcher matcher.Matcher, rule string) (map[string]struct{}, matcher.Matcher, error) {
+	if currentMatcher == nil {
+		return nil, nil, errors.Errorf(illegalMsg, rule, separator, content)
+	}
+	values := currentMatcher.GetMatches()
+	if separator == "!=" {
+		values = currentMatcher.GetMismatches()
+	}
+	values[content] = struct{}{}
+	return values, currentMatcher, nil
+}
+
+func processCommaSeparator(content string, values map[string]struct{}, rule string) (map[string]struct{}, error) {
+	if len(values) == 0 {
+		return nil, errors.Errorf(illegalMsg, rule, ",", content)
+	}
+	values[content] = struct{}{}
+	return values, nil
+}
+
+func getMatcher(key string) matcher.Matcher {
+	for _, factory := range matcherFactories {
+		if factory.ShouldMatch(key) {
+			return factory.NewMatcher(key)
+		}
+	}
+	return matcher.GetMatcherFactory("param").NewMatcher(key)
+}
+
+func doMatch(url *common.URL, param *common.URL, invocation protocol.Invocation, conditions map[string]matcher.Matcher, isWhenCondition bool) bool {
+	sample := url.ToMap()
+	for _, matcherPair := range conditions {
+		if !matcher.Match(matcherPair, sample, param, invocation, isWhenCondition) {
+			return false
+		}
+	}
+	return true
+}
+
+func initMatcherFactories() {
+	factoriesMap := matcher.GetMatcherFactories()
+	if len(factoriesMap) == 0 {
+		return
+	}
+	for _, factory := range factoriesMap {
+		matcherFactories = append(matcherFactories, factory())
+	}
+	sortMatcherFactories(matcherFactories)
+}
+
+func sortMatcherFactories(matcherFactories []matcher.ConditionMatcherFactory) {
+	sort.Stable(byPriority(matcherFactories))
+}
+
+type byPriority []matcher.ConditionMatcherFactory
+
+func (a byPriority) Len() int           { return len(a) }
+func (a byPriority) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a byPriority) Less(i, j int) bool { return a[i].Priority() < a[j].Priority() }
+
+func parseRoute(routeContent string) (*config.RouterConfig, error) {
+	routeDecoder := yaml.NewDecoder(strings.NewReader(routeContent))
+	routerConfig := &config.RouterConfig{}
+	err := routeDecoder.Decode(routerConfig)
+	if err != nil {
+		return nil, err
+	}
+	return routerConfig, nil
+}
diff --git a/cluster/router/condition/router_test.go b/cluster/router/condition/router_test.go
new file mode 100644
index 000000000..6b264e033
--- /dev/null
+++ b/cluster/router/condition/router_test.go
@@ -0,0 +1,479 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 condition
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"dubbo.apache.org/dubbo-go/v3/common"
+	"dubbo.apache.org/dubbo-go/v3/common/constant"
+	"dubbo.apache.org/dubbo-go/v3/protocol"
+	"dubbo.apache.org/dubbo-go/v3/protocol/invocation"
+)
+
+const (
+	conditionAddr = "condition://127.0.0.1/com.foo.BarService"
+
+	localConsumerAddr  = "consumer://127.0.0.1/com.foo.BarService"
+	remoteConsumerAddr = "consumer://dubbo.apache.org/com.foo.BarService"
+
+	localProviderAddr  = "dubbo://127.0.0.1:20880/com.foo.BarService"
+	remoteProviderAddr = "dubbo://dubbo.apache.org:20880/com.foo.BarService"
+)
+
+func TestRouteMatchWhen(t *testing.T) {
+
+	rpcInvocation := invocation.NewRPCInvocation("getFoo", nil, nil)
+	whenConsumerURL, _ := common.NewURL(remoteConsumerAddr)
+
+	testData := []struct {
+		name        string
+		consumerUrl *common.URL
+		rule        string
+
+		wantVal bool
+	}{
+		{
+			name:        " => host = 1.2.3.4",
+			consumerUrl: whenConsumerURL,
+			rule:        " => host = 1.2.3.4",
+
+			wantVal: true,
+		},
+		{
+			name:        "host = 1.2.3.4 => ",
+			consumerUrl: whenConsumerURL,
+			rule:        "host = 1.2.3.4 => ",
+
+			wantVal: false,
+		},
+		{
+			name:        "host = 2.2.2.2,dubbo.apache.org,3.3.3.3 => host = 1.2.3.4",
+			consumerUrl: whenConsumerURL,
+			rule:        "host = 2.2.2.2,dubbo.apache.org,3.3.3.3 => host = 1.2.3.4",
+
+			wantVal: true,
+		},
+		{
+			name:        "host = 2.2.2.2,dubbo.apache.org,3.3.3.3 & host !=dubbo.apache.org => host = 1.2.3.4",
+			consumerUrl: whenConsumerURL,
+			rule:        "host = 2.2.2.2,dubbo.apache.org,3.3.3.3 & host !=dubbo.apache.org => host = 1.2.3.4",
+
+			wantVal: false,
+		},
+		{
+			name:        "host !=4.4.4.4 & host = 2.2.2.2,dubbo.apache.org,3.3.3.3 => host = 1.2.3.4",
+			consumerUrl: whenConsumerURL,
+			rule:        "host !=4.4.4.4 & host = 2.2.2.2,dubbo.apache.org,3.3.3.3 => host = 1.2.3.4",
+
+			wantVal: true,
+		},
+		{
+			name:        "host !=4.4.4.* & host = 2.2.2.2,dubbo.apache.org,3.3.3.3 => host = 1.2.3.4",
+			consumerUrl: whenConsumerURL,
+			rule:        "host !=4.4.4.* & host = 2.2.2.2,dubbo.apache.org,3.3.3.3 => host = 1.2.3.4",
+
+			wantVal: true,
+		},
+		{
+			name:        "host = 2.2.2.2,dubbo.apache.*,3.3.3.3 & host != dubbo.apache.org => host = 1.2.3.4",
+			consumerUrl: whenConsumerURL,
+			rule:        "host = 2.2.2.2,dubbo.apache.*,3.3.3.3 & host != dubbo.apache.org => host = 1.2.3.4",
+
+			wantVal: false,
+		},
+		{
+			name:        "host = 2.2.2.2,dubbo.apache.*,3.3.3.3 & host != 1.1.1.2 => host = 1.2.3.4",
+			consumerUrl: whenConsumerURL,
+			rule:        "host = 2.2.2.2,dubbo.apache.*,3.3.3.3 & host != 1.1.1.2 => host = 1.2.3.4",
+
+			wantVal: true,
+		},
+	}
+
+	for _, data := range testData {
+		t.Run(data.name, func(t *testing.T) {
+			url, err := common.NewURL(conditionAddr)
+			assert.Nil(t, err)
+			url.AddParam(constant.RuleKey, data.rule)
+			router, err := NewConditionStateRouter(url)
+			assert.Nil(t, err)
+			resVal := router.matchWhen(data.consumerUrl, rpcInvocation)
+			assert.Equal(t, data.wantVal, resVal)
+		})
+	}
+}
+
+// TestRouteMatchFilter also tests pattern_value.WildcardValuePattern's Match method
+func TestRouteMatchFilter(t *testing.T) {
+
+	consumerURL, _ := common.NewURL(localConsumerAddr)
+	url1, _ := common.NewURL(remoteProviderAddr + "?serialization=fastjson")
+	url2, _ := common.NewURL(localProviderAddr)
+	url3, _ := common.NewURL(localProviderAddr)
+
+	rpcInvocation := invocation.NewRPCInvocation("getFoo", nil, nil)
+
+	ink1 := protocol.NewBaseInvoker(url1)
+	ink2 := protocol.NewBaseInvoker(url2)
+	ink3 := protocol.NewBaseInvoker(url3)
+
+	invokerList := make([]protocol.Invoker, 0, 3)
+	invokerList = append(invokerList, ink1)
+	invokerList = append(invokerList, ink2)
+	invokerList = append(invokerList, ink3)
+
+	testData := []struct {
+		name        string
+		comsumerURL *common.URL
+		rule        string
+
+		wantVal int
+	}{
+		{
+			name:        "host = 127.0.0.1 => host = dubbo.apache.org",
+			comsumerURL: consumerURL,
+			rule:        "host = 127.0.0.1 => host = dubbo.apache.org",
+
+			wantVal: 1,
+		},
+		{
+			name:        "host = 127.0.0.1 => host = 10.20.3.* & host != dubbo.apache.org",
+			comsumerURL: consumerURL,
+			rule:        "host = 127.0.0.1 => host = 10.20.3.* & host != dubbo.apache.org",
+
+			wantVal: 0,
+		},
+		{
+			name:        "host = 127.0.0.1 => host = dubbo.apache.org  & host != dubbo.apache.org",
+			comsumerURL: consumerURL,
+			rule:        "host = 127.0.0.1 => host = dubbo.apache.org  & host != dubbo.apache.org",
+
+			wantVal: 0,
+		},
+		{
+			name:        "host = 127.0.0.1 => host = 10.20.3.2,dubbo.apache.org,10.20.3.4",
+			comsumerURL: consumerURL,
+			rule:        "host = 127.0.0.1 => host = 10.20.3.2,dubbo.apache.org,10.20.3.4",
+
+			wantVal: 1,
+		},
+		{
+			name:        "host = 127.0.0.1 => host != dubbo.apache.org",
+			comsumerURL: consumerURL,
+			rule:        "host = 127.0.0.1 => host != dubbo.apache.org",
+
+			wantVal: 2,
+		},
+		{
+			name:        "host = 127.0.0.1 => serialization = fastjson",
+			comsumerURL: consumerURL,
+			rule:        "host = 127.0.0.1 => serialization = fastjson",
+
+			wantVal: 1,
+		},
+	}
+
+	for _, data := range testData {
+		t.Run(data.name, func(t *testing.T) {
+			url, err := common.NewURL(conditionAddr)
+			assert.Nil(t, err)
+			url.AddParam(constant.RuleKey, data.rule)
+			url.AddParam(constant.ForceKey, "true")
+
+			router, err := NewConditionStateRouter(url)
+			assert.Nil(t, err)
+
+			filteredInvokers := router.Route(invokerList, data.comsumerURL, rpcInvocation)
+			resVal := len(filteredInvokers)
+			assert.Equal(t, data.wantVal, resVal)
+		})
+	}
+}
+
+func TestRouterMethodRoute(t *testing.T) {
+
+	rpcInvocation := invocation.NewRPCInvocation("getFoo", nil, nil)
+
+	testData := []struct {
+		name        string
+		consumerURL string
+		rule        string
+
+		wantVal bool
+	}{
+		{
+			name:        "More than one methods, mismatch",
+			consumerURL: remoteConsumerAddr + "?methods=setFoo,getFoo,findFoo",
+			rule:        "methods=getFoo => host = 1.2.3.4",
+
+			wantVal: true,
+		},
+		{
+			name:        "Exactly one method, match",
+			consumerURL: remoteConsumerAddr + "?methods=getFoo",
+			rule:        "methods=getFoo => host = 1.2.3.4",
+
+			wantVal: true,
+		},
+		{
+			name:        "Method routing and Other condition routing can work together",
+			consumerURL: remoteConsumerAddr + "?methods=getFoo",
+			rule:        "methods=getFoo & host!=dubbo.apache.org => host = 1.2.3.4",
+
+			wantVal: false,
+		},
+	}
+
+	for _, data := range testData {
+		t.Run(data.name, func(t *testing.T) {
+			url, err := common.NewURL(conditionAddr)
+			assert.Nil(t, err)
+			url.AddParam(constant.RuleKey, data.rule)
+			router, err := NewConditionStateRouter(url)
+			assert.Nil(t, err)
+			consumer, _ := common.NewURL(data.consumerURL)
+			resVal := router.matchWhen(consumer, rpcInvocation)
+			assert.Equal(t, data.wantVal, resVal)
+		})
+	}
+}
+
+func TestRouteReturn(t *testing.T) {
+
+	rpcInvocation := invocation.NewRPCInvocation("getFoo", nil, nil)
+	consumerURL, _ := common.NewURL(localConsumerAddr)
+
+	testData := []struct {
+		name string
+		urls []string
+		rule string
+
+		wantUrls []string
+		wantVal  int
+	}{
+		{
+			name: "ReturnFalse",
+			urls: []string{
+				"",
+				"",
+				"",
+			},
+			rule: "host = 127.0.0.1 => false",
+
+			wantUrls: []string{},
+			wantVal:  0,
+		},
+		{
+			name: "ReturnEmpty",
+			urls: []string{
+				"",
+				"",
+				"",
+			},
+			rule: "host = 127.0.0.1 => ",
+
+			wantUrls: []string{},
+			wantVal:  0,
+		},
+		{
+			name: "ReturnAll",
+			urls: []string{
+				localProviderAddr,
+				localProviderAddr,
+				localProviderAddr,
+			},
+			rule: "host = 127.0.0.1 => host = 127.0.0.1",
+
+			wantUrls: []string{
+				localProviderAddr,
+				localProviderAddr,
+				localProviderAddr,
+			},
+			wantVal: 3,
+		},
+		{
+			name: "HostFilter",
+			urls: []string{
+				remoteProviderAddr,
+				localProviderAddr,
+				localProviderAddr,
+			},
+			rule: "host = 127.0.0.1 => host = 127.0.0.1",
+
+			wantUrls: []string{
+				localProviderAddr,
+				localProviderAddr,
+			},
+			wantVal: 2,
+		},
+		{
+			name: "EmptyHostFilter",
+			urls: []string{
+				remoteProviderAddr,
+				localProviderAddr,
+				localProviderAddr,
+			},
+			rule: " => host = 127.0.0.1",
+
+			wantUrls: []string{
+				localProviderAddr,
+				localProviderAddr,
+			},
+			wantVal: 2,
+		},
+		{
+			name: "FalseHostFilter",
+			urls: []string{
+				remoteProviderAddr,
+				localProviderAddr,
+				localProviderAddr,
+			},
+			rule: "true => host = 127.0.0.1",
+
+			wantUrls: []string{
+				localProviderAddr,
+				localProviderAddr,
+			},
+			wantVal: 2,
+		},
+		{
+			name: "PlaceHolder",
+			urls: []string{
+				remoteProviderAddr,
+				localProviderAddr,
+				localProviderAddr,
+			},
+			rule: "host = 127.0.0.1 => host = $host",
+
+			wantUrls: []string{
+				localProviderAddr,
+				localProviderAddr,
+			},
+			wantVal: 2,
+		},
+	}
+
+	for _, data := range testData {
+		t.Run(data.name, func(t *testing.T) {
+
+			invokers := make([]protocol.Invoker, 0, len(data.urls))
+			for _, urlStr := range data.urls {
+				url, _ := common.NewURL(urlStr)
+				invoker := protocol.NewBaseInvoker(url)
+				invokers = append(invokers, invoker)
+			}
+
+			wantInvokers := make([]protocol.Invoker, 0, len(data.wantUrls))
+			for _, wantUrlStr := range data.wantUrls {
+				url, _ := common.NewURL(wantUrlStr)
+				invoker := protocol.NewBaseInvoker(url)
+				wantInvokers = append(wantInvokers, invoker)
+			}
+
+			url, err := common.NewURL(conditionAddr)
+			assert.Nil(t, err)
+			url.AddParam(constant.RuleKey, data.rule)
+			router, err := NewConditionStateRouter(url)
+			assert.Nil(t, err)
+
+			filterInvokers := router.Route(invokers, consumerURL, rpcInvocation)
+			resVal := len(filterInvokers)
+
+			assert.Equal(t, data.wantVal, resVal)
+			assert.Equal(t, wantInvokers, filterInvokers)
+		})
+	}
+}
+
+// TestRouteArguments also tests matcher.ArgumentConditionMatcher's GetValue method
+func TestRouteArguments(t *testing.T) {
+
+	url1, _ := common.NewURL(remoteProviderAddr)
+	url2, _ := common.NewURL(localProviderAddr)
+	url3, _ := common.NewURL(localProviderAddr)
+
+	ink1 := protocol.NewBaseInvoker(url1)
+	ink2 := protocol.NewBaseInvoker(url2)
+	ink3 := protocol.NewBaseInvoker(url3)
+
+	invokerList := make([]protocol.Invoker, 0, 3)
+	invokerList = append(invokerList, ink1)
+	invokerList = append(invokerList, ink2)
+	invokerList = append(invokerList, ink3)
+
+	consumerURL, _ := common.NewURL(localConsumerAddr)
+
+	testData := []struct {
+		name     string
+		argument interface{}
+		rule     string
+
+		wantVal int
+	}{
+		{
+			name:     "Empty arguments",
+			argument: nil,
+			rule:     "arguments[0] = a => host = 1.2.3.4",
+
+			wantVal: 3,
+		},
+		{
+			name:     "String arguments",
+			argument: "a",
+			rule:     "arguments[0] = a => host = 1.2.3.4",
+
+			wantVal: 0,
+		},
+		{
+			name:     "Int arguments",
+			argument: 1,
+			rule:     "arguments[0] = 1 => host = 127.0.0.1",
+
+			wantVal: 2,
+		},
+	}
+
+	for _, data := range testData {
+		t.Run(data.name, func(t *testing.T) {
+
+			url, err := common.NewURL(conditionAddr)
+			assert.Nil(t, err)
+			url.AddParam(constant.RuleKey, data.rule)
+			url.AddParam(constant.ForceKey, "true")
+			router, err := NewConditionStateRouter(url)
+			assert.Nil(t, err)
+
+			arguments := make([]interface{}, 0, 1)
+			arguments = append(arguments, data.argument)
+
+			rpcInvocation := invocation.NewRPCInvocation("getBar", arguments, nil)
+
+			filterInvokers := router.Route(invokerList, consumerURL, rpcInvocation)
+			resVal := len(filterInvokers)
+			assert.Equal(t, data.wantVal, resVal)
+
+		})
+	}
+}
diff --git a/common/constant/key.go b/common/constant/key.go
index 783eae098..131b200ae 100644
--- a/common/constant/key.go
+++ b/common/constant/key.go
@@ -296,14 +296,21 @@ const (
 
 // Use for router module
 const (
-	TagRouterRuleSuffix       = ".tag-router"
-	ConditionRouterRuleSuffix = ".condition-router"       // Specify condition router suffix
-	MeshRouteSuffix           = ".MESHAPPRULE"            // Specify mesh router suffix
-	ForceUseTag               = "dubbo.force.tag"         // the tag in attachment
-	Tagkey                    = "dubbo.tag"               // key of tag
-	AttachmentKey             = DubboCtxKey("attachment") // key in context in invoker
-	TagRouterFactoryKey       = "tag"
-	MeshRouterFactoryKey      = "mesh"
+	TagRouterRuleSuffix              = ".tag-router"
+	ConditionRouterRuleSuffix        = ".condition-router" // Specify condition router suffix
+	MeshRouteSuffix                  = ".MESHAPPRULE"      // Specify mesh router suffix
+	ForceUseTag                      = "dubbo.force.tag"   // the tag in attachment
+	ForceUseCondition                = "dubbo.force.condition"
+	Tagkey                           = "dubbo.tag" // key of tag
+	ConditionKey                     = "dubbo.condition"
+	AttachmentKey                    = DubboCtxKey("attachment") // key in context in invoker
+	TagRouterFactoryKey              = "tag"
+	ConditionAppRouterFactoryKey     = "provider.condition"
+	ConditionServiceRouterFactoryKey = "service.condition"
+	ForceKey                         = "force"
+	Arguments                        = "arguments"
+	Attachments                      = "attachments"
+	MeshRouterFactoryKey             = "mesh"
 )
 
 // Auth filter