You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by il...@apache.org on 2018/08/22 08:17:48 UTC

[incubator-dubbo] branch master updated: [Dubbo- support tag router feature] Add a new Router implement -- TagRouter (#2228)

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

iluo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-dubbo.git


The following commit(s) were added to refs/heads/master by this push:
     new fcd1af8  [Dubbo- support tag router feature] Add a new Router implement -- TagRouter (#2228)
fcd1af8 is described below

commit fcd1af81fd2fd6ad55c78f281d7b29feb10a006a
Author: xujingfeng <25...@qq.com>
AuthorDate: Wed Aug 22 16:17:44 2018 +0800

    [Dubbo- support tag router feature] Add a new Router implement -- TagRouter (#2228)
    
    * tagRouter feature
    
    * update dubbo.xsd
    
    * remove reference router param
    
    * add Unit Test
    
    * rollback pom.xml for merge
    
    * rollback pom.xml for merge
    
    * fix checkstyle
    
    * fix checkstyle
    
    * fix unit test
    
    * format import style
    
    * add license&remove author info
    
    * trigger again
---
 .../dubbo/rpc/cluster/router/tag/TagRouter.java    | 109 +++++++++++++
 .../rpc/cluster/router/tag/TagRouterFactory.java   |  32 ++++
 .../org.apache.dubbo.rpc.cluster.RouterFactory     |   3 +-
 .../rpc/cluster/router/tag/TagRouterTest.java      | 169 +++++++++++++++++++++
 .../java/org/apache/dubbo/common/Constants.java    |   4 +
 .../dubbo/config/AbstractInterfaceConfig.java      |   1 -
 .../dubbo/config/AbstractReferenceConfig.java      |   1 -
 .../apache/dubbo/config/AbstractServiceConfig.java |  10 ++
 .../apache/dubbo/config/annotation/Service.java    |   5 +
 .../apache/dubbo/config/spring/AnnotationBean.java |  24 +--
 .../src/main/resources/META-INF/compat/dubbo.xsd   |   6 +
 .../src/main/resources/META-INF/dubbo.xsd          |   6 +
 .../registry/integration/RegistryDirectory.java    |   8 +-
 13 files changed, 360 insertions(+), 18 deletions(-)

diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouter.java
new file mode 100644
index 0000000..38fd616
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouter.java
@@ -0,0 +1,109 @@
+/*
+ * 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 org.apache.dubbo.rpc.cluster.router.tag;
+
+
+import org.apache.dubbo.common.Constants;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.RpcContext;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.cluster.Router;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TagRouter
+ */
+public class TagRouter implements Router, Comparable<Router> {
+
+    private static final Logger logger = LoggerFactory.getLogger(TagRouter.class);
+
+    private final int priority;
+    private final URL url;
+
+    public static final URL ROUTER_URL = new URL("tag", Constants.ANYHOST_VALUE, 0, Constants.ANY_VALUE).addParameters(Constants.RUNTIME_KEY, "true");
+
+    public TagRouter(URL url) {
+        this.url = url;
+        this.priority = url.getParameter(Constants.PRIORITY_KEY, 0);
+    }
+
+    public TagRouter() {
+        this.url = ROUTER_URL;
+        this.priority = url.getParameter(Constants.PRIORITY_KEY, 0);
+    }
+
+    @Override
+    public URL getUrl() {
+        return url;
+    }
+
+    @Override
+    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
+        // filter
+        List<Invoker<T>> result = new ArrayList<>();
+        try {
+            // Dynamic param
+            String tag = RpcContext.getContext().getAttachment(Constants.REQUEST_TAG_KEY);
+            // Tag request
+            if (!StringUtils.isEmpty(tag)) {
+                // Select tag invokers first
+                for (Invoker<T> invoker : invokers) {
+                    if (tag.equals(invoker.getUrl().getParameter(Constants.TAG_KEY))) {
+                        result.add(invoker);
+                    }
+                }
+                // If no invoker be selected, downgrade to normal invokers
+                if (result.isEmpty()) {
+                    for (Invoker<T> invoker : invokers) {
+                        if (StringUtils.isEmpty(invoker.getUrl().getParameter(Constants.TAG_KEY))) {
+                            result.add(invoker);
+                        }
+                    }
+                }
+            // Normal request
+            } else {
+                for (Invoker<T> invoker : invokers) {
+                    // Can't access tag invoker,only normal invoker should be selected
+                    if (StringUtils.isEmpty(invoker.getUrl().getParameter(Constants.TAG_KEY))) {
+                        result.add(invoker);
+                    }
+                }
+            }
+            return result;
+        } catch (Exception e) {
+            logger.error("Route by tag error,return all invokers.", e);
+        }
+        // Downgrade to all invokers
+        return invokers;
+    }
+
+    @Override
+    public int compareTo(Router o) {
+        if (o == null || o.getClass() != TagRouter.class) {
+            return 1;
+        }
+        TagRouter c = (TagRouter) o;
+        return this.priority == c.priority ? url.toFullString().compareTo(c.url.toFullString()) : (this.priority > c.priority ? 1 : -1);
+    }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouterFactory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouterFactory.java
new file mode 100644
index 0000000..05ad427
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouterFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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 org.apache.dubbo.rpc.cluster.router.tag;
+
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.rpc.cluster.Router;
+import org.apache.dubbo.rpc.cluster.RouterFactory;
+
+public class TagRouterFactory implements RouterFactory {
+
+    public static final String NAME = "tag";
+
+    @Override
+    public Router getRouter(URL url) {
+        return new TagRouter(url);
+    }
+}
diff --git a/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.RouterFactory b/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.RouterFactory
index 0ada9c3..2d4717c 100644
--- a/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.RouterFactory
+++ b/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.RouterFactory
@@ -1,3 +1,4 @@
 file=org.apache.dubbo.rpc.cluster.router.file.FileRouterFactory
 script=org.apache.dubbo.rpc.cluster.router.script.ScriptRouterFactory
-condition=org.apache.dubbo.rpc.cluster.router.condition.ConditionRouterFactory
\ No newline at end of file
+condition=org.apache.dubbo.rpc.cluster.router.condition.ConditionRouterFactory
+tag=org.apache.dubbo.rpc.cluster.router.tag.TagRouterFactory
\ No newline at end of file
diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouterTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouterTest.java
new file mode 100644
index 0000000..839af44
--- /dev/null
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouterTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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 org.apache.dubbo.rpc.cluster.router.tag;
+
+import org.apache.dubbo.common.Constants;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.RpcContext;
+import org.apache.dubbo.rpc.RpcInvocation;
+import org.apache.dubbo.rpc.cluster.Router;
+import org.apache.dubbo.rpc.cluster.RouterFactory;
+import org.apache.dubbo.rpc.cluster.router.MockInvoker;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TagRouterTest {
+
+    private URL tagUrl = new URL("tag"
+            , Constants.ANYHOST_VALUE, 0
+            , Constants.ANY_VALUE)
+            .addParameters(
+                    Constants.RUNTIME_KEY, "true"
+            );
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() throws Exception {
+    }
+
+    @Test
+    public void testRoute_matchTag() {
+
+        RpcContext.getContext().setAttachment(Constants.REQUEST_TAG_KEY, "red");
+
+        List<Invoker<String>> invokers = new ArrayList<>();
+        Invoker<String> redInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.1:20880/com.foo.BarService?tag=red"));
+        Invoker<String> yellowInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.2:20880/com.foo.BarService?tag=yellow"));
+        Invoker<String> blueInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.3:20880/com.foo.BarService?tag=blue"));
+        Invoker<String> defaultInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.4:20880/com.foo.BarService"));
+
+        invokers.add(redInvoker);
+        invokers.add(yellowInvoker);
+        invokers.add(blueInvoker);
+        invokers.add(defaultInvoker);
+
+        Router tagRouter = new TagRouterFactory().getRouter(tagUrl);
+        List<Invoker<String>> filteredInvokers = tagRouter.route(invokers, URL.valueOf("consumer://" + NetUtils.getLocalHost() + "/com.foo.BarService"), new RpcInvocation());
+        Assert.assertTrue(filteredInvokers.contains(redInvoker));
+        Assert.assertFalse(filteredInvokers.contains(yellowInvoker));
+        Assert.assertFalse(filteredInvokers.contains(blueInvoker));
+        Assert.assertFalse(filteredInvokers.contains(defaultInvoker));
+    }
+
+    @Test
+    public void testRoute_matchDefault() {
+
+        RpcContext.getContext().setAttachment(Constants.REQUEST_TAG_KEY, "");
+
+        List<Invoker<String>> invokers = new ArrayList<>();
+        Invoker<String> redInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.1:20880/com.foo.BarService?tag=red"));
+        Invoker<String> yellowInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.2:20880/com.foo.BarService?tag=yellow"));
+        Invoker<String> blueInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.3:20880/com.foo.BarService?tag=blue"));
+        Invoker<String> defaultInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.4:20880/com.foo.BarService"));
+
+        invokers.add(redInvoker);
+        invokers.add(yellowInvoker);
+        invokers.add(blueInvoker);
+        invokers.add(defaultInvoker);
+
+        Router tagRouter = new TagRouterFactory().getRouter(tagUrl);
+        List<Invoker<String>> filteredInvokers = tagRouter.route(invokers, URL.valueOf("consumer://" + NetUtils.getLocalHost() + "/com.foo.BarService"), new RpcInvocation());
+        Assert.assertTrue(filteredInvokers.contains(defaultInvoker));
+        Assert.assertFalse(filteredInvokers.contains(yellowInvoker));
+        Assert.assertFalse(filteredInvokers.contains(blueInvoker));
+        Assert.assertFalse(filteredInvokers.contains(redInvoker));
+    }
+
+    @Test
+    public void testRoute_requestWithTag_shouldDowngrade() {
+
+        RpcContext.getContext().setAttachment(Constants.REQUEST_TAG_KEY, "black");
+
+        List<Invoker<String>> invokers = new ArrayList<>();
+        Invoker<String> redInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.1:20880/com.foo.BarService?tag=red"));
+        Invoker<String> yellowInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.2:20880/com.foo.BarService?tag=yellow"));
+        Invoker<String> blueInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.3:20880/com.foo.BarService?tag=blue"));
+        Invoker<String> defaultInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.4:20880/com.foo.BarService"));
+
+        invokers.add(redInvoker);
+        invokers.add(yellowInvoker);
+        invokers.add(blueInvoker);
+        invokers.add(defaultInvoker);
+
+        Router tagRouter = new TagRouterFactory().getRouter(tagUrl);
+        List<Invoker<String>> filteredInvokers = tagRouter.route(invokers, URL.valueOf("consumer://" + NetUtils.getLocalHost() + "/com.foo.BarService"), new RpcInvocation());
+        Assert.assertTrue(filteredInvokers.contains(defaultInvoker));
+        Assert.assertFalse(filteredInvokers.contains(yellowInvoker));
+        Assert.assertFalse(filteredInvokers.contains(blueInvoker));
+        Assert.assertFalse(filteredInvokers.contains(redInvoker));
+    }
+
+    @Test
+    public void testRoute_requestWithoutTag_shouldNotDowngrade() {
+
+        RpcContext.getContext().setAttachment(Constants.REQUEST_TAG_KEY, "");
+
+        List<Invoker<String>> invokers = new ArrayList<>();
+        Invoker<String> redInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.1:20880/com.foo.BarService?tag=red"));
+        Invoker<String> yellowInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.2:20880/com.foo.BarService?tag=yellow"));
+        Invoker<String> blueInvoker = new MockInvoker<>(URL.valueOf(
+                "dubbo://10.20.3.3:20880/com.foo.BarService?tag=blue"));
+
+        invokers.add(redInvoker);
+        invokers.add(yellowInvoker);
+        invokers.add(blueInvoker);
+
+        Router tagRouter = new TagRouterFactory().getRouter(tagUrl);
+        List<Invoker<String>> filteredInvokers = tagRouter.route(invokers, URL.valueOf("consumer://" + NetUtils.getLocalHost() + "/com.foo.BarService"), new RpcInvocation());
+        Assert.assertEquals(0, filteredInvokers.size());
+    }
+
+    @Test
+    public void testRoute_createBySpi() {
+        URL zkProvider = URL.valueOf("zookeeper://10.20.3.1:20880/com.foo.BarService?router=tag");
+        String parameter = zkProvider.getParameter(Constants.ROUTER_KEY);
+        RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(parameter);
+        Router tagRouter = routerFactory.getRouter(zkProvider);
+        Assert.assertTrue(tagRouter instanceof TagRouter);
+    }
+
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/Constants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/Constants.java
index a1c8fac..9506930 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/Constants.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/Constants.java
@@ -641,6 +641,10 @@ public class Constants {
 
     public static final String MULTICAST = "multicast";
 
+    public static final String TAG_KEY = "tag";
+
+    public static final String REQUEST_TAG_KEY = "request.tag";
+
     /*
      * private Constants(){ }
      */
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
index 91a4a16..70ab62e 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
@@ -522,5 +522,4 @@ public abstract class AbstractInterfaceConfig extends AbstractMethodConfig {
     public void setScope(String scope) {
         this.scope = scope;
     }
-
 }
\ No newline at end of file
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java
index 08f3ad5..552ccf1 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java
@@ -197,5 +197,4 @@ public abstract class AbstractReferenceConfig extends AbstractInterfaceConfig {
         checkKey("group", group);
         this.group = group;
     }
-
 }
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractServiceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractServiceConfig.java
index f23341f..4da1ffa 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractServiceConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractServiceConfig.java
@@ -73,6 +73,9 @@ public abstract class AbstractServiceConfig extends AbstractInterfaceConfig {
     // serialization
     private String serialization;
 
+    // provider tag
+    protected String tag;
+
     public String getVersion() {
         return version;
     }
@@ -240,4 +243,11 @@ public abstract class AbstractServiceConfig extends AbstractInterfaceConfig {
         this.serialization = serialization;
     }
 
+    public String getTag() {
+        return tag;
+    }
+
+    public void setTag(String tag) {
+        this.tag = tag;
+    }
 }
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/annotation/Service.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/annotation/Service.java
index bee8387..6f956f5 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/annotation/Service.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/annotation/Service.java
@@ -263,4 +263,9 @@ public @interface Service {
      * Registry spring bean name
      */
     String[] registry() default {};
+
+    /**
+     * Service tag name
+     */
+    String tag() default "";
 }
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/AnnotationBean.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/AnnotationBean.java
index 6151354..7612c33 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/AnnotationBean.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/AnnotationBean.java
@@ -33,7 +33,6 @@ import org.apache.dubbo.config.RegistryConfig;
 import org.apache.dubbo.config.ServiceConfig;
 import org.apache.dubbo.config.annotation.Reference;
 import org.apache.dubbo.config.annotation.Service;
-
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.DisposableBean;
 import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
@@ -76,7 +75,7 @@ public class AnnotationBean extends AbstractConfig implements DisposableBean, Be
     public void setPackage(String annotationPackage) {
         this.annotationPackage = annotationPackage;
         this.annotationPackages = (annotationPackage == null || annotationPackage.length() == 0) ? null
-            : Constants.COMMA_SPLIT_PATTERN.split(annotationPackage);
+                : Constants.COMMA_SPLIT_PATTERN.split(annotationPackage);
     }
 
     @Override
@@ -86,7 +85,7 @@ public class AnnotationBean extends AbstractConfig implements DisposableBean, Be
 
     @Override
     public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
-        throws BeansException {
+            throws BeansException {
         if (annotationPackage == null || annotationPackage.length() == 0) {
             return;
         }
@@ -135,7 +134,7 @@ public class AnnotationBean extends AbstractConfig implements DisposableBean, Be
 
     @Override
     public Object postProcessAfterInitialization(Object bean, String beanName)
-        throws BeansException {
+            throws BeansException {
         if (!isMatchPackage(bean)) {
             return bean;
         }
@@ -144,7 +143,7 @@ public class AnnotationBean extends AbstractConfig implements DisposableBean, Be
             ServiceBean<Object> serviceConfig = new ServiceBean<Object>(service);
             serviceConfig.setRef(bean);
             if (void.class.equals(service.interfaceClass())
-                && "".equals(service.interfaceName())) {
+                    && "".equals(service.interfaceName())) {
                 if (bean.getClass().getInterfaces().length > 0) {
                     serviceConfig.setInterface(bean.getClass().getInterfaces()[0]);
                 } else {
@@ -186,6 +185,9 @@ public class AnnotationBean extends AbstractConfig implements DisposableBean, Be
                     }
                     serviceConfig.setProtocols(protocolConfigs);
                 }
+                if (service.tag().length() > 0) {
+                    serviceConfig.setTag(service.tag());
+                }
                 try {
                     serviceConfig.afterPropertiesSet();
                 } catch (RuntimeException e) {
@@ -202,7 +204,7 @@ public class AnnotationBean extends AbstractConfig implements DisposableBean, Be
 
     @Override
     public Object postProcessBeforeInitialization(Object bean, String beanName)
-        throws BeansException {
+            throws BeansException {
         if (!isMatchPackage(bean)) {
             return bean;
         }
@@ -210,9 +212,9 @@ public class AnnotationBean extends AbstractConfig implements DisposableBean, Be
         for (Method method : methods) {
             String name = method.getName();
             if (name.length() > 3 && name.startsWith("set")
-                && method.getParameterTypes().length == 1
-                && Modifier.isPublic(method.getModifiers())
-                && !Modifier.isStatic(method.getModifiers())) {
+                    && method.getParameterTypes().length == 1
+                    && Modifier.isPublic(method.getModifiers())
+                    && !Modifier.isStatic(method.getModifiers())) {
                 try {
                     Reference reference = method.getAnnotation(Reference.class);
                     if (reference != null) {
@@ -262,8 +264,8 @@ public class AnnotationBean extends AbstractConfig implements DisposableBean, Be
         if (referenceConfig == null) {
             referenceConfig = new ReferenceBean<Object>(reference);
             if (void.class.equals(reference.interfaceClass())
-                && "".equals(reference.interfaceName())
-                && referenceClass.isInterface()) {
+                    && "".equals(reference.interfaceName())
+                    && referenceClass.isInterface()) {
                 referenceConfig.setInterface(referenceClass);
             }
             if (applicationContext != null) {
diff --git a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd
index 6206048..290c750 100644
--- a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd
+++ b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd
@@ -307,6 +307,12 @@
                         <xsd:documentation><![CDATA[ The serialization protocol of service. ]]></xsd:documentation>
                     </xsd:annotation>
                 </xsd:attribute>
+                <xsd:attribute name="tag" type="xsd:string">
+                    <xsd:annotation>
+                        <xsd:documentation>
+                            <![CDATA[ Defines the service tag]]></xsd:documentation>
+                    </xsd:annotation>
+                </xsd:attribute>
                 <xsd:anyAttribute namespace="##other" processContents="lax"/>
             </xsd:extension>
         </xsd:complexContent>
diff --git a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd
index ecf7d7b..ecec23a 100644
--- a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd
+++ b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd
@@ -307,6 +307,12 @@
                         <xsd:documentation><![CDATA[ The serialization protocol of service. ]]></xsd:documentation>
                     </xsd:annotation>
                 </xsd:attribute>
+                <xsd:attribute name="tag" type="xsd:string">
+                    <xsd:annotation>
+                        <xsd:documentation>
+                            <![CDATA[ Defines the service tag]]></xsd:documentation>
+                    </xsd:annotation>
+                </xsd:attribute>
                 <xsd:anyAttribute namespace="##other" processContents="lax"/>
             </xsd:extension>
         </xsd:complexContent>
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
index 727bca2..6b366b2 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
@@ -55,7 +55,6 @@ import java.util.Set;
 
 /**
  * RegistryDirectory
- *
  */
 public class RegistryDirectory<T> extends AbstractDirectory<T> implements NotifyListener {
 
@@ -445,7 +444,8 @@ public class RegistryDirectory<T> extends AbstractDirectory<T> implements Notify
         List<Router> routers = getRouters();
         if (routers != null) {
             for (Router router : routers) {
-                if (router.getUrl() != null) {
+                // If router's url not null and is not route by runtime,we filter invokers here
+                if (router.getUrl() != null && !router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
                     invokers = router.route(invokers, getConsumerUrl(), invocation);
                 }
             }
@@ -573,8 +573,8 @@ public class RegistryDirectory<T> extends AbstractDirectory<T> implements Notify
         if (forbidden) {
             // 1. No service provider 2. Service providers are disabled
             throw new RpcException(RpcException.FORBIDDEN_EXCEPTION,
-                "No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " +  NetUtils.getLocalHost()
-                        + " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist).");
+                    "No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " + NetUtils.getLocalHost()
+                            + " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist).");
         }
         List<Invoker<T>> invokers = null;
         Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local reference