diff --git a/all/pom.xml b/all/pom.xml index 09bfe85daf8..edc0c4ad7a1 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -319,6 +319,13 @@ compile true + + com.alibaba + dubbo-bootstrap + ${project.version} + compile + true + com.alibaba hessian-lite @@ -418,6 +425,7 @@ com.alibaba:dubbo-serialization-fst com.alibaba:dubbo-serialization-kryo com.alibaba:dubbo-serialization-jdk + com.alibaba:dubbo-bootstrap diff --git a/dubbo-bootstrap/pom.xml b/dubbo-bootstrap/pom.xml new file mode 100644 index 00000000000..bb2b1a4ecd3 --- /dev/null +++ b/dubbo-bootstrap/pom.xml @@ -0,0 +1,47 @@ + + + + dubbo-parent + com.alibaba + 2.6.2-SNAPSHOT + + 4.0.0 + + dubbo-bootstrap + + + + + com.alibaba + dubbo-config-api + ${project.parent.version} + + + com.alibaba + dubbo-common + ${project.parent.version} + + + com.alibaba + dubbo-registry-api + ${project.parent.version} + + + \ 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 00000000000..8694e48de35 --- /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 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(); + 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 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 6b07cc8035f..d605e130660 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 @@ public static boolean isTerminated(Executor executor) { 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 73fc07da473..8d8d1828832 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 @@ public abstract class AbstractConfig implements Serializable { 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 0e7a52431c5..b15129bf6fb 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 @@ public class ProtocolConfig extends AbstractConfig { // if it's default private Boolean isDefault; - private static final AtomicBoolean destroyed = new AtomicBoolean(false); - public ProtocolConfig() { } @@ -149,27 +145,6 @@ public ProtocolConfig(String name, int port) { 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 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 7e2672b9c30..e5684f38be0 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 @@ public RegistryConfig(String address) { 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 43f3621e287..fb105dd5d4e 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.rpc.Protocol; import org.junit.Test; import org.mockito.Mockito; @@ -32,15 +31,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 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-spring/pom.xml b/dubbo-config/dubbo-config-spring/pom.xml index c7a666080b3..170ac2c1e85 100644 --- a/dubbo-config/dubbo-config-spring/pom.xml +++ b/dubbo-config/dubbo-config-spring/pom.xml @@ -35,14 +35,28 @@ dubbo-config-api ${project.parent.version} + + com.alibaba + dubbo-bootstrap + ${project.parent.version} + org.springframework spring-beans + + org.springframework + spring-web + org.springframework spring-context + + javax.servlet + javax.servlet-api + provided + com.alibaba dubbo-registry-default 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 00000000000..43ee49ded8c --- /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 { + + 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 00000000000..8d0f79db14a --- /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 ced0efad38d..e081957e681 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 @@ public abstract class FailbackRegistry extends AbstractRegistry { private final ConcurrentMap>> failedNotified = new ConcurrentHashMap>>(); + /** + * 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 @@ public void destroy() { } 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 9d5ab70159b..bf8056e6271 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 @@ public class DubboRegistry extends FailbackRegistry { 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 @@ public class DubboRegistry extends FailbackRegistry { private final RegistryService registryService; + /** + * The time in milliseconds the reconnectTimer will wait + */ + private final int reconnectPeriod; + public DubboRegistry(Invoker 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 @@ public void destroy() { 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 aa499fc1bc2..d3928397b3b 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 @@ public void destroy() { } 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 ea4b22cba05..6a997918abf 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 @@ public void destroy() { 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 856eca68ef7..9af7c4c947a 100644 --- a/pom.xml +++ b/pom.xml @@ -133,6 +133,7 @@ dubbo-demo dubbo-plugin dubbo-serialization + dubbo-bootstrap dependencies-bom bom all