今天看了一些博文,都是关于dubbo源码解析方面的。觉得有必要记一下。
问题1:spring 如何注入dubbo 的?或者说怎么集成dubbo 的,或者说 dubbo启动时怎么启动spring的?
1、首先想要实现 在spring 中 发挥某框架的功能,就必须将该框架注入到springBean 中。
2、dubbo 中 dubbo-container-spring 模块,类 spirngContainer 里面的start方法实现了这功能。
3、上述的start方法 由dubbo 的Main方法调用。。。
4、start方法执行时 会调用spring配置文件路径。如果没有配置Spring xml文件的路径,会默认加载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();
}
问题2:spring 是如何识别dubbo 标签的?
1、首先要清楚,spring 原生的标签 比如一些注解,aop的。bean 的。为什么都能认识?
是因为在命名空间中指定了对应的标签比如aop的 xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop"指定这两个,就可以使用aop的相关标签。
2、dubbo 也是一样(需要在spring配置文件中添加dubbo的命名空间)dubbo-config-spring包下的META-INF目录下spring.handlers和spring.schemas文件两个文件就是来处理命名空间和xsd。
spring 在识别命名空间时,使用的是NamespaceHandlerSupport抽象类和NamespaceHandler接口
而dubbo 的DubboNamespaceHandler 继承了NamespaceHandlerSupport,会在初始化时,加载并识别dubbo标签,启动dubbo的Main 方法时就加载了上述两个文件进而让spring知道自定义了NamespaceHandlerSupport;
DubboNamespaceHandler源码
/*
* Copyright 1999-2011 Alibaba Group.
*
* Licensed 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.schema;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import com.alibaba.dubbo.common.Version;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import com.alibaba.dubbo.config.ModuleConfig;
import com.alibaba.dubbo.config.MonitorConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.AnnotationBean;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.config.spring.ServiceBean;
/**
* DubboNamespaceHandler
*
* @author william.liangf
* @export
*/
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));
}
}
进入registerBeanDefinitionParser的方法,可以看到他是NamespaceHandlerSupport 的方法。
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
第一个参数 是 元素名称,就是告诉spring 我要解析注入这个标签中的class 第二个参数DubboBeanDefinitionParser 实现了BeanDefinitionParser接口,而BeanDefinitionParser接口 是spring 将xml标签解析成BeanDefinition对象的接口,所以DubboBeanDefinitionParser 就是将spring中dubbo 的标签转换成BeanDefinition对象,其元素名称以后还要给Invoker 对象调用服务端方法使用;
*如果是dubbo:protocol标签,dubboh还会检查所有已经包含protocol属性的BeanDefinition且protocol属性对应的值是ProtocolConfig对象的bean,将其属性的protocol值设置成当前的bean引用:
definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
*如果是dubbo服务提供者的dubbo:service标签,则还会设置ref属性为对应接口class的实现类bean:
beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
问题3:怎么注入的呢?
1、其实就是上面的DubboBeanDefinitionParser类,进行操作的,该类将dubbo:reference这种标签解析成spring能够管理的BeanDefinition对象(我喜欢叫容器,这货是一个map)
2、仔细看dubbo消费者 注入类型是ReferenceBean。其父类是ReferenceConfig 他的属性:
private static finalProxyFactoryproxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
//接口类型
private String interfaceName;
private Class<?> interfaceClass;
//接口代理类引用
private transient volatileTref;
private transient volatileInvoker<?>invoker;
可知,dubbo 将标签转化为对象的时候使用的是动态代理,这点与spring 的IOC大不一样,,IOC是反射原理。
ReferenceConfig实现了FactoryBean接口 调用get方法返回代理对象,其子类ReferenceBean 再调用getObject(就是FactoryBean的方法),获取代理对象。
那么 dubbo使用的 是jdk 还是cgLib呢?
cgLib没有在使用的行列,不知道原因。但,jdk 和Javassist 两种却被使用,默认使用Javassist动态代理模式(据说效率很高)。可自定义使用jdk代理模式(因这种动态代理仅限接口,使用范围受限,且效率不高,很少用)。
以上是小弟 从网上看到并实际操作实践+自己一些理解的(已经看了不少,但不敢写。怕理解有误)。先记下来。有不妥当的地方后期完善。
2018-11-22补充:
1、在注入和生成动态代理的时候使用Javassist 其实生成的是一个字节码文件,保存在jvm缓存中,等待调用。
简要说明。对于提供者。就是spring 将dubbo标签通过ServiceConfig 解析成了ServiceBean,并生成好了一个invoker,该invoker放在exporter对象下,
exporter对象又放在exporters对象下,然后创建的nettyServer 放在了protocol(单例的)对象下的一个容器里,这样serviceBean 里面就有了可用的invoker(接收consumer 请求,执行自己的invoke方法,之后反射出业务类impl,执行业务代码,将结果通过netty通道返回),nettyServer,等待被调用。
注意:服务提供者在创建出invoker 后 执行业务类的时候使用的是反射技术。不是代理。消费者在执行时,使用代理对象执行invoke方法 才能实现远程调用。