Merge branch 'ralf0131-graceful-shutdown-in-tomcat'
diff --git a/all/pom.xml b/all/pom.xml
index 09bfe85..edc0c4a 100644
--- a/all/pom.xml
+++ b/all/pom.xml
@@ -321,6 +321,13 @@
         </dependency>
         <dependency>
             <groupId>com.alibaba</groupId>
+            <artifactId>dubbo-bootstrap</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
             <artifactId>hessian-lite</artifactId>
             <version>3.2.2</version>
             <scope>compile</scope>
@@ -418,6 +425,7 @@
                                     <include>com.alibaba:dubbo-serialization-fst</include>
                                     <include>com.alibaba:dubbo-serialization-kryo</include>
                                     <include>com.alibaba:dubbo-serialization-jdk</include>
+                                    <include>com.alibaba:dubbo-bootstrap</include>
                                 </includes>
                             </artifactSet>
                             <transformers>
diff --git a/dubbo-bootstrap/pom.xml b/dubbo-bootstrap/pom.xml
new file mode 100644
index 0000000..bb2b1a4
--- /dev/null
+++ b/dubbo-bootstrap/pom.xml
@@ -0,0 +1,47 @@
+<!--
+  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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>dubbo-parent</artifactId>
+        <groupId>com.alibaba</groupId>
+        <version>2.6.2-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>dubbo-bootstrap</artifactId>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>dubbo-config-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>dubbo-common</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>dubbo-registry-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/DubboBootstrap.java b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/DubboBootstrap.java
new file mode 100644
index 0000000..8694e48
--- /dev/null
+++ b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/DubboBootstrap.java
@@ -0,0 +1,133 @@
+/*
+ * 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.bootstrap;
+
+import com.alibaba.dubbo.common.extension.ExtensionLoader;
+import com.alibaba.dubbo.common.logger.Logger;
+import com.alibaba.dubbo.common.logger.LoggerFactory;
+import com.alibaba.dubbo.config.ServiceConfig;
+import com.alibaba.dubbo.registry.support.AbstractRegistryFactory;
+import com.alibaba.dubbo.rpc.Protocol;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A bootstrap class to easily start and stop Dubbo via programmatic API.
+ * The bootstrap class will be responsible to cleanup the resources during stop.
+ */
+public class DubboBootstrap {
+
+    private static final Logger logger = LoggerFactory.getLogger(DubboBootstrap.class);
+
+    /**
+     * The list of ServiceConfig
+     */
+    private List<ServiceConfig> serviceConfigList;
+
+    /**
+     * Has it already been destroyed or not?
+     */
+    private final AtomicBoolean destroyed;
+
+    /**
+     * The shutdown hook used when Dubbo is running under embedded environment
+     */
+    private Thread shutdownHook;
+
+    public DubboBootstrap() {
+        this.serviceConfigList = new ArrayList<ServiceConfig>();
+        this.destroyed = new AtomicBoolean(false);
+        this.shutdownHook = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                if (logger.isInfoEnabled()) {
+                    logger.info("Run shutdown hook now.");
+                }
+                destroy();
+            }
+        }, "DubboShutdownHook");
+    }
+
+    /**
+     * Register service config to bootstrap, which will be called during {@link DubboBootstrap#stop()}
+     * @param serviceConfig the service
+     * @return the bootstrap instance
+     */
+    public DubboBootstrap regsiterServiceConfig(ServiceConfig serviceConfig) {
+        serviceConfigList.add(serviceConfig);
+        return this;
+    }
+
+    public void start() {
+        registerShutdownHook();
+        for (ServiceConfig serviceConfig: serviceConfigList) {
+            serviceConfig.export();
+        }
+    }
+
+    public void stop() {
+        for (ServiceConfig serviceConfig: serviceConfigList) {
+            serviceConfig.unexport();
+        }
+        destroy();
+        removeShutdownHook();
+    }
+
+    /**
+     * Register the shutdown hook
+     */
+    public void registerShutdownHook() {
+        Runtime.getRuntime().addShutdownHook(shutdownHook);
+    }
+
+    /**
+     * Remove this shutdown hook
+     */
+    public void removeShutdownHook() {
+        try {
+            Runtime.getRuntime().removeShutdownHook(shutdownHook);
+        }
+        catch (IllegalStateException ex) {
+            // ignore - VM is already shutting down
+        }
+    }
+
+    /**
+     * Destroy all the resources, including registries and protocols.
+     */
+    private void destroy() {
+        if (!destroyed.compareAndSet(false, true)) {
+            return;
+        }
+        // destroy all the registries
+        AbstractRegistryFactory.destroyAll();
+        // destroy all the protocols
+        ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
+        for (String protocolName : loader.getLoadedExtensions()) {
+            try {
+                Protocol protocol = loader.getLoadedExtension(protocolName);
+                if (protocol != null) {
+                    protocol.destroy();
+                }
+            } catch (Throwable t) {
+                logger.warn(t.getMessage(), t);
+            }
+        }
+    }
+}
diff --git a/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/ExecutorUtil.java b/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/ExecutorUtil.java
index 6b07cc8..d605e13 100644
--- a/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/ExecutorUtil.java
+++ b/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/ExecutorUtil.java
@@ -43,19 +43,27 @@
         return false;
     }
 
