2024年最新源码分析Dubbo服务消费端启动流程,腾讯java面试机试

总结

上述知识点,囊括了目前互联网企业的主流应用技术以及能让你成为“香饽饽”的高级架构知识,每个笔记里面几乎都带有实战内容。

很多人担心学了容易忘,这里教你一个方法,那就是重复学习。

打个比方,假如你正在学习 spring 注解,突然发现了一个注解@Aspect,不知道干什么用的,你可能会去查看源码或者通过博客学习,花了半小时终于弄懂了,下次又看到@Aspect 了,你有点郁闷了,上次好像在哪哪哪学习,你快速打开网页花了五分钟又学会了。

从半小时和五分钟的对比中可以发现多学一次就离真正掌握知识又近了一步。

人的本性就是容易遗忘,只有不断加深印象、重复学习才能真正掌握,所以很多书我都是推荐大家多看几遍。哪有那么多天才,他只是比你多看了几遍书。

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

if (b == null && getConsumer() != null) {

b = getConsumer().isInit();

}

if (b != null && b.booleanValue()) {

getObject();

}

Step6:判断是否初始化,如果为初始化,则调用getObject()方法,该方法也是FactoryBean定义的方法,ReferenceBean是dubbo:reference所真实引用的类(interface)的实例工程,getObject发返回的是interface的实例,而不是ReferenceBean实例。

1.1 源码分析getObject()

public Object getObject() throws Exception {

return get();

}

ReferenceBean#getObject()方法直接调用其父类的get方法,get方法内部调用init()方法进行初始化

1.2 源码分析ReferenceConfig#init方法

ReferenceConfig#init

if (initialized) {

return;

}

initialized = true;

if (interfaceName == null || interfaceName.length() == 0) {

throw new IllegalStateException(“<dubbo:reference interface=”" /> interface not allow null!");

}

Step1:如果已经初始化,直接返回,如果interfaceName为空,则抛出异常。

ReferenceConfig#init调用ReferenceConfig#checkDefault

private void checkDefault() {

if (consumer == null) {

consumer = new ConsumerConfig();

}

appendProperties(consumer);

}

Step2:如果dubbo:reference标签也就是ReferenceBean的consumer属性为空,调用appendProperties方法,填充默认属性,其具体加载顺序:

  1. 从系统属性加载对应参数值,参数键:dubbo.consumer.属性名,从系统属性中获取属性值的方法为:System.getProperty(key)。

  2. 加载属性配置文件的值。属性配置文件,可通过系统属性:dubbo.properties.file,如果该值未配置,则默认取dubbo.properties属性配置文件。

ReferenceConfig#init

appendProperties(this);

Step3:调用appendProperties方法,填充ReferenceBean的属性,属性值来源与step2一样,当然只填充ReferenceBean中属性为空的属性。

ReferenceConfig#init

if (getGeneric() == null && getConsumer() != null) {

setGeneric(getConsumer().getGeneric());

}

if (ProtocolUtils.isGeneric(getGeneric())) {

interfaceClass = GenericService.class;

} else {

try {

interfaceClass = Class.forName(interfaceName, true, Thread.currentThread().getContextClassLoader());

} catch (ClassNotFoundException e) {

throw new IllegalStateException(e.getMessage(), e);

}

checkInterfaceAndMethods(interfaceClass, methods);

}

Step4:如果使用返回引用,将interface值替换为GenericService全路径名,如果不是,则加载interfacename,并检验dubbo:reference子标签dubbo:method引用的方法是否在interface指定的接口中存在。

ReferenceConfig#init

String resolve = System.getProperty(interfaceName); // @1

String resolveFile = null;

if (resolve == null || resolve.length() == 0) { // @2

resolveFile = System.getProperty(“dubbo.resolve.file”); // @3 start

if (resolveFile == null || resolveFile.length() == 0) {

File userResolveFile = new File(new File(System.getProperty(“user.home”)), “dubbo-resolve.properties”);

if (userResolveFile.exists()) {

resolveFile = userResolveFile.getAbsolutePath();

}

} // @3 end

if (resolveFile != null && resolveFile.length() > 0) { // @4

Properties properties = new Properties();

FileInputStream fis = null;

try {

fis = new FileInputStream(new File(resolveFile));

properties.load(fis);

} catch (IOException e) {

throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);

} finally {

try {

if (null != fis) fis.close();

} catch (IOException e) {

logger.warn(e.getMessage(), e);

}

}

resolve = properties.getProperty(interfaceName);

}

}

