服务发布
SpringContainer
启动之后就开始进入Spring
的生命周期管理了,依赖Spring提供的各种扩展机制,比如spring
会自动查找dubbo.jar!/META-INF/spring.handlers
文件的内容,实现命名空间自定义扩展
DubboNamespaceHandler
Dubbo的配置文件中有很多xml节点比如:
如果我们要自己写标签同时整合到Spring
,首先要查看官方文档找到Xml
扩展接口,Dubbo
就是通过DubboNamespaceHandler
继承Spring
提供的扩展接口NamespaceHandlerSupport
,允许Dubbo
自定义Xml
标签向Spring
容器注册BeanDefinition
,而Xml
文件经过DubboBeanDefinitionParser.parse()
之后又具备了XML
向Java
代码转换的能力,具体的配置参数如下所示,后面有专题章节对配置以及转换过程做详细介绍,这里只需要了解下面的xml等同于手写Dubbo Api
代码即可。
Consumer配置
Provider配置
DubboNamespaceHandler
只有一个init()
方法,从中观察到,
和
标签注册的分别是ServiceBean
和ReferenceBean
对象,而其他的元素注册的都是Config
对象,表明
和
被作者赋予高于普通配置的行为和地位(皆继承AbstractConfig
)
ServiceBean和ReferenceBean是Dubbo与Spring容器整合的核心方法
每一个elementName都是xml中的标签,有其相应属性,具有可配置的能力
package org.apache.dubbo.config.spring.schema;
class DubboNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// 代码太长放不下,为了格式漂亮定义下变量
// register=registerBeanDefinitionParser
// BeanDefParser=DubboBeanDefinitionParser
register("application", new BeanDefParser(ApplicationConfig.class, true));
register("module", new BeanDefParser(ModuleConfig.class, true));
register("registry", new BeanDefParser(RegistryConfig.class, true));
register("config-center", new BeanDefParser(ConfigCenterBean.class, true));
register("metadata-report", new BeanDefParser(MetadataReportConfig.class, true));
register("monitor", new BeanDefParser(MonitorConfig.class, true));
register("metrics", new BeanDefParser(MetricsConfig.class, true));
register("provider", new BeanDefParser(ProviderConfig.class, true));
register("consumer", new BeanDefParser(ConsumerConfig.class, true));
register("protocol", new BeanDefParser(ProtocolConfig.class, true));
// 名字是以Bean结尾的区别于上面的Config
register("service", new BeanDefParser(ServiceBean.class, true));
register("reference", new BeanDefParser(ReferenceBean.class, false));
register("annotation", new BeanDefParser());
}
}
ServiceBean
Dubbo
要求
标签由Provider
端定义,下面开始介绍Provider
的定义入口ServiceBean
。其泛型由interface="org.apache.dubbo.demo.DemoService"
字段指定。
package org.apache.dubbo.config.spring;
public class ServiceBean extends ServiceConfig
implements InitializingBean, DisposableBean, BeanNameAware,
ApplicationContextAware, ApplicationListener,
ApplicationEventPublisherAware
ServiceBean
实现了好多接口,看下各自的作用
接口 | 方法 | 作用 |
---|---|---|
InitializingBean | afterPropertiesSet() | bean启动扩展 |
DisposableBean | destroy() | bean销毁扩展 |
ApplicationContextAware | setApplicationContext(ApplicationContext) | 注入spring环境 |
ApplicationEventPublisherAware | setApplicationEventPublisher(Publisher) | 注入事件发布 |
BeanNameAware | setBeanName(String name) | 注入当前bean的名字 |
ServiceConfig | export(),doExportUrls() | 生成Protocol |
ServiceBean
实现了Spring的InitializingBean
扩展,容器启动时会自动执行afterPropertiesSet()
ServiceBean
实现了Spring的DisposableBean
扩展,Bean销毁时会自动执行destroy()
ServiceBean
实现了Spring的ApplicationContextAware
扩展,应用启动完毕会自动注入ApplicationContext
上下文,方便Dubbo从全局获取Bean对象ServiceBean
实现了Spring的ApplicationEventPublisherAware
扩展,具有发布事件的能力ServiceBean
实现了Spring的BeanNameAware
扩展,知道自己的BeanName
-
ServiceBean
继承了ServiceConfig,主要目的是根据Config生成dubbo协议dubbo://host/com.foo.FooService?route=route&cluster=&version=1.0.0
Spring
容器启动自动调用afterPropertiesSet()
,时序上先于onApplicationEvent()
ServiceBean.afterPropertiesSet():
afterPropertiesSet():代码很长分两部分说明
1.标签优先级设定:根据
BeanDefinition
标签设定优先级2.判断当前Spring版本是否支持
ApplicationListener()
,否则延迟导出直到Spring容器启动完毕
// 标签优先级设定伪代码
if exist( ) {find( ); setProviders(ProviderBean)}
if exist( ) { setApplication(ApplicationBean)}
if exist( ) {super.setRegistries(ModuleBean)}
if exist( ) {super.setProtocol(ProtocolBean)}
...
// 以上设置完毕判断spring是否支持事件监听,低版本兼容
if !supportedApplicationListener {export();}
Spring
容器启动完毕后监听ContextRefreshedEvent
执行onApplicationEvent()
ServiceBean.onApplicationEvent():
onApplicationEvent():根据服务export发布状态,判断是否export()
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
ServiceBean.export():
export方法做了两件事
- 调用父类的export(),父类为ServiceConfig,
ServiceBean#export()
->ServiceConfig#export()
- 发布ExportEvent
public void export() {
// Step1
super.export();
// Step2
publishExportEvent();
}
super.export():
Step1:super.export() => ServiceConfig#export()
- 配置检查和更新事先由afterPropertiesSet()配置完毕
- 判断是否需要导出,不导出就是本地方法
- 若延迟导出则委托delayExecutor执行导出
- 否则调用 doExport()
package org.apache.dubbo.config;
super class ServiceConfig
public synchronized void export() {
// 配置检查和更新 事先由afterPropertiesSet配置完毕
checkAndUpdateSubConfigs();
if (!shouldExport()) { // 标签属性export boolean
return;
}
if (shouldDelay()) {// 标签属性delay boolean
// 延时export
delayExecutor.schedule(this::doExport, getDelay(), MILLISECONDS);
} else {
doExport();
}
}
super.doExportUrls():
doExport()
在简单开关验证及设置path=interfaceName
之后执行doExportUrls()
doExportUrls():
获取注册中心
URL
地址,由上面标签定义,此处已知配置为
multicast
获取协议列表,一般只配置一个
protocol
,由标签定义
开始循环协议列表
获取
pathKey
:com.kaiyuan.qyd.topic.dubbo.basic.DemoService
创建
ProviderModel
实体对象并加入缓存,interfaceClass是接口类,ref是接口实现类执行核心逻辑
doExportUrlsFor1Protocol()
package org.apache.dubbo.config;
super class ServiceConfig
private void doExportUrls() {
// registry://host/org.xxx.registry.RegistryService?application=demo-provider®istry=multicast
List registryURLs = loadRegistries(true);
//
for (ProtocolConfig protocolConfig : protocols) {
// pathKey = com.kaiyuan.qyd.topic.dubbo.basic.DemoService
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
// interfaceClass是接口类,ref是接口实现类
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
// 放入缓存PROVIDED_SERVICES
ApplicationModel.initProviderModel(pathKey, providerModel);
// 核心代码
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
AbstractInterfaceConfig.loadRegistries():
这个方法来自抽象父类,主要逻辑为:
1.参数验证,代码中省略
2.根据
BeanDefinition
配置,获取parameters
放到map
中3.根据
map
中的参数生成注册中心URL
地址,翻译如下:
registry://host/org.xxx.RegistryService?application=demo-provider®istry=multicast
protected List loadRegistries(boolean provider) {
List registryList = new ArrayList();
// 省略参数验证
if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map map = new HashMap();
// 把中的parameter放到map中,比如name
appendParameters(map, application);
// 把中的parameter反射放到map中,比如host
appendParameters(map, config);
map.put(PATH_KEY, RegistryService.class.getName());
// 追加一些运行时参数比如时间戳,版本号,pid
appendRuntimeParameters(map);
List urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
url = URLBuilder.from(url)
.addParameter(REGISTRY_KEY, url.getProtocol())
.setProtocol(REGISTRY_PROTOCOL)
.build();
// registry://host/org.xxx.RegistryService?application=demo-provider®istry=multicast
if ((provider && url.getParameter(REGISTER_KEY, true))
|| (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
return registryList;
}
appendParameters()
看下参数追加的代码逻辑
1.获取当前
BeanDefinition
所配置的所有Getter
方法,本例是application
的getApplication()
2.反射获取
getApplication()
的注解修饰类,判断是否被@Parameter(excluded = true)
修饰跳出设置,否则追加到parameters
protected static void appendParameters(Map parameters, Object config, String prefix) {
Method[] methods = config.getClass().getMethods();
for (Method method : methods) {
try {
String name = method.getName();
// 获取ApplicationConfig下所有的Getter方法:getApplication()
if (MethodUtils.isGetter(method)) {//getName()
Parameter parameter = method.getAnnotation(Parameter.class);
if (method.getReturnType() == Object.class || parameter != null && parameter.excluded()) {
continue;
}
}
// put parameters key:prefix.xxx ....
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
super.doExportUrlsFor1Protocol():
服务发布核心代码,比较长,不适合阅读,所以拆分成四块分析
1.根据BeanDefinition
配置组装URL
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
String name = protocolConfig.getName();
if (StringUtils.isEmpty(name)) {
name = DUBBO;
}
Map map = new HashMap();
// 标记为Provider端
map.put(SIDE_KEY, PROVIDER_SIDE);
// 追加一些运行时参数比如时间戳,版本号,pid
appendRuntimeParameters(map);
// 把BeanDefinition中的parameter反射放到map中
appendParameters(map, metrics);
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider);
appendParameters(map, protocolConfig);
appendParameters(map, this);
// 子标签配置
}
此时的map
是这样的:
{
"side": "provider", // 标记为生产者
"application": "demo-provider",
"release": "2.7.2", // 从jar包截取的版本号
"deprecated": "false",
"dubbo": "2.0.2", // Dubbo RPC protocol version
"pid": "48180",
"dynamic": "true",
"interface": "com.kaiyuan.qyd.topic.dubbo.basic.DemoService",
"generic": "false", // 是否是泛化调用
"timestamp": "1563865286121", // 时间戳
"register": "true",
"bean.name": "com.kaiyuan.qyd.topic.dubbo.basic.DemoService"
}
2.子标签配置
,配置粒度可以到
级别,跟踪这段代码需要补充配置标签:
细粒度的配置主要用来为泛化调用,区分参数类型,参数下标,正常业务很少对它配置
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
// 判断是否配置
if (CollectionUtils.isNotEmpty(methods)) {
for (MethodConfig method : methods) {
// 追加timeout参数到map中,key:sayHello.timeout value:2000
appendParameters(map, method, method.getName());
// 判断是否设置sayHello.retry或重写retries
String retryKey = method.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
List arguments = method.getArguments();
// 是否配置了,列表判断是因为参数可能多参
if (CollectionUtils.isNotEmpty(arguments)) {
// 省略的这部分代码主要是配置和上面的逻辑相同
}
} // end of methods for
}
}
配置完
,map
变成这样:
{
... // 省略未变化参数
"sayHello.timeout": "2000", // 方法的超时时间
"sayHello.0.callback": "true" // 标记下标为0的参数是callback函数(举例而已)
}
3.判断是否是泛化调用,并追加一些参数到map
中,生成URL
对象
这里有个特殊的
Wrapper
,javassist
处理后自动生成getMethodNames()
,留在Invoke
章节详细介绍
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
// 是否泛化调用,本例是用户业务接口,不是泛化调用接口
if (ProtocolUtils.isGeneric(generic)) {
map.put(GENERIC_KEY, generic);
map.put(METHODS_KEY, ANY_VALUE);
} else {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put(REVISION_KEY, revision);
}
// 包装类,javassist处理之后自动生成getMethodNames(),Invoker章节详细介绍
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("No method found in service interface " + interfaceClass.getName());
map.put(METHODS_KEY, ANY_VALUE);
} else {
map.put(METHODS_KEY, StringUtils.join(new HashSet(Arrays.asList(methods)), ","));
}
}
// 是否配置token
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put(TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(TOKEN_KEY, token);
}
}
// 获取本地ip
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
// port:20880
Integer port = this.findConfigedPorts(protocolConfig, name, map);
// 由map对象转换生成url
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
// export service ...
}
url
由map
对象转换而成:
dubbo://172.16.69.49:20880/com.kaiyuan.qyd.topic.dubbo.basic.DemoService?anyhost=true&application=demo-provider&bean.name=com.kaiyuan.qyd.topic.dubbo.basic.DemoService&bind.ip=172.16.69.49&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.kaiyuan.qyd.topic.dubbo.basic.DemoService&methods=sayHello&pid=46800®ister=true&release=2.7.2&sayHello.0.callback=true&sayHello.timeout=2000&side=provider×tamp=1563870489213
4.服务发布,涉及RPC
模块的内容,将在第三章Invoker
详细介绍,这里需要了解Dubbo
的领域模型
实体域:
Invoker
,是RPC
调用的核心会话域:
Invocation
,一次RPC
调用的上下文服务域:
Protocol
,实体域会话域生命周期管理,发布或引用入口
package org.apache.dubbo.rpc;
@SPI("dubbo")
// 服务域
public interface Protocol {
int getDefaultPort();
@Adaptive
Exporter export(Invoker invoker) throws RpcException;
@Adaptive
Invoker refer(Class type, URL url) throws RpcException;
}
// 实体域
public interface Invoker extends Node {
Class getInterface();
Result invoke(Invocation invocation) throws RpcException;
}
// 会话域
public interface Invocation {
String getMethodName();
Class>[] getParameterTypes();
Object[] getArguments();
Map getAttachments();
void setAttachment(String key, String value);
void setAttachmentIfAbsent(String key, String value);
String getAttachment(String key);
String getAttachment(String key, String defaultValue);
Invoker> getInvoker();
}
服务发布逻辑:
1.判断协议是否被重新定义
2.本地服务发布:url重写,创建Invoker动态代理类,缓存Exporter等待export()
3.远程服务发布:和本地调用一样先生成Exporter后执行export(),开启远程端口,注册
dubbo
协议到注册中心等待调用
package org.apache.dubbo.config;
public class ServiceConfig extends AbstractServiceConfig {
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
// 判断dubbo协议是否被用户自定义,此处使用默认dubbo协议
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
// url中没有配置scope,此处为null
String scope = url.getParameter(SCOPE_KEY);
// don't export when none is configured
if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
// 先从本地export
exportLocal(url);
}
// 如果配置了注册中心则进行远程export
// 1.createExporter(ref,interface,register)
// 2.register("dubbo://xxx")
// 3.openServer(url);
}
}
private void exportLocal(URL url) {
// url host port重写变成injvm://127.0.0.1,其他参数未变
URL local = URLBuilder.from(url)
.setProtocol(LOCAL_PROTOCOL)
.setHost(LOCALHOST_VALUE)
.setPort(0)
.build();
// 包装ref引用为Invoker接口对象,执行protocol.export()
Exporter> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
// 加入缓存
exporters.add(exporter);
}
}
小结:方法export()
概括的说做了两件事
-
配置
URL
,生成Protocol
服务域。dubbo
协议是register
协议的一个export
参数同步到注册中心registry://host/xxx.RegistryService?export=dubbo%3A%2F%2F172.16.69.49%3A20880%2F/xxx
生成远程代理类
Invoker
,执行Protocol.export(Invoker)
,注册服务,开启端口监听,等待Protocol.refer()
ServiceBean.export()
方法Step2执行publishExportEvent()
,发布一个事件,下面看下具体逻辑
Step2:publishExportEvent()
- 创建ServiceBeanExportedEvent事件,此对象结构比较简单,只有一个构造参数source
- source对象代表发生事件的主体,继承自Spring的ApplicationEvent,source=ServiceBean(this)
- 调用spring注入applicationEventPublisher发布事件等待监听器响应
public void export() {
// Step1
super.export();
// Step2
publishExportEvent();
}
private void publishExportEvent() {
// 创建事件实体对象,source对象为ServiceBean
ServiceBeanExportedEvent exportEvent = new ServiceBeanExportedEvent(this);
// ApplicationEventPublisherAware方法注入的Publisher
applicationEventPublisher.publishEvent(exportEvent);
}
ReferenceAnnotationBeanPostProcessor
ServiceBeanExportedEvent
事件只有一个监听Listener即ReferenceAnnotationBeanPostProcessor
,该类结构如下:
package org.apache.dubbo.config.spring.beans.factory.annotation;
public class ReferenceAnnotationBeanPostProcessor
extends AnnotationInjectedBeanPostProcessor
implements ApplicationContextAware, ApplicationListener
接口 | 方法 | 作用 |
---|---|---|
ApplicationContextAware | setApplicationContext() | Spring上下文 |
ApplicationListener | onApplicationEvent(E event) | 监听逻辑 |
AnnotationInjectedBeanPostProcessor | doGetInjectedBean(...) | 注解依赖注入 |
-
ApplicationListener
,监听ServiceBeanExportedEvent
,执行onApplicationEvent()
-
AnnotationInjectedBeanPostProcessor
,dubbo的依赖注入工具与spring注入对接
先看ApplicationListener
唯一方法onApplicationEvent()
,事件的执行者
onApplicationEvent()
- 判断instanceof ServiceBeanExportedEvent
- 执行onServiceBeanExportEvent(event)
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ServiceBeanExportedEvent) {
// 判断类型执行相应操作 内部直接调用了initReferenceBeanInvocationHandler方法
onServiceBeanExportEvent((ServiceBeanExportedEvent) event);
} else if (event instanceof ContextRefreshedEvent) {
onContextRefreshedEvent((ContextRefreshedEvent) event);
}
}
在onServiceBeanExportEvent()
内部直接调用了initReferenceBeanInvocationHandler()
initReferenceBeanInvocationHandler()
- 获取cache key
- 从localReferenceBeanInvocationHandlerCache中移除了key对象
- 执行了init()
private void initReferenceBeanInvocationHandler(ServiceBean serviceBean) {
String beanName = serviceBean.getBeanName();
// Remove ServiceBean when it's exported
// 为了调整格式修改了变量名称lrbhc=localReferenceBeanHandlerCache
ReferenceBeanInvocationHandler handler = lrbhc.remove(beanName);
// Initialize
if (handler != null) {
handler.init();
}
}
问题:为什么要用缓存?
因为
和
都是重量级对象,不能频繁创建,缓存和双重锁保证单例
dubbo依托spring生命周期做缓存证明了dubbo使用api启动的方式要考虑程序健壮性,需要自己做内存管理
问题: 为什么要从localReferenceBeanHandlerCache中移除对象?
带着这个问题继续分析,cache可以被remove说明cache里面有值,如果我们知道缓存存放的是什么值,以及存放的逻辑,那么也就知道问题所在了。
按照这个思路定位到方法doGetInjectedBean
(),是来自父类AnnotationInjectedBeanPostProcessor
的抽象方法,该方法有个关键的参数
Object doGetInjectedBean(Reference reference, Object bean, String beanName,Class> injectedType,InjectedElement injectedElement)
Reference reference: 这是我们@Reference
的注解类,通过它可以注入SpringBean
,通常这么写
package com.kaiyuan.qyd.business.tx;
@Reference FooService fooService;
Dubbo
依托Spring
包扫描机制把@Service
/@Reference
注解修饰的对象注册/注入到Spring
容器中
@Reference
使用范围:可以用在Field
,Method
上,说明是用来依赖注入
package org.apache.dubbo.config.annotation;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface Reference {}
@Service
的使用范围:只能用在Class上为了注册到spring容器
package org.apache.dubbo.config.annotation;
@Target({ElementType.TYPE})
public @interface Service {}
DubboComponentScanRegistrar:
上面说到Dubbo
依托Spring
包扫描机制把@Service
/@Reference
注解修饰的对象注册/注入到Spring
容器中,下面简单介绍下是怎么实现的
父类ImportBeanDefinitionRegistrar是Spring接口,用来实现包扫描注册功能
- @DubboComponentScan包扫描获取路径
- 注册@Service注解修饰的对象到spring,会触发BeanDefinition拦截器自定义注入逻辑
- 注入@Reference注解修饰的对象到spring,最终会触发依赖注入doGetInjectedBean逻辑
package org.apache.dubbo.config.spring.context.annotation;
// 本节只做介绍后面会有spring和dubbo整合的源码分析
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
void registerBeanDefinitions(AnnotationMetadata meta, BeanDefinitionRegistry registry) {
// @DubboComponentScan包扫描
Set packagesToScan = getPackagesToScan(meta);
// 注册@Service注解修饰的对象到spring,使用了BeanDefinitionRegistry注册工厂
// 核心逻辑在ServiceAnnotationBeanPostProcessor.registerServiceBean方法内部
registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
// 注入@Reference注解修饰的对象到spring,使用了BeanDefinitionRegistry注册工厂
registerReferenceAnnotationBeanPostProcessor(registry);
}
}
Spring
所使用的注册工厂如下,registerBeanDefinition()
执行注册逻辑时会触发Dubbo实现的BeanDefinitionRegistryPostProcessor
子类ServiceAnnotationBeanPostProcessor
实现自定义注册
package org.springframework.beans.factory.support;
public interface BeanDefinitionRegistry extends AliasRegistry {
// spring注册方法
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
doGetInjectedBean
上面说到使用@Reference依赖注入最终会执行本方法,下面开始分析方法逻辑
doGetInjectedBean
- 获取referencedBeanName
- 包装业务接口成ReferenceBean
- 缓存ReferenceBean到BeanCache
- 创建代理类
@Override
Object doGetInjectedBean(Reference reference, Object bean, String beanName,Class> injectedType,InjectedElement injectedElement){
// 构建beanname
String referencedBeanName = buildReferencedBeanName(reference, injectedType);
// 包装业务接口成ReferenceBean
ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referencedBeanName,reference, injectedType, getClassLoader());
cacheInjectedReferenceBean(referenceBean, injectedElement);
// 创建代理类
return buildProxy(referencedBeanName, referenceBean, injectedType);
}
动态代理在这里的作用是动态生成代理类,对原始方法进行控制,对使用者透明,本章只阐述下直观作用
未代理之前:
调用方->业务接口->业务接口实现类
动态代理后:
调用方->业务接口->动态代理类->业务接口实现类
动态代理类InvocationHandler.invoke()
方法示例:
begin()
try
action1
srcMethod.invoke();// 原始方法调用
action2
catch e {}
finally {}
end()
真实的代理比这个要复杂,本代理类其实就是远程调用的Invoker对象(包含五元组,协议,序列化等等),比如consumer端执行远程调用的同时也要生成代理类,provider端接收亦然,在后面章节Invoker和Proxy会详细介绍
buildProxy:
介绍完动态代理,回过头来看下代码逻辑
buildProxy目的是针对业务接口生成代理方法,对调用方无感知
Object buildProxy(String referencedBeanName, ReferenceBean referenceBean, Class> injectedType) {
// 生成动态代理handler,对业务接口进行控制代理
InvocationHandler handler = buildInvocationHandler(referencedBeanName, referenceBean);
// dubbo注解管理的bean对象都是动态代理类,会执行handler.invoke()
return Proxy.newProxyInstance(getClassLoader(), new Class[]{injectedType}, handler);
}
问题的线索就在
buildInvocationHandler()
内部
缓存存放的值:ReferenceBeanInvocationHandler
,该对象是上面的动态代理类
缓存存放逻辑:@Reference
注入的是不是本地@Service
注册的Spring对象
InvocationHandler buildInvocationHandler(String referencedBeanName, ReferenceBean referenceBean) {
ReferenceBeanInvocationHandler handler = lcrih.get(referencedBeanName);
if (handler == null) {
handler = new ReferenceBeanInvocationHandler(referenceBean);
}
// 从这能看到缓存添加的是本地spring管理的bean对象,用referencedBeanName做区分
if (applicationContext.containsBean(referencedBeanName)) {
// lcrih=localReferenceBeanInvocationHandlerCache
lcrih.put(referencedBeanName, handler);
} else {
handler.init();
}
return handler;
}
ReferenceBeanInvocationHandler
ReferenceBeanInvocationHandler
继承了InvocationHandler
动态代理类,代理对象是业务定义的接口:
方法逻辑很简单,只是从缓存中获取管理的ReferenceBean
调用执行,同时解决了一个单元测试的bug:
package org.apache.dubbo.config.spring.beans.factory.annotation;
public class ReferenceAnnotationBeanPostProcessor {}
// static内部类
private static class ReferenceBeanInvocationHandler implements InvocationHandler {
private final ReferenceBean referenceBean;
private Object bean;
private ReferenceBeanInvocationHandler(ReferenceBean referenceBean) {
this.referenceBean = referenceBean;
}
@Override // 动态代理方法执行入口
public Object invoke(Object proxy, Method method, Object[] args) {
Object result;
try {
if (bean == null) {
// issue: https://github.com/apache/dubbo/issues/3429
init();
}
result = method.invoke(bean, args);
} catch (InvocationTargetException e) {
// re-throws the actual Exception.
throw e.getTargetException();
}
return result;
}
private void init() {
this.bean = referenceBean.get();
}
}
}
小结:
- 当
ServiceBean
执行export方法时发布ServiceBeanExportedEvent
事件 -
ReferenceAnnotationBeanPostProcessor
监听到该事件,从本地localReferenceBeanInvocationHandlerCache
缓存中移除bean对象 - 缓存中的数据是依托DubboComponentScan包扫描机制根据
@Service
注解注入到spring容器中,同时对@Reference
进行依赖注入 - 如果
ReferenceBean
所代理的bean其实是本地spring容器管理的,也就是本地调用,则存放到localReferenceBeanInvocationHandlerCache
中 - 因为有延迟导出的存在,所以采用事件通知机制及时更新本地缓存,动态代理生成
InvocationHandler
对象 -
localReferenceBeanInvocationHandlerCache
移除的只是本地代理对象,真正的远程代理类都在referenceBeanCache
里面保存着
服务引用
服务发布完成后,生成RPC
代理,同步Invoker
信息到注册中心,等待Consumer
端引用,配置如下:
ReferenceBean
Dubbo
要求
标签由Consumer
端定义,下面开始介绍Consumer
的定义入口ReferenceBean
。
public class ReferenceBean extends ReferenceConfig
implements FactoryBean, ApplicationContextAware,
InitializingBean, DisposableBean {}
接口 | 方法 | 作用 |
---|---|---|
FactoryBean | getObject() | 获取spring对象 |
ApplicationContextAware | setApplicationContext(ApplicationContext) | 注入spring环境 |
InitializingBean | afterPropertiesSet() | bean启动扩展 |
DisposableBean | destroy() | bean销毁扩展 |
这个类实现的接口与
ServiceBean
类似,相同的地方无需赘述,区别在于FactoryBean.getObject()
Spring
容器启动自动调用afterPropertiesSet()
,进行一系列的BeanDefinition
设置之后执行FactoryBean.getObject()
package org.apache.dubbo.config.spring;
public class ReferenceBean extends ReferenceConfig
implements FactoryBean, ApplicationContextAware,
InitializingBean, DisposableBean {}
public void afterPropertiesSet() throws Exception {
// 执行一系列BeanDefinition配置
// setConsumer()
// setApplication()
// setRegistries()
if (shouldInit()) {// 是否延迟加载
getObject();
}
}
public Object getObject() {
// 父类ReferenceConfig中的方法
return get();
}
ReferenceConfig.get():
同步方法get()
和非空判断保证了单例,内部逻辑先是做了内部的验证和默认配置重写,之后执行ReferenceConfig.init()
package org.apache.dubbo.config;
public class ReferenceConfig {
public synchronized T get() {
// 内部验证比如注册中心,还有一些默认参数的重写
checkAndUpdateSubConfigs();
if (destroyed) {
throw new IllegalStateException("The invoker has already destroyed!");
}
if (ref == null) {
// 单例初始化
init();
}
return ref;
}
}
ReferenceConfig.init():
核心是从注册中心获取元数据创建代理类,细节会在
Invoker
里面详细介绍1.例行参数检查
2.map对象参数设置
2.创建远程代理类
private void init() {
if (initialized) {
return;
}
// 例行检查
checkStubAndLocal(interfaceClass);
checkMock(interfaceClass);
Map map = new HashMap();
map.put(SIDE_KEY, CONSUMER_SIDE);
appendRuntimeParameters(map);
// 省略map参数准备...
String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY);
if (StringUtils.isEmpty(hostToRegistry)) {
hostToRegistry = NetUtils.getLocalHost();
} else if (isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property:");
}
map.put(REGISTER_IP_KEY, hostToRegistry);
// 从注册中心获取元数据创建远程调用代理类
ref = createProxy(map);
String serviceKey = URL.buildKey(interfaceName, group, version);
// 创建ConsumerModel并放到ApplicationModel缓存里
ApplicationModel.initConsumerModel(serviceKey, buildConsumerModel(serviceKey, attributes));
initialized = true;
}
小结:
ServiceConfig
和ReferenceConfig
最终都指向了Invoker
,也说明了实体域Invoker
的重要性,在这里对两个重要方法做个标记,也是后面第三章的引子。
1.服务发布创建远程代理类:ServiceConfig.doExportUrlsFor1Protocol()
2.服务导出引用远程代理类:ReferenceConfig.init()