Dubbo 采用全 Spring 配置方式,官方推荐使用内置 Main 启动,并提供了JDK 的 ShutdownHook 优雅停机。这里看的是dubbo 2.6.2版本的代码
贴出dubbo提供的Main启动类
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package com.alibaba.dubbo.container; 18 19 import com.alibaba.dubbo.common.Constants; 20 import com.alibaba.dubbo.common.extension.ExtensionLoader; 21 import com.alibaba.dubbo.common.logger.Logger; 22 import com.alibaba.dubbo.common.logger.LoggerFactory; 23 import com.alibaba.dubbo.common.utils.ConfigUtils; 24 25 import java.text.SimpleDateFormat; 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 import java.util.Date; 29 import java.util.List; 30 import java.util.concurrent.locks.Condition; 31 import java.util.concurrent.locks.ReentrantLock; 32 33 /** 34 * Main. (API, Static, ThreadSafe) 35 */ 36 public class Main { 37 38 public static final String CONTAINER_KEY = "dubbo.container"; 39 40 public static final String SHUTDOWN_HOOK_KEY = "dubbo.shutdown.hook"; 41 42 private static final Logger logger = LoggerFactory.getLogger(Main.class); 43 44 private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class); 45 46 private static final ReentrantLock LOCK = new ReentrantLock(); 47 48 private static final Condition STOP = LOCK.newCondition(); 49 50 public static void main(String[] args) { 51 try { 52 if (args == null || args.length == 0) { 53 String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName()); 54 args = Constants.COMMA_SPLIT_PATTERN.split(config); 55 } 56 57 final List<Container> containers = new ArrayList<Container>(); 58 for (int i = 0; i < args.length; i++) { 59 containers.add(loader.getExtension(args[i])); 60 } 61 logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce."); 62 63 if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) { 64 Runtime.getRuntime().addShutdownHook(new Thread() { 65 @Override 66 public void run() { 67 for (Container container : containers) { 68 try { 69 container.stop(); 70 logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!"); 71 } catch (Throwable t) { 72 logger.error(t.getMessage(), t); 73 } 74 try { 75 LOCK.lock(); 76 STOP.signal(); 77 } finally { 78 LOCK.unlock(); 79 } 80 } 81 } 82 }); 83 } 84 85 for (Container container : containers) { 86 container.start(); 87 logger.info("Dubbo " + container.getClass().getSimpleName() + " started!"); 88 } 89 System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Dubbo service server started!"); 90 } catch (RuntimeException e) { 91 e.printStackTrace(); 92 logger.error(e.getMessage(), e); 93 System.exit(1); 94 } 95 try { 96 LOCK.lock(); 97 STOP.await(); 98 } catch (InterruptedException e) { 99 logger.warn("Dubbo service server stopped, interrupted by other thread!", e); 100 } finally { 101 LOCK.unlock(); 102 } 103 } 104 105 }
分析main方法 main方法的参数传入的是要启动的容器类(Container),dubbo提供了三个实现,分别是SpringContainer、Log4jContainer、LogbackContainer,并在这里配置好
分析
首先判断是否传入了启动容器,如果没有传入则从-D参数数或者dubbo.properties文件中获取key dubbo.container的值,如果都没获取到那么就默认使用默认容器启动,默认容器是哪个呢? 查看 getDefaultExtensionName 方法可以看到调用的loadExtensionClasses()方法中有获取默认容器的代码 如下
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 // synchronized in getExtensionClasses 2 private Map<String, Class<?>> loadExtensionClasses() { 3 final SPI defaultAnnotation = type.getAnnotation(SPI.class); 4 if (defaultAnnotation != null) { 5 String value = defaultAnnotation.value(); 6 if ((value = value.trim()).length() > 0) { 7 String[] names = NAME_SEPARATOR.split(value); 8 if (names.length > 1) { 9 throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() 10 + ": " + Arrays.toString(names)); 11 } 12 if (names.length == 1) cachedDefaultName = names[0]; 13 } 14 } 15 16 Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); 17 loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); 18 loadDirectory(extensionClasses, DUBBO_DIRECTORY); 19 loadDirectory(extensionClasses, SERVICES_DIRECTORY); 20 return extensionClasses; 21 }
在这里获取了Container接口上的注解SPI的value,回去看Container接口 可以看到接口上有@SPI("spring")注解,value就是spring 所以这里会默认使用spring容器启动
然后继续往下看
会循环获取到的启动类列表获取启动类实例,就是在META-INF/dubbo.internal/{接口名称} 对应的文件会有键值对,就是对应的实现类,详情请看 loader.getExtension 这里就不展开。
继续往下, 如果能获取到 dubbo.shutdown.hook 配置并且是true 则加入 addShutdownHook 钩子 ,这个方法会在程序正常停止时候被调用,dubbo就是使用这个实现的优雅停机,看里面的代码,循环拿到启动的容器类调用stop() 方法 来停止容器,这里还用到了锁通知,用来唤醒Main的主程序。
再往下 是启动类的启动方法 , 会循环获取到的容器实例并调用start()方法,各个实现类的启动方法可以查看实现类的实现代码,这里贴出SpringContainer的实现
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package com.alibaba.dubbo.container.spring; 18 19 import com.alibaba.dubbo.common.logger.Logger; 20 import com.alibaba.dubbo.common.logger.LoggerFactory; 21 import com.alibaba.dubbo.common.utils.ConfigUtils; 22 import com.alibaba.dubbo.container.Container; 23 24 import org.springframework.context.support.ClassPathXmlApplicationContext; 25 26 /** 27 * SpringContainer. (SPI, Singleton, ThreadSafe) 28 */ 29 public class SpringContainer implements Container { 30 31 public static final String SPRING_CONFIG = "dubbo.spring.config"; 32 public static final String DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml"; 33 private static final Logger logger = LoggerFactory.getLogger(SpringContainer.class); 34 static ClassPathXmlApplicationContext context; 35 36 public static ClassPathXmlApplicationContext getContext() { 37 return context; 38 } 39 40 @Override 41 public void start() { 42 String configPath = ConfigUtils.getProperty(SPRING_CONFIG); 43 if (configPath == null || configPath.length() == 0) { 44 configPath = DEFAULT_SPRING_CONFIG; 45 } 46 context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+")); 47 context.start(); 48 } 49 50 @Override 51 public void stop() { 52 try { 53 if (context != null) { 54 context.stop(); 55 context.close(); 56 context = null; 57 } 58 } catch (Throwable e) { 59 logger.error(e.getMessage(), e); 60 } 61 } 62 63 }
可以看到start方法初始化了一个spring应用上下文 其实也就是spring容器,这里如果没有配置 dubbo.spring.config 则会默认扫描 classpath*:META-INF/spring/*.xml 文件来加载spring bean.
stop方法关闭spring容器。
main方法的最后使用了Lock和Condition 并且使主线程等待,其实这里应该就是起到阻塞主线程的作用。关闭的钩子里会唤醒使主线程结束。
over...