if (resolve != null && resolve.length() > 0) { // @5

url = resolve;

if (logger.isWarnEnabled()) {

if (resolveFile != null && resolveFile.length() > 0) {

logger.warn(“Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + “” + resolve + " to p2p invoke remote service.”);

} else {

logger.warn(“Using -D” + interfaceName + “=” + resolve + " to p2p invoke remote service.");

}

}

}

Step5:处理dubbo服务消费端resolve机制,也就是说消息消费者只连服务提供者,绕过注册中心。

代码@1:从系统属性中获取该接口的直连服务提供者,如果存在 -Dinterface=dubbo://127.0.0.1:20880,其中interface为dubbo:reference interface属性的值。

代码@2:如果未指定-D属性,尝试从resolve配置文件中查找,从这里看出-D的优先级更高。

代码@3:首先尝试获取resolve配置文件的路径,其来源可以通过-Ddubbo.resolve.file=文件路径名来指定,如果未配置该系统参数,则默认从${user.home}/dubbo-resolve.properties,如果过文件存在,则设置resolveFile的值,否则resolveFile为null。

代码@4:如果resolveFile不为空,则加载resolveFile文件中内容,然后通过interface获取其配置的直连服务提供者URL。

代码@5:如果resolve不为空,则填充ReferenceBean的url属性为resolve(点对点服务提供者URL),打印日志,点对点URL的来源(系统属性、resolve配置文件)。

ReferenceConfig#init

checkApplication();

checkStubAndMock(interfaceClass);

Step6:校验ReferenceBean的application是否为空,如果为空,new 一个application,并尝试从系统属性(优先)、资源文件中填充其属性;同时校验stub、mock实现类与interface的兼容性。系统属性、资源文件属性的配置如下:

application dubbo.application.属性名,例如 dubbo.application.name

ReferenceConfig#init

Map<String, String> map = new HashMap<String, String>();

Map<Object, Object> attributes = new HashMap<Object, Object>();

map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);

map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());

map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));

if (ConfigUtils.getPid() > 0) {

map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));

}

Step7:构建Map,封装服务消费者引用服务提供者URL的属性,这里主要填充side:consume(消费端)、dubbo:2.0.0(版本)、timestamp、pid:进程ID。

ReferenceConfig#init

if (!isGeneric()) {

String revision = Version.getVersion(interfaceClass, version);

if (revision != null && revision.length() > 0) {

map.put(“revision”, revision);

}

String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();

if (methods.length == 0) {

logger.warn("NO method found in service interface " + interfaceClass.getName());

map.put(“methods”, Constants.ANY_VALUE);

} else {

map.put(“methods”, StringUtils.join(new HashSet(Arrays.asList(methods)), “,”));

}

}

Step8:如果不是泛化引用,增加methods:interface的所有方法名,多个用逗号隔开。

ReferenceConfig#init

map.put(Constants.INTERFACE_KEY, interfaceName);

appendParameters(map, application);

appendParameters(map, module);

appendParameters(map, consumer, Constants.DEFAULT_KEY);

appendParameters(map, this);

Step9:用Map存储application配置、module配置、默认消费者参数(ConsumerConfig)、服务消费者dubbo:reference的属性。

ReferenceConfig#init

String prefix = StringUtils.getServiceKey(map);

if (methods != null && !methods.isEmpty()) {

for (MethodConfig method : methods) {

appendParameters(map, method, method.getName());

String retryKey = method.getName() + “.retry”;

if (map.containsKey(retryKey)) {

String retryValue = map.remove(retryKey);

if (“false”.equals(retryValue)) {

map.put(method.getName() + “.retries”, “0”);

}

}

appendAttributes(attributes, method, prefix + “.” + method.getName());

checkAndConvertImplicitConfig(method, map, attributes);

}

}

Step10:获取服务键值 /{group}/interface:版本,如果group为空,则为interface:版本,其值存为prifex,然后将dubbo:method的属性名称也填入map中,键前缀为dubbo.method.methodname.属性名。dubbo:method的子标签dubbo:argument标签的属性也追加到attributes map中,键为 prifex + methodname.属性名。

