Fix url attributes merge (#2504)

followup of #2503
diff --git a/client/action.go b/client/action.go
index aa69089..7cc67a0 100644
--- a/client/action.go
+++ b/client/action.go
@@ -255,7 +255,7 @@
 				}
 				// replace params of serviceURL with params of cfgUrl
 				// other stuff, e.g. IP, port, etc., are same as serviceURL
-				newURL := common.MergeURL(serviceURL, cfgURL)
+				newURL := serviceURL.MergeURL(cfgURL)
 				newURL.AddParam("peer", "true")
 				refOpts.urls = append(refOpts.urls, newURL)
 			}
diff --git a/common/url.go b/common/url.go
index e985433..156ba9f 100644
--- a/common/url.go
+++ b/common/url.go
@@ -772,20 +772,20 @@
 // TODO configuration merge, in the future , the configuration center's config should merge too.
 
 // MergeURL will merge those two URL
-// the result is based on serviceURL, and the key which si only contained in referenceURL
+// the result is based on c, and the key which si only contained in anotherUrl
 // will be added into result.
-// for example, if serviceURL contains params (a1->v1, b1->v2) and referenceURL contains params(a2->v3, b1 -> v4)
+// for example, if c contains params (a1->v1, b1->v2) and anotherUrl contains params(a2->v3, b1 -> v4)
 // the params of result will be (a1->v1, b1->v2, a2->v3).
 // You should notice that the value of b1 is v2, not v4
 // except constant.LoadbalanceKey, constant.ClusterKey, constant.RetriesKey, constant.TimeoutKey.
 // due to URL is not thread-safe, so this method is not thread-safe