+    /**
+     * Use the shutdown pattern from:
+     *  https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html
+     * @param executor the Executor to shutdown
+     * @param timeout the timeout in milliseconds before termination
+     */
     public static void gracefulShutdown(Executor executor, int timeout) {
         if (!(executor instanceof ExecutorService) || isTerminated(executor)) {
             return;
         }
         final ExecutorService es = (ExecutorService) executor;
         try {
-            es.shutdown(); // Disable new tasks from being submitted
+            // Disable new tasks from being submitted
+            es.shutdown();
         } catch (SecurityException ex2) {
             return;
         } catch (NullPointerException ex2) {
             return;
         }
         try {
+            // Wait a while for existing tasks to terminate
             if (!es.awaitTermination(timeout, TimeUnit.MILLISECONDS)) {
                 es.shutdownNow();
             }
diff --git a/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/AbstractConfig.java b/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/AbstractConfig.java
index 73fc07d..8d8d182 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/AbstractConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/AbstractConfig.java
@@ -73,18 +73,6 @@
         legacyProperties.put("dubbo.service.url", "dubbo.service.address");

     }

 

-    static {

-        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

-            @Override

-            public void run() {

-                if (logger.isInfoEnabled()) {

-                    logger.info("Run shutdown hook now.");

-                }

-                ProtocolConfig.destroyAll();

-            }

-        }, "DubboShutdownHook"));

-    }