ReferenceConfig#init

String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);

if (hostToRegistry == null || hostToRegistry.length() == 0) {

hostToRegistry = NetUtils.getLocalHost();

} else if (isInvalidLocalHost(hostToRegistry)) {

throw new IllegalArgumentException(“Specified invalid registry ip from property:” + Constants.DUBBO_IP_TO_REGISTRY + “, value:” +

hostToRegistry);

}

map.put(Constants.REGISTER_IP_KEY, hostToRegistry);

Step11:填充register.ip属性,该属性是消息消费者连接注册中心的IP,并不是注册中心自身的IP。

ReferenceConfig#init

ref = createProxy(map);

Step12:调用createProxy方法创建消息消费者代理,下面详细分析其实现细节。

ReferenceConfig#init

ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());

ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);

Step13:将消息消费者缓存在ApplicationModel中。

1.2.1 源码分析ReferenceConfig#createProxy方法

ReferenceConfig#createProxy

URL tmpUrl = new URL(“temp”, “localhost”, 0, map);

final boolean isJvmRefer;

if (isInjvm() == null) {

if (url != null && url.length() > 0) { // if a url is specified, don’t do local reference

isJvmRefer = false;

} else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {

// by default, reference local service if there is

isJvmRefer = true;

} else {

isJvmRefer = false;

}

} else {

isJvmRefer = isInjvm().booleanValue();

}

Step1:判断该消费者是否是引用本(JVM)内提供的服务。

如果dubbo:reference标签的injvm(已过期,被local属性替换)如果不为空,则直接取该值,如果该值未配置,则判断ReferenceConfig的url属性是否为空,如果不为空,则isJvmRefer =false,表明该服务消费者将直连该URL的服务提供者;如果url属性为空,则判断该协议是否是isInjvm,其实现逻辑:获取dubbo:reference的scop属性,根据其值判断:

  • 如果为空,isJvmRefer为false。

  • 如果协议为injvm,就是表示为本地协议,既然提供了本地协议的实现,则无需配置isJvmRefer该标签为true,故,isJvmRerfer=false。

  • 如果scope=local或injvm=true,isJvmRefer=true。

  • 如果scope=remote,isJvmRefer设置为false。

  • 如果是泛化引用,isJvmRefer设置为false。

  • 其他默认情况,isJvmRefer设置为true。

ReferenceConfig#createProxy

if (isJvmRefer) {

URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);

invoker = refprotocol.refer(interfaceClass, url);

if (logger.isInfoEnabled()) {

logger.info("Using injvm service " + interfaceClass.getName());

}

}

总结

以上是字节二面的一些问题,面完之后其实挺后悔的,没有提前把各个知识点都复习到位。现在重新好好复习手上的面试大全资料(含JAVA、MySQL、算法、Redis、JVM、架构、中间件、RabbitMQ、设计模式、Spring等),现在起闭关修炼半个月,争取早日上岸!!!

下面给大家分享下我的面试大全资料

  • 第一份是我的后端JAVA面试大全

image.png

后端JAVA面试大全

  • 第二份是MySQL+Redis学习笔记+算法+JVM+JAVA核心知识整理

字节二面拜倒在“数据库”脚下,闭关修炼半个月,我还有机会吗?

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理

  • 第三份是Spring全家桶资料

字节二面拜倒在“数据库”脚下,闭关修炼半个月,我还有机会吗?

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

料(含JAVA、MySQL、算法、Redis、JVM、架构、中间件、RabbitMQ、设计模式、Spring等)**,现在起闭关修炼半个月,争取早日上岸!!!

下面给大家分享下我的面试大全资料

  • 第一份是我的后端JAVA面试大全

[外链图片转存中…(img-gLMQ1wuO-1715251545947)]

后端JAVA面试大全

  • 第二份是MySQL+Redis学习笔记+算法+JVM+JAVA核心知识整理

[外链图片转存中…(img-4CfFM8Ec-1715251545947)]

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理

  • 第三份是Spring全家桶资料

[外链图片转存中…(img-9WzrtfZB-1715251545948)]

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值