dubbo Main启动服务浅析
Dubbo 采用全 Spring 配置方式,官方推荐使用内置 Main 启动,并提供了JDK 的 ShutdownHook 优雅停机。这里看的是dubbo 2.6.2版本的代码
贴出dubbo提供的Main启动类
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 }
View Code
分析main方法 main方法的参数传入的是要启动的容器类(Container),dubbo提供了三个实现,分别是SpringContainer、Log4jContainer、LogbackContainer,并在这里配置好
分析
首先判断是否传入了启动容器,如果没有传入则从-D参数数或者dubbo.properties文件中获取key dubbo.container的值,如果都没获取到那么就默认使用默认容器启动,默认容器是哪个呢? 查看 getDefaultExtensionName 方法可以看到调用的loadExtensionClasses()方法中有获取默认容器的代码 如下
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 }
View Code
在这里获取了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的实现
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 }
View Code
可以看到start方法初始化了一个spring应用上下文 其实也就是spring容器,这里如果没有配置 dubbo.spring.config 则会默认扫描 classpath*:META-INF/spring/*.xml 文件来加载spring bean.
stop方法关闭spring容器。
main方法的最后使用了Lock和Condition 并且使主线程等待,其实这里应该就是起到阻塞主线程的作用。关闭的钩子里会唤醒使主线程结束。
over...