-

     protected String id;

 

     private static String convertLegacyValue(String key, String value) {

diff --git a/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ProtocolConfig.java b/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ProtocolConfig.java
index 0e7a524..b15129b 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ProtocolConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ProtocolConfig.java
@@ -21,7 +21,6 @@
 import com.alibaba.dubbo.common.status.StatusChecker;

 import com.alibaba.dubbo.common.threadpool.ThreadPool;

 import com.alibaba.dubbo.config.support.Parameter;

-import com.alibaba.dubbo.registry.support.AbstractRegistryFactory;

 import com.alibaba.dubbo.remoting.Codec;

 import com.alibaba.dubbo.remoting.Dispatcher;

 import com.alibaba.dubbo.remoting.Transporter;

@@ -30,7 +29,6 @@
 import com.alibaba.dubbo.rpc.Protocol;

 

 import java.util.Map;

-import java.util.concurrent.atomic.AtomicBoolean;

 

 /**

  * ProtocolConfig

@@ -135,8 +133,6 @@
     // if it's default

     private Boolean isDefault;

 

-    private static final AtomicBoolean destroyed = new AtomicBoolean(false);

-

     public ProtocolConfig() {

     }

 

@@ -149,27 +145,6 @@
         setPort(port);

     }

 

-    // TODO: 2017/8/30 to move this method somewhere else

-    public static void destroyAll() {

-        if (!destroyed.compareAndSet(false, true)) {

-            return;

-        }

-

-        AbstractRegistryFactory.destroyAll();

-

-        ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);

-        for (String protocolName : loader.getLoadedExtensions()) {

-            try {

-                Protocol protocol = loader.getLoadedExtension(protocolName);

-                if (protocol != null) {

-                    protocol.destroy();

-                }

-            } catch (Throwable t) {

-                logger.warn(t.getMessage(), t);

-            }

-        }

-    }

-

     @Parameter(excluded = true)

     public String getName() {

         return name;

diff --git a/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/RegistryConfig.java b/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/RegistryConfig.java
index 7e2672b..e5684f3 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/RegistryConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/RegistryConfig.java
@@ -18,7 +18,6 @@
 

 import com.alibaba.dubbo.common.Constants;

 import com.alibaba.dubbo.config.support.Parameter;

-import com.alibaba.dubbo.registry.support.AbstractRegistryFactory;

 

 import java.util.Map;

 

@@ -96,13 +95,9 @@
         setAddress(address);

     }

 

-    public static void destroyAll() {

-        AbstractRegistryFactory.destroyAll();

-    }

-

-    @Deprecated

-    public static void closeAll() {

-        destroyAll();

+    public RegistryConfig(String address, String protocol) {

+        setAddress(address);

+        setProtocol(protocol);

     }

 

     public String getProtocol() {

diff --git a/dubbo-config/dubbo-config-api/src/test/java/com/alibaba/dubbo/config/ProtocolConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/com/alibaba/dubbo/config/ProtocolConfigTest.java
index c9d9046..4d10c72 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/com/alibaba/dubbo/config/ProtocolConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/com/alibaba/dubbo/config/ProtocolConfigTest.java
@@ -17,7 +17,6 @@
 
 package com.alibaba.dubbo.config;
 
-import com.alibaba.dubbo.common.extension.ExtensionLoader;
 import com.alibaba.dubbo.config.mock.MockProtocol2;
 import com.alibaba.dubbo.rpc.Protocol;
 import org.junit.Test;
@@ -33,15 +32,6 @@
 import static org.junit.Assert.assertThat;
 
 public class ProtocolConfigTest {
-    @Test
-    public void testDestroyAll() throws Exception {
-        Protocol protocol = Mockito.mock(Protocol.class);
-        MockProtocol2.delegate = protocol;
-        ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
-        loader.getExtension("mockprotocol2");
-        ProtocolConfig.destroyAll();
-        Mockito.verify(protocol).destroy();
-    }
 
     @Test
     public void testDestroy() throws Exception {
diff --git a/dubbo-config/dubbo-config-api/src/test/java/com/alibaba/dubbo/config/ProtocolConfigTest.java.orig b/dubbo-config/dubbo-config-api/src/test/java/com/alibaba/dubbo/config/ProtocolConfigTest.java.orig
new file mode 100644
index 0000000..c22910b
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/test/java/com/alibaba/dubbo/config/ProtocolConfigTest.java.orig
@@ -0,0 +1,214 @@
+/*
+ * 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 com.alibaba.dubbo.config;
+
+<<<<<<< HEAD
+import com.alibaba.dubbo.common.extension.ExtensionLoader;
+import com.alibaba.dubbo.config.mock.MockProtocol2;
+=======
+>>>>>>> e201004e985f3ae43ee8c65baa16bcc0aecc0000
+import com.alibaba.dubbo.rpc.Protocol;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class ProtocolConfigTest {
+
+    @Test
+    public void testDestroy() throws Exception {
+        Protocol protocol = Mockito.mock(Protocol.class);
+        MockProtocol2.delegate = protocol;
+        ProtocolConfig protocolConfig = new ProtocolConfig();
+        protocolConfig.setName("mockprotocol2");
+        protocolConfig.destory();
+        Mockito.verify(protocol).destroy();
+    }
+
+    @Test
+    public void testName() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setName("name");
+        Map<String, String> parameters = new HashMap<String, String>();
+        ProtocolConfig.appendParameters(parameters, protocol);
+        assertThat(protocol.getName(), equalTo("name"));
+        assertThat(protocol.getId(), equalTo("name"));
+        assertThat(parameters.isEmpty(), is(true));
+    }
+
+    @Test
+    public void testHost() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setHost("host");
+        Map<String, String> parameters = new HashMap<String, String>();
+        ProtocolConfig.appendParameters(parameters, protocol);
+        assertThat(protocol.getHost(), equalTo("host"));
+        assertThat(parameters.isEmpty(), is(true));
+    }
+
+    @Test
+    public void testPort() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setPort(8080);
+        Map<String, String> parameters = new HashMap<String, String>();
+        ProtocolConfig.appendParameters(parameters, protocol);
+        assertThat(protocol.getPort(), equalTo(8080));
+        assertThat(parameters.isEmpty(), is(true));
+    }
+
+    @Test
+    public void testPath() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setContextpath("context-path");
+        Map<String, String> parameters = new HashMap<String, String>();
+        ProtocolConfig.appendParameters(parameters, protocol);
+        assertThat(protocol.getPath(), equalTo("context-path"));
+        assertThat(protocol.getContextpath(), equalTo("context-path"));
+        assertThat(parameters.isEmpty(), is(true));
+        protocol.setPath("path");
+        assertThat(protocol.getPath(), equalTo("path"));
+        assertThat(protocol.getContextpath(), equalTo("path"));
+    }
+
+    @Test
+    public void testThreads() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setThreads(10);
+        assertThat(protocol.getThreads(), is(10));
+    }
+
+    @Test
+    public void testIothreads() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setIothreads(10);
+        assertThat(protocol.getIothreads(), is(10));
+    }
+
+    @Test
+    public void testQueues() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setQueues(10);
+        assertThat(protocol.getQueues(), is(10));
+    }
+
+    @Test
+    public void testAccepts() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setAccepts(10);
+        assertThat(protocol.getAccepts(), is(10));
+    }
+
+    @Test
+    public void testCodec() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setName("dubbo");
+        protocol.setCodec("mockcodec");
+        assertThat(protocol.getCodec(), equalTo("mockcodec"));
+    }
+
+    @Test
+    public void testAccesslog() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setAccesslog("access.log");
+        assertThat(protocol.getAccesslog(), equalTo("access.log"));
+    }
+
+    @Test
+    public void testTelnet() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setTelnet("mocktelnethandler");
+        assertThat(protocol.getTelnet(), equalTo("mocktelnethandler"));
+    }
+
+    @Test
+    public void testRegister() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setRegister(true);
+        assertThat(protocol.isRegister(), is(true));
+    }
+
+    @Test
+    public void testTransporter() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setTransporter("mocktransporter");
+        assertThat(protocol.getTransporter(), equalTo("mocktransporter"));
+    }
+
+    @Test
+    public void testExchanger() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setExchanger("mockexchanger");
+        assertThat(protocol.getExchanger(), equalTo("mockexchanger"));
+    }
+
+    @Test
+    public void testDispatcher() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setDispatcher("mockdispatcher");
+        assertThat(protocol.getDispatcher(), equalTo("mockdispatcher"));
+    }
+
+    @Test
+    public void testNetworker() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setNetworker("networker");
+        assertThat(protocol.getNetworker(), equalTo("networker"));
+    }
+
+    @Test
+    public void testParameters() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setParameters(Collections.singletonMap("k1", "v1"));
+        assertThat(protocol.getParameters(), hasEntry("k1", "v1"));
+    }
+
+    @Test
+    public void testDefault() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setDefault(true);
+        assertThat(protocol.isDefault(), is(true));
+    }
+
+    @Test
+    public void testKeepAlive() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setKeepAlive(true);
+        assertThat(protocol.getKeepAlive(), is(true));
+    }
+
+    @Test
+    public void testOptimizer() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setOptimizer("optimizer");
+        assertThat(protocol.getOptimizer(), equalTo("optimizer"));
+    }
+
+    @Test
+    public void testExtension() throws Exception {
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setExtension("extension");
+        assertThat(protocol.getExtension(), equalTo("extension"));
+    }
+}
diff --git a/dubbo-config/dubbo-config-spring/pom.xml b/dubbo-config/dubbo-config-spring/pom.xml
index c7a6660..170ac2c 100644
--- a/dubbo-config/dubbo-config-spring/pom.xml
+++ b/dubbo-config/dubbo-config-spring/pom.xml
@@ -36,14 +36,28 @@
             <version>${project.parent.version}</version>

         </dependency>

         <dependency>

+            <groupId>com.alibaba</groupId>

+            <artifactId>dubbo-bootstrap</artifactId>

+            <version>${project.parent.version}</version>

+        </dependency>

+        <dependency>

             <groupId>org.springframework</groupId>

             <artifactId>spring-beans</artifactId>

         </dependency>

         <dependency>

             <groupId>org.springframework</groupId>

+            <artifactId>spring-web</artifactId>

+        </dependency>

+        <dependency>

+            <groupId>org.springframework</groupId>

             <artifactId>spring-context</artifactId>

         </dependency>

         <dependency>

+            <groupId>javax.servlet</groupId>

+            <artifactId>javax.servlet-api</artifactId>

+            <scope>provided</scope>

+        </dependency>

+        <dependency>

             <groupId>com.alibaba</groupId>

             <artifactId>dubbo-registry-default</artifactId>

             <version>${project.parent.version}</version>

diff --git a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/initializer/DubboApplicationListener.java b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/initializer/DubboApplicationListener.java
new file mode 100644
index 0000000..43ee49d
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/initializer/DubboApplicationListener.java
@@ -0,0 +1,45 @@
+/*
+ * 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 com.alibaba.dubbo.config.spring.initializer;
+
+import org.apache.dubbo.bootstrap.DubboBootstrap;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.context.event.ContextRefreshedEvent;
+
+/**
+ * An application listener that listens the ContextClosedEvent.
+ * Upon the event, this listener will do the necessary clean up to avoid memory leak.
+ */
+public class DubboApplicationListener implements ApplicationListener<ApplicationEvent> {
+
+    private DubboBootstrap dubboBootstrap;
+
+    public DubboApplicationListener() {
+        dubboBootstrap = new DubboBootstrap();
+    }
+
+    @Override
+    public void onApplicationEvent(ApplicationEvent applicationEvent) {
+        if (applicationEvent instanceof ContextRefreshedEvent) {
+            dubboBootstrap.start();
+        } else if (applicationEvent instanceof ContextClosedEvent) {
+            dubboBootstrap.stop();
+        }
+    }
+}
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/initializer/DubboWebApplicationInitializer.java b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/initializer/DubboWebApplicationInitializer.java
new file mode 100644
index 0000000..8d0f79d
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/initializer/DubboWebApplicationInitializer.java
@@ -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 com.alibaba.dubbo.config.spring.initializer;
+
+import org.springframework.web.context.AbstractContextLoaderInitializer;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.XmlWebApplicationContext;
+
+/**
+ * An initializer to register {@link DubboApplicationListener}
+ * to the ApplicationContext seamlessly.
+ */
+public class DubboWebApplicationInitializer extends AbstractContextLoaderInitializer {
+
+    /**
+     * This method won't be triggered if running on spring-boot.
+     * It only works when running under a servlet container.
+     * @return a WebApplicationContext with DubboApplicationListener registered.
+     */
+    @Override
+    protected WebApplicationContext createRootApplicationContext() {
+        XmlWebApplicationContext webApplicationContext = new XmlWebApplicationContext();
+        webApplicationContext.addApplicationListener(new DubboApplicationListener());
+        return webApplicationContext;
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/com/alibaba/dubbo/registry/support/FailbackRegistry.java b/dubbo-registry/dubbo-registry-api/src/main/java/com/alibaba/dubbo/registry/support/FailbackRegistry.java
index ced0efa..e081957 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/com/alibaba/dubbo/registry/support/FailbackRegistry.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/com/alibaba/dubbo/registry/support/FailbackRegistry.java
@@ -19,6 +19,7 @@
 import com.alibaba.dubbo.common.Constants;

 import com.alibaba.dubbo.common.URL;

 import com.alibaba.dubbo.common.utils.ConcurrentHashSet;

+import com.alibaba.dubbo.common.utils.ExecutorUtil;

 import com.alibaba.dubbo.common.utils.NamedThreadFactory;

 import com.alibaba.dubbo.registry.NotifyListener;

 

@@ -57,9 +58,14 @@
 

     private final ConcurrentMap<URL, Map<NotifyListener, List<URL>>> failedNotified = new ConcurrentHashMap<URL, Map<NotifyListener, List<URL>>>();

 

+    /**

+     * The time in milliseconds the retryExecutor will wait

+     */

+    private final int retryPeriod;

+

     public FailbackRegistry(URL url) {

         super(url);

-        int retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);

+        this.retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);

         this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {

             @Override

             public void run() {

@@ -440,6 +446,7 @@
         } catch (Throwable t) {

             logger.warn(t.getMessage(), t);

         }

+        ExecutorUtil.gracefulShutdown(retryExecutor, retryPeriod);

     }

 

     // ==== Template method ====

diff --git a/dubbo-registry/dubbo-registry-default/src/main/java/com/alibaba/dubbo/registry/dubbo/DubboRegistry.java b/dubbo-registry/dubbo-registry-default/src/main/java/com/alibaba/dubbo/registry/dubbo/DubboRegistry.java
index 9d5ab70..bf8056e 100644
--- a/dubbo-registry/dubbo-registry-default/src/main/java/com/alibaba/dubbo/registry/dubbo/DubboRegistry.java
+++ b/dubbo-registry/dubbo-registry-default/src/main/java/com/alibaba/dubbo/registry/dubbo/DubboRegistry.java
@@ -21,6 +21,7 @@
 import com.alibaba.dubbo.common.Version;

 import com.alibaba.dubbo.common.logger.Logger;

 import com.alibaba.dubbo.common.logger.LoggerFactory;

+import com.alibaba.dubbo.common.utils.ExecutorUtil;

 import com.alibaba.dubbo.common.utils.NamedThreadFactory;

 import com.alibaba.dubbo.common.utils.NetUtils;

 import com.alibaba.dubbo.registry.NotifyListener;

@@ -47,7 +48,7 @@
     private static final int RECONNECT_PERIOD_DEFAULT = 3 * 1000;

 

     // Scheduled executor service

-    private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryReconnectTimer", true));

+    private final ScheduledExecutorService reconnectTimer = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryReconnectTimer", true));

 

     // Reconnection timer, regular check connection is available. If unavailable, unlimited reconnection.

     private final ScheduledFuture<?> reconnectFuture;

@@ -59,13 +60,18 @@
 

     private final RegistryService registryService;

 

+    /**

+     * The time in milliseconds the reconnectTimer will wait

+     */

+    private final int reconnectPeriod;

+

     public DubboRegistry(Invoker<RegistryService> registryInvoker, RegistryService registryService) {

         super(registryInvoker.getUrl());

         this.registryInvoker = registryInvoker;

         this.registryService = registryService;

         // Start reconnection timer

-        int reconnectPeriod = registryInvoker.getUrl().getParameter(Constants.REGISTRY_RECONNECT_PERIOD_KEY, RECONNECT_PERIOD_DEFAULT);

-        reconnectFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {

+        this.reconnectPeriod = registryInvoker.getUrl().getParameter(Constants.REGISTRY_RECONNECT_PERIOD_KEY, RECONNECT_PERIOD_DEFAULT);

+        reconnectFuture = reconnectTimer.scheduleWithFixedDelay(new Runnable() {

             @Override

             public void run() {

                 // Check and connect to the registry

@@ -127,6 +133,7 @@
             logger.warn("Failed to cancel reconnect timer", t);

         }

         registryInvoker.destroy();

+        ExecutorUtil.gracefulShutdown(reconnectTimer, reconnectPeriod);

     }

 

     @Override

diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/java/com/alibaba/dubbo/registry/multicast/MulticastRegistry.java b/dubbo-registry/dubbo-registry-multicast/src/main/java/com/alibaba/dubbo/registry/multicast/MulticastRegistry.java
index aa499fc..d392839 100644
--- a/dubbo-registry/dubbo-registry-multicast/src/main/java/com/alibaba/dubbo/registry/multicast/MulticastRegistry.java
+++ b/dubbo-registry/dubbo-registry-multicast/src/main/java/com/alibaba/dubbo/registry/multicast/MulticastRegistry.java
@@ -21,6 +21,7 @@
 import com.alibaba.dubbo.common.logger.Logger;

 import com.alibaba.dubbo.common.logger.LoggerFactory;

 import com.alibaba.dubbo.common.utils.ConcurrentHashSet;

+import com.alibaba.dubbo.common.utils.ExecutorUtil;

 import com.alibaba.dubbo.common.utils.NamedThreadFactory;

 import com.alibaba.dubbo.common.utils.NetUtils;

 import com.alibaba.dubbo.common.utils.StringUtils;

@@ -314,6 +315,7 @@
         } catch (Throwable t) {

             logger.warn(t.getMessage(), t);

         }

+        ExecutorUtil.gracefulShutdown(cleanExecutor, cleanPeriod);

     }

 

     protected void registered(URL url) {

diff --git a/dubbo-registry/dubbo-registry-redis/src/main/java/com/alibaba/dubbo/registry/redis/RedisRegistry.java b/dubbo-registry/dubbo-registry-redis/src/main/java/com/alibaba/dubbo/registry/redis/RedisRegistry.java
index ea4b22c..6a99791 100644
--- a/dubbo-registry/dubbo-registry-redis/src/main/java/com/alibaba/dubbo/registry/redis/RedisRegistry.java
+++ b/dubbo-registry/dubbo-registry-redis/src/main/java/com/alibaba/dubbo/registry/redis/RedisRegistry.java
@@ -20,6 +20,7 @@
 import com.alibaba.dubbo.common.URL;

 import com.alibaba.dubbo.common.logger.Logger;

 import com.alibaba.dubbo.common.logger.LoggerFactory;

+import com.alibaba.dubbo.common.utils.ExecutorUtil;

 import com.alibaba.dubbo.common.utils.NamedThreadFactory;

 import com.alibaba.dubbo.common.utils.StringUtils;

 import com.alibaba.dubbo.common.utils.UrlUtils;

@@ -263,6 +264,7 @@
                 logger.warn("Failed to destroy the redis registry client. registry: " + entry.getKey() + ", cause: " + t.getMessage(), t);

             }

         }

+        ExecutorUtil.gracefulShutdown(expireExecutor, expirePeriod);

     }

 

     @Override

diff --git a/pom.xml b/pom.xml
index 0c94439..c8536ae 100644
--- a/pom.xml
+++ b/pom.xml
@@ -136,6 +136,7 @@
         <module>dubbo-demo</module>
         <module>dubbo-plugin</module>
         <module>dubbo-serialization</module>
+        <module>dubbo-bootstrap</module>
         <module>dependencies-bom</module>
         <module>bom</module>
         <module>all</module>