资源准备
1.dubbo开发手册下载:
地址:http://dubbo.io/docs/dubbo-dev-book.pdf
2.dubbo源码下载
地址:https://github/alibaba/dubbo/tags
3.导入项目到eclipse或者idea
注意:
- dubbo项目是标准的maven工程,直接以maven项目导入即可
- setting配置可以不配置,默认会从repo.maven拉去依赖
4.构建源代码
mvn package -Dmaven.test.skip=true
源码阅读
启动方式(dubbo项目一般有两种启动方式)
1.Standlone模式,通过Main加载Spring启动
2.使用容器tomcat、jetty、resin等加载Spring启动
两种启动方式,最终都和spring启动过程紧密相关,其实dubbo就是利用spring的扩展性,将自己的启动流程无缝的融合到spring的启动过程中。
启动流程
先看一张完整的启动时序图,如下图:
下载此图
这种大图,看起来会相当苦恼,但是细心的你,如果能坚持看完,那么对于dubbo启动的流程,应该已经心中有数。如果有点懵,没关系,图中我省略了一些模块,并且还有些是父子类方法的相互调用,下面我会用相当的篇幅来解释这张图。
从Main函数到SpringContainer
在dubbo源码的dubbo-container-api模块的src/main/resources/assembly/bin目录下,存放了一些脚本,这些脚本是预留给使用dubbo项目的开发者们启动项目使用的,其中有6个shell脚本:
dump.sh 通过jstack,jinfo,jmap等命令dump内存信息
restart.sh 重启项目
server.sh 集成其他几个命令
start.bat windows上启动项目
start.sh Linux上启动项目
stop.sh 优雅关闭
我们打开start.sh这个脚本,会看到如下的命令:
nohup java $JAVA_OPTS</span> <span class="hljs-variable">$JAVA_MEM_OPTS $JAVA_DEBUG_OPTS</span> <span class="hljs-variable">$JAVA_JMX_OPTS -classpath $CONF_DIR</span>:<span class="hljs-variable">$LIB_JARS com.alibaba.dubbo.container.Main > $STDOUT_FILE 2>&1 &
- 1
这是通过java命令来调用com.alibaba.dubbo.container.Main类中的入口函数main,看看代码
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);
}
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.");
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);
}
try {
LOCK.lock();
STOP.signal();
} finally {
LOCK.unlock();
}
}
}
});
}
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);
}
try {
LOCK.lock();
STOP.await();
} catch (InterruptedException e) {
logger.warn("Dubbo service server stopped, interrupted by other thread!", e);
} finally {
LOCK.unlock();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
重点在String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName()); 这一行代码上
这个loader是一个成员变量,定义如下:
private static final ExtensionLoader loader = ExtensionLoader.getExtensionLoader(Container.class);传入的泛型类是Container,该类的定义为:
@SPI("spring")
public interface Container {
/**
* start.
*/
void start();
/**
* stop.
*/
void stop();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
我们跟loader.getDefaultExtensionName()方法走下去,会进到ExtensionLoader的loadExtensionClasses方法
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if (value != null && (value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if (names.length == 1) cachedDefaultName = names[0];
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadFile(extensionClasses, DUBBO_DIRECTORY);
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
重点在final SPI defaultAnnotation = type.getAnnotation(SPI.class);,type就是泛型类Container,刚刚已经看过这个类,他有@SPI(“spring”)的注释,为此Main类中的main方法的String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());这行代码,返回的是spring,代入原代码中,即是SpringContainer。
SpringContainer
SpringContainer代码很简单
public static final String SPRING_CONFIG = "dubbo.spring.config";
public static final String DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml";
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();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
这里我们只要知道这个configPath是从dubbo.properties配置文件中读取的即可,如果我们没有在类路径下配置这个属性,默认是从DEFAULT_SPRING_CONFIG对应的META-INF里去读取的。
Spring初始化流程
在SpringContainer的start方法中,看到了new了一个ClassPathXmlApplicationContext,这个基本上就回到了spring的初始化流程里面,dubbo只是利用了spring支持对schema自定义的扩展类NamespaceHandler和spring对支持xml解析自定义的扩展类EntityResolver,做了一些扩展,从而在spring初始化的过程中顺带着将dubbo给启动起来了。
如果你对spring的启动过程非常清楚,那么可以直接去看DubboNamespaceHandler和PluggableSchemaResolver,如果你还并不是很清楚spring的这一套流程,建议还是耐心的从这里看下去。
在上面new ClassPathXmlApplicationContext(configPath.split(“[,\s]+”))这行代码,实际调用的是该类的三个参数的构建函数:
public ClassPathXmlApplicationContext(String[] configLocations,
boolean refresh, ApplicationContext parent) throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh)
refresh();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
refresh实际上调用了是其父类AbstractApplicationContext的方法
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
initMessageSource();
initApplicationEventMulticaster();
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
} catch (BeansException ex) {
if (this.logger.isWarnEnabled()) {
this.logger
.warn(new StringBuilder()
.append("Exception encountered during context initialization - cancelling refresh attempt: ")
.append(ex).toString());
}
destroyBeans();
throw ex;
} finally {
resetCommonCaches();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
这段代码是spring初始化流程的精髓,涵盖了spring的ApplicationContext启动的大部分工作,如果你使用过spring,却从来没有看到过这段代码,那一定是你的损失,强烈的建议你一定要像熟悉回家的路一样,熟悉这段代码,摸清楚每个方法的所做的工作。
针对dubbo初始化,我们关注的方法在obtainFreshBeanFactory里面
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (this.logger.isDebugEnabled()) {
this.logger.debug(new StringBuilder().append("Bean factory for ")
.append(getDisplayName()).append(": ").append(beanFactory)
.toString());
}
return beanFactory;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
这个方法里核心调用了refreshBeanFactory和getBeanFactory两个方法,恰巧这两个方法在AbstractApplicationContext里都是abstrast的,
protected abstract void refreshBeanFactory() throws BeansException,
IllegalStateException;
protected abstract void closeBeanFactory();
public abstract ConfigurableListableBeanFactory getBeanFactory()
throws IllegalStateException;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
这两个抽象方法有两个实现类:
1 AbstractRefreshableApplicationContext
2 GenericApplicationContext
那么具体调用的是哪个类的方法呢?,这个需要我们结合ClassPathXmlApplicationContext的继承关系来看。ClassPathXmlApplicationContext继承了AbstractXmlApplicationContext,AbstractXmlApplicationContext继承了AbstractRefreshableConfigApplicationContext,AbstractRefreshableConfigApplicationContext继承了AbstractRefreshableApplicationContext,咦,就是他。进入到AbstractRefreshableApplicationContext类中,可以看到重点在refreshBeanFactory方法中。
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException ex) {
throw new ApplicationContextException(
"I/O error parsing bean definition source for "
+ getDisplayName(), ex);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
refreshBeanFactory调用了loadBeanDefinitions,这个方法又是abstract的,实现他的类就更多了。
AnnotationConfigWebApplicationContext
GroovyWebApplicationContext
XmlWebApplicationContext
AbstractXmlApplicationContext等等,这里进入的又是哪个类呢?还是按照继承关系的上下文来找,ClassPathXmlApplicationContext继承了AbstractXmlApplicationContext,所以进入的是AbstractXmlApplicationContext的loadBeanDefinitions方法
为了避免篇幅太冗长,非核心代码,我将不再贴出来了,大家看的时候,一定自己结合源码。
loadBeanDefinitions方法进一步调用XmlBeanDefinitionReader类的loadBeanDefinitions方法,该方法会调到该类的doLoadBeanDefinitions方法上,代码如下
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException
{
try
{
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex)
{
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex
.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
这个doLoadBeanDefinitions方法太关键了,他的核心代码有两行,这两行做了两个关键的动作
1 doLoadDocument,这个方法用来加载项目中的xml文件,并使用dubbo对应的xsd文件来做校验和检查
2 registerBeanDefinitions,用来解析加载进来的xml文件,针对xml中的每个元素做对应处理,比如spring的要生成一个对应的bean实例放入spring容器中,比如形式的标签要做切面处理,比如标签要生成dubbo模型中的服务端并注册到注册中心等等。
我们知道标签是spring-bean模块的,是spring的aop模块的,甚至还有事务的,的等等,是dubbo自由的,可见spring支持其本身不同模块,还能支持外部的标签,这种强大的扩展性是多么有必要学习啊。 而dubbo的作者正是利用了这个扩展性,无缝的将dubbo和spring对接起来,让使用dubbo的开发者,只要使用过spring,就可以使用dubbo。
针对这两个方法,我会单独抽出来具体的写,我们不仅要学习到dubbo是怎么利用spring的这个特性来实现扩展的,我们更要学会他的本质,对我们今后自己写框架大有帮助。
此处,你大概知道
doLoadDocument
doLoadDocument方法最终会通过spring.schema的内容替换我们项目中配置的spring文件
spring.schema文件中内容
http\://code.alibabatech/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
- 1
从而引用到项目本地的xsd文件对spring或者dubbo的配置做校验和检查,最终将这些配置文件加载到内存中,这里有个重要的类DelegatingEntityResolver,他会根据是dtd结尾还是xsd结尾选择不同的Resolver,BeansDtdResolver还是PluggableSchemaResolver。
registerBeanDefinitions
registerBeanDefinitions方法是对doLoadDocument加载进来的文档做解析,生成对应的处理逻辑。他也会解析到spring.handler文件中的内容
http\://code.alibabatech/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
- 1
DubboNamespaceHandler类的代码
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));
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
该类的init方法会被调用,从而会在内存中维护一份,当解析到,等等都用DubboBeanDefinitionParser类去处理,
DubboBeanDefinitionParser的parse方法针对不同类型会做不同的处理逻辑
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
String id = element.getAttribute("id");
if ((id == null || id.length() == 0) && required) {
String generatedBeanName = element.getAttribute("name");
if (generatedBeanName == null || generatedBeanName.length() == 0) {
if (ProtocolConfig.class.equals(beanClass)) {
generatedBeanName = "dubbo";
} else {
generatedBeanName = element.getAttribute("interface");
}
}
if (generatedBeanName == null || generatedBeanName.length() == 0) {
generatedBeanName = beanClass.getName();
}
id = generatedBeanName;
int counter = 2;
while (parserContext.getRegistry().containsBeanDefinition(id)) {
id = generatedBeanName + (counter++);
}
}
if (id != null && id.length() > 0) {
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate spring bean id " + id);
}
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
beanDefinition.getPropertyValues().addPropertyValue("id", id);
}
if (ProtocolConfig.class.equals(beanClass)) {
for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
if (property != null) {
Object value = property.getValue();
if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
}
}
}
} else if (ServiceBean.class.equals(beanClass)) {
String className = element.getAttribute("class");
if (className != null && className.length() > 0) {
RootBeanDefinition classDefinition = new RootBeanDefinition();
classDefinition.setBeanClass(ReflectUtils.forName(className));
classDefinition.setLazyInit(false);
parseProperties(element.getChildNodes(), classDefinition);
beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
}
} else if (ProviderConfig.class.equals(beanClass)) {
parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
} else if (ConsumerConfig.class.equals(beanClass)) {
parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
}
Set<String> props = new HashSet<String>();
ManagedMap parameters = null;
for (Method setter : beanClass.getMethods()) {
String name = setter.getName();
if (name.length() > 3 && name.startsWith("set")
&& Modifier.isPublic(setter.getModifiers())
&& setter.getParameterTypes().length == 1) {
Class<?> type = setter.getParameterTypes()[0];
String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), "-");
props.add(property);
Method getter = null;
try {
getter = beanClass.getMethod("get" + name.substring(3), new Class<?>[0]);
} catch (NoSuchMethodException e) {
try {
getter = beanClass.getMethod("is" + name.substring(3), new Class<?>[0]);
} catch (NoSuchMethodException e2) {
}
}
if (getter == null
|| !Modifier.isPublic(getter.getModifiers())
|| !type.equals(getter.getReturnType())) {
continue;
}
if ("parameters".equals(property)) {
parameters = parseParameters(element.getChildNodes(), beanDefinition);
} else if ("methods".equals(property)) {
parseMethods(id, element.getChildNodes(), beanDefinition, parserContext);
} else if ("arguments".equals(property)) {
parseArguments(id, element.getChildNodes(), beanDefinition, parserContext);
} else {
String value = element.getAttribute(property);
...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
这个类中就引出来dubbo的四大核心:registry,provider,consumer和monitor。
这些核心的组件的执行过程,也会在后面分别阐述。
到这里,相信你对dubbo的初始化过程有了一定的理解,希望本文对你有帮助。
<link rel="stylesheet" href="http://csdnimg/release/phoenix/production/markdown_views-68a8aad09e.css">
</div>
更多推荐
龙哥dubbo源码阅读实践-源码入口(第一章)
发布评论