本文只讨论dubbo是这么启动的,以及粗略探讨dubbo启动的类层次结构图,dubbo的配置细节功能请参考dubbo官方文档,不做详细解释
1.dubbo是怎么启动的
Dubbo是这么启动的,通过了解dubbo官方文档和阅读dubbo源码可以发现,dubbo启动的方式有两种,一种是借助spring启动一种是直接new对象启动
首先我们看看dubbo通过spring方式进行启动,dubbo有一个container模块,负责管理dubbo加载启动,日志管理这个模块一共分为5个子模块:dubbo-container-api,dubbo-container-jetty,dubbo-container-log4j,dubbo-container-logback,dubbo-container-spring
dubbo-container-log4j
自动配置log4j的配置,在多进程启动时,自动给日志文件按进程分目录,如果启动的时候启动这个模块,用户不能控制log4j的配置,不灵活
dubbo-container-logback
dubbo-container-jetty
启动一个内嵌Jetty,用于汇报状态,大量访问页面时,会影响服务器的线程和内存
dubbo-container-spring
自动加载META-INF/spring目录下的所有Spring配置,这个是我个人认为比较推荐的启动dubbo的方式,dubbo通过SPI的机制,默认的会开启spring这个容器的加载dubbo配置,完成初始化操作。
@SPI("spring")
public interface Container {
/**
* start.
*/
void start();
/**
* stop.
*/
void stop();
}
从Container这个接口可以看出spring作为默认的dubbo加载器,dubbo为我们提供了一个模块用来启动dubbo服务的,这个类的名称是Main位于dubbo-container-api包里,dubbo为用户提供服务的模块往往以api为结尾,我们可以看一下Main这个类里到底都做了什么
public class Main {
public static final String CONTAINER_KEY = "dubbo.container";
public static final String SHUTDOWN_HOOK_KEY = "dubbo.shutdown.hook";
private static final Logger logger = LoggerFactory.getLogger(Main.class);
private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
private static volatile boolean running = true;
public static void main(String[] args) {
try {
if (args == null || args.length == 0) {
// 如果启动不传递参数获取默认配置
String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
args = Constants.COMMA_SPLIT_PATTERN.split(config);
}
// 取到所有的启动要初始化的所有工作主要指log4j,logbac,jetty,spring
final List<Container> containers = new ArrayList<Container>();
for (int i = 0; i < args.length; i ++) {
containers.add(loader.getExtension(args[i]));
}
logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");
// 如果容器里已经启动过了就停止,优雅的停止java线程,俗称钩子,这里放掉主线程停止程序
if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
for (Container container : containers) {
try {
container.stop();
logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
synchronized (Main.class) {
running = false;
Main.class.notify();
}
}
}
});
}
// 启动容器链
for (Container container : containers) {
container.start();
logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
}
System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Dubbo service server started!");
} catch (RuntimeException e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
System.exit(1);
}
// 让主线程一直等待,等待钩子释放停止
synchronized (Main.class) {
while (running) {
try {
Main.class.wait();
} catch (Throwable e) {
}
}
}
}
}
这里的容器链就是指dubbo-container这个项目下的dubbo-container-jetty,dubbo-container-log4j,dubbo-container-logback,dubbo-container-spring,通过启动Main类的main方法的参数设置
public class DubboMain {
private static final Logger logger = LoggerFactory.getLogger(DubboMain.class);
public static void main(String[] args){
com.alibaba.dubbo.container.Main.main(new String[]{"spring","log4j"});
}
}
通过main方法传递参数必须在META-INF.dubbo.internal文件夹配置,这样spring在启动的时候才能加载的到
Dubbo容器的log4j,logback,jetty这里不细描述,我们主要看看dubbo的container的spring模块代码
public class SpringContainer implements Container {
private static final Logger logger = LoggerFactory.getLogger(SpringContainer.class);
public static final String SPRING_CONFIG = "dubbo.spring.config";
public static final String DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml";
static ClassPathXmlApplicationContext context;
public static ClassPathXmlApplicationContext getContext() {
return context;
}
public void start() {
String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
if (configPath == null || configPath.length() == 0) {
configPath = DEFAULT_SPRING_CONFIG;
}
context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"));
context.start();
}
public void stop() {
try {
if (context != null) {
context.stop();
context.close();
context = null;
}
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
}
Spring模块的代码也就是用spring加载dubbo,dubbo有一个默认的方式spring配置文件的地方,默认启动Main类的main方法可以默认加载这个文件夹下所有的xml文件,然而问题来了,spring是这么加载dubbo了,这里就是dubbo的精髓了,dubbo的扩展机制
Dubbo定义了DubboBeanDefinitionParser类用来解析dubbo自定义的spring标签,自定义标签要继承spring的BeanDefinitionParser实现parse方法解析自定标签的参数。
找到dubbo-config\dubbo-config-spring\src\main\resources\META-INF\spring.handlers文件。找到负责具体解析dubbo标签的handler。
http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
DubboNamespaceHandler类将Dubbo自定义标签解析的bean注册到spring中
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
}
从代码中可以看到dubbo自定义标签元素,也可以知道dubbo就是这时候初始化的,那么问题来了是哪个类加载的时候初始化的了看下图
Dubbo服务端是在ServiceBean中初始化的,dubbo的客户端是在ReferenceBean中初始化的。
本文只讨论dubbo是这么启动的,以及粗略探讨dubbo启动的类层次结构图,dubbo的配置细节功能请参考dubbo官方文档,不做详细解释
1.dubbo是怎么启动的
Dubbo是这么启动的,通过了解dubbo官方文档和阅读dubbo源码可以发现,dubbo启动的方式有两种,一种是借助spring启动一种是直接new对象启动
首先我们看看dubbo通过spring方式进行启动,dubbo有一个container模块,负责管理dubbo加载启动,日志管理这个模块一共分为5个子模块:dubbo-container-api,dubbo-container-jetty,dubbo-container-log4j,dubbo-container-logback,dubbo-container-spring
dubbo-container-log4j
自动配置log4j的配置,在多进程启动时,自动给日志文件按进程分目录,如果启动的时候启动这个模块,用户不能控制log4j的配置,不灵活
dubbo-container-logback
dubbo-container-jetty
启动一个内嵌Jetty,用于汇报状态,大量访问页面时,会影响服务器的线程和内存
dubbo-container-spring
自动加载META-INF/spring目录下的所有Spring配置,这个是我个人认为比较推荐的启动dubbo的方式,dubbo通过SPI的机制,默认的会开启spring这个容器的加载dubbo配置,完成初始化操作。
@SPI("spring")
public interface Container {
/**
* start.
*/
void start();
/**
* stop.
*/
void stop();
}
从Container这个接口可以看出spring作为默认的dubbo加载器,dubbo为我们提供了一个模块用来启动dubbo服务的,这个类的名称是Main位于dubbo-container-api包里,dubbo为用户提供服务的模块往往以api为结尾,我们可以看一下Main这个类里到底都做了什么
public class Main {
public static final String CONTAINER_KEY = "dubbo.container";
public static final String SHUTDOWN_HOOK_KEY = "dubbo.shutdown.hook";
private static final Logger logger = LoggerFactory.getLogger(Main.class);
private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
private static volatile boolean running = true;
public static void main(String[] args) {
try {
if (args == null || args.length == 0) {
// 如果启动不传递参数获取默认配置
String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
args = Constants.COMMA_SPLIT_PATTERN.split(config);
}
// 取到所有的启动要初始化的所有工作主要指log4j,logbac,jetty,spring
final List<Container> containers = new ArrayList<Container>();
for (int i = 0; i < args.length; i ++) {
containers.add(loader.getExtension(args[i]));
}
logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");
// 如果容器里已经启动过了就停止,优雅的停止java线程,俗称钩子,这里放掉主线程停止程序
if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
for (Container container : containers) {
try {
container.stop();
logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
synchronized (Main.class) {
running = false;
Main.class.notify();
}
}
}
});
}
// 启动容器链
for (Container container : containers) {
container.start();
logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
}
System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Dubbo service server started!");
} catch (RuntimeException e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
System.exit(1);
}
// 让主线程一直等待,等待钩子释放停止
synchronized (Main.class) {
while (running) {
try {
Main.class.wait();
} catch (Throwable e) {
}
}
}
}
}
这里的容器链就是指dubbo-container这个项目下的dubbo-container-jetty,dubbo-container-log4j,dubbo-container-logback,dubbo-container-spring,通过启动Main类的main方法的参数设置
public class DubboMain {
private static final Logger logger = LoggerFactory.getLogger(DubboMain.class);
public static void main(String[] args){
com.alibaba.dubbo.container.Main.main(new String[]{"spring","log4j"});
}
}
通过main方法传递参数必须在META-INF.dubbo.internal文件夹配置,这样spring在启动的时候才能加载的到
Dubbo容器的log4j,logback,jetty这里不细描述,我们主要看看dubbo的container的spring模块代码
public class SpringContainer implements Container {
private static final Logger logger = LoggerFactory.getLogger(SpringContainer.class);
public static final String SPRING_CONFIG = "dubbo.spring.config";
public static final String DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml";
static ClassPathXmlApplicationContext context;
public static ClassPathXmlApplicationContext getContext() {
return context;
}
public void start() {
String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
if (configPath == null || configPath.length() == 0) {
configPath = DEFAULT_SPRING_CONFIG;
}
context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"));
context.start();
}
public void stop() {
try {
if (context != null) {
context.stop();
context.close();
context = null;
}
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
}
Spring模块的代码也就是用spring加载dubbo,dubbo有一个默认的方式spring配置文件的地方,默认启动Main类的main方法可以默认加载这个文件夹下所有的xml文件,然而问题来了,spring是这么加载dubbo了,这里就是dubbo的精髓了,dubbo的扩展机制
Dubbo定义了DubboBeanDefinitionParser类用来解析dubbo自定义的spring标签,自定义标签要继承spring的BeanDefinitionParser实现parse方法解析自定标签的参数。
找到dubbo-config\dubbo-config-spring\src\main\resources\META-INF\spring.handlers文件。找到负责具体解析dubbo标签的handler。
http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
DubboNamespaceHandler类将Dubbo自定义标签解析的bean注册到spring中,
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
}
从代码中可以看到dubbo自定义标签元素,也可以知道dubbo就是这时候初始化的,那么问题来了是哪个类加载的时候初始化的了看下图
Dubbo客户端是在ServiceBean中初始化的,dubbo的客户端是在ReferenceBean中初始化的,下图是ServiceBean的类图层次结构
可以看出ServiceBean实现了
InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware 这些接口其中InitializingBean,ApplicationListener两个接口非常的重要,ServiceBean实现了InitializingBean afterPropertiesSet方法在ServiceBean被初始化的时候加载启动dubbo服务端,有就是dubbo服务启动的入口,另外ServiceBean还实现了ApplicationListener的onApplicationEvent在spring refresh的时候触发ContextRefreshedEvent事件加载启动dubbo服务,ReferenceBean实现原理和ServiceBean类似,ReferenceBean的类层次结构图如下:
Dubbo客户端没有实现ApplicationListener接口因此在spring refresh的时候不会重现加载
下面给大家介绍下通过最原始的方式启动spring
通过new的方式启动dubbo服务端
public class ProviderStart {
public static void main(String[] args) {
// 当前应用配置
ApplicationConfig application = new ApplicationConfig();
application.setName("xxx");
// 连接注册中心配置
RegistryConfig registry = new RegistryConfig();
registry.setAddress("10.20.130.230:9090");
registry.setUsername("aaa");
registry.setPassword("bbb");
// 服务提供者协议配置
ProtocolConfig protocol = new ProtocolConfig();
protocol.setName("dubbo");
protocol.setPort(12345);
protocol.setThreads(200);
// 注意:ServiceConfig为重对象,内部封装了与注册中心的连接,以及开启服务端口
// 服务提供者暴露服务配置
ServiceConfig<XxxService> service = new ServiceConfig<XxxService>(); // 此实例很重,封装了与注册中心的连接,请自行缓存,否则可能造成内存和连接泄漏
service.setApplication(application);
service.setRegistry(registry); // 多个注册中心可以用setRegistries()
service.setProtocol(protocol); // 多个协议可以用setProtocols()
service.setInterface(XxxService.class);
XxxServiceImpl xxxService = new XxxServiceImpl();
service.setRef(xxxService);
service.setVersion("1.0.0");
// 暴露及注册服务
service.export();
}
通过new的方式启动dubbo客户端
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
/**
* Created by xuehan on 2017/2/23.
*/
public class ConsumerStart {
public static void main(String[] args) {
// 当前应用配置
ApplicationConfig application = new ApplicationConfig();
application.setName("yyy");
// 连接注册中心配置
RegistryConfig registry = new RegistryConfig();
registry.setAddress("10.20.130.230:9090");
registry.setUsername("aaa");
registry.setPassword("bbb");
// 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接
// 引用远程服务
ReferenceConfig<XxxService> reference = new ReferenceConfig<XxxService>();
// 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
reference.setApplication(application);
reference.setRegistry(registry); // 多个注册中心可以用setRegistries()
reference.setInterface(XxxService.class);
reference.setVersion("1.0.0");
// 和本地bean一样使用xxxService
XxxService xxxService = reference.get(); // 注意:此代理对象内部封装了所有通讯细节,对象较重,请缓存复用
}
}