-func MergeURL(serviceURL *URL, referenceURL *URL) *URL {
+func (c *URL) MergeURL(anotherUrl *URL) *URL {
 	// After Clone, it is a new URL that there is no thread safe issue.
-	mergedURL := serviceURL.Clone()
+	mergedURL := c.Clone()
 	params := mergedURL.GetParams()
-	// iterator the referenceURL if serviceURL not have the key ,merge in
-	// referenceURL usually will not changed. so change RangeParams to GetParams to avoid the string value copy.// Group get group
-	for key, value := range referenceURL.GetParams() {
+	// iterator the anotherUrl if c not have the key ,merge in
+	// anotherUrl usually will not changed. so change RangeParams to GetParams to avoid the string value copy.// Group get group
+	for key, value := range anotherUrl.GetParams() {
 		if _, ok := mergedURL.GetNonDefaultParam(key); !ok {
 			if len(value) > 0 {
 				params[key] = value
@@ -796,35 +796,36 @@
 	}
 
 	// remote timestamp
-	if v, ok := serviceURL.GetNonDefaultParam(constant.TimestampKey); !ok {
+	if v, ok := c.GetNonDefaultParam(constant.TimestampKey); !ok {
 		params[constant.RemoteTimestampKey] = []string{v}
-		params[constant.TimestampKey] = []string{referenceURL.GetParam(constant.TimestampKey, "")}
+		params[constant.TimestampKey] = []string{anotherUrl.GetParam(constant.TimestampKey, "")}
 	}
 
 	// finally execute methodConfigMergeFcn
-	mergedURL.Methods = make([]string, len(referenceURL.Methods))
-	for i, method := range referenceURL.Methods {
+	mergedURL.Methods = make([]string, len(anotherUrl.Methods))
+	for i, method := range anotherUrl.Methods {
 		for _, paramKey := range []string{constant.LoadbalanceKey, constant.ClusterKey, constant.RetriesKey, constant.TimeoutKey} {
-			if v := referenceURL.GetParam(paramKey, ""); len(v) > 0 {
+			if v := anotherUrl.GetParam(paramKey, ""); len(v) > 0 {
 				params[paramKey] = []string{v}
 			}
 
 			methodsKey := "methods." + method + "." + paramKey
 			//if len(mergedURL.GetParam(methodsKey, "")) == 0 {
-			if v := referenceURL.GetParam(methodsKey, ""); len(v) > 0 {
+			if v := anotherUrl.GetParam(methodsKey, ""); len(v) > 0 {
 				params[methodsKey] = []string{v}
 			}
 			//}
 			mergedURL.Methods[i] = method
 		}
 	}
-
 	// merge attributes
 	if mergedURL.attributes == nil {
-		mergedURL.attributes = make(map[string]interface{}, len(referenceURL.attributes))
+		mergedURL.attributes = make(map[string]interface{}, len(anotherUrl.attributes))
 	}
-	for attrK, attrV := range referenceURL.attributes {
-		mergedURL.attributes[attrK] = attrV
+	for attrK, attrV := range anotherUrl.attributes {
+		if _, ok := mergedURL.GetAttribute(attrK); !ok {
+			mergedURL.attributes[attrK] = attrV
+		}
 	}
 	// In this way, we will raise some performance.
 	mergedURL.ReplaceParams(params)
diff --git a/common/url_test.go b/common/url_test.go
index 7c9cdf8..fcb437f 100644
--- a/common/url_test.go
+++ b/common/url_test.go
@@ -340,7 +340,7 @@
 	referenceUrl, _ := NewURL("mock1://127.0.0.1:1111", WithParams(referenceUrlParams), WithMethods([]string{"testMethod"}))
 	serviceUrl, _ := NewURL("mock2://127.0.0.1:20000", WithParams(serviceUrlParams))
 
-	mergedUrl := MergeURL(serviceUrl, referenceUrl)
+	mergedUrl := serviceUrl.MergeURL(referenceUrl)
 	assert.Equal(t, "random", mergedUrl.GetParam(constant.ClusterKey, ""))
 	assert.Equal(t, "1", mergedUrl.GetParam("test2", ""))
 	assert.Equal(t, "1", mergedUrl.GetParam("test3", ""))
diff --git a/config/reference_config.go b/config/reference_config.go
index c48bc5d..5cd98cc 100644
--- a/config/reference_config.go
+++ b/config/reference_config.go
@@ -220,7 +220,7 @@
 				}
 				// replace params of serviceURL with params of cfgUrl
 				// other stuff, e.g. IP, port, etc., are same as serviceURL
-				newURL := common.MergeURL(serviceURL, cfgURL)
+				newURL := serviceURL.MergeURL(cfgURL)
 				newURL.AddParam("peer", "true")
 				rc.urls = append(rc.urls, newURL)
 			}
diff --git a/registry/directory/directory.go b/registry/directory/directory.go
index 27cab62..1423615 100644
--- a/registry/directory/directory.go
+++ b/registry/directory/directory.go
@@ -184,7 +184,7 @@
 		// Originally it will Merge URL many times, now we just execute once.
 		// MergeURL is executed once and put the result into Event. After this, the key will get from Event.Key().
 		newUrl := dir.convertUrl(event)
-		newUrl = common.MergeURL(newUrl, referenceUrl)
+		newUrl = newUrl.MergeURL(referenceUrl)
 		dir.overrideUrl(newUrl)
 		event.Update(newUrl)
 	}
@@ -249,7 +249,7 @@
 		return event.Key()
 	}
 	referenceUrl := dir.GetDirectoryUrl().SubURL
-	newUrl := common.MergeURL(event.Service, referenceUrl)
+	newUrl := event.Service.MergeURL(referenceUrl)
 	event.Update(newUrl)
 	return event.Key()
 }
@@ -391,7 +391,7 @@
 	}
 	// check the url's protocol is equal to the protocol which is configured in reference config or referenceUrl is not care about protocol
 	if url.Protocol == referenceUrl.Protocol || referenceUrl.Protocol == "" {
-		newUrl := common.MergeURL(url, referenceUrl)
+		newUrl := url.MergeURL(referenceUrl)
 		dir.overrideUrl(newUrl)
 		event.Update(newUrl)
 		if v, ok := dir.doCacheInvoker(newUrl, event); ok {