Ribbon为服务创建独立上下文的流程

2022-07-26 20:05:34 593

  1. 从Feign接口开始
goodsFeignClient.getGoods(id)
  1. 进入代理类的拦截方法
feign.ReflectiveFeign.FeignInvocationHandler#invoke

feign.SynchronousMethodHandler#invoke
  1. 继续跳转, 默认使用ribbon包下的LoadBalancerFeignClient
feign.SynchronousMethodHandler#executeAndDecode

org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute
  1. 为每个服务创建独立的配置
org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#getClientConfig
// 如果接口有Options参数, 则使用Feign接口上的(后续仍会创建上下文); 没有的话直接走this.clientFactory.getClientConfig创建容器加载配置
IClientConfig getClientConfig(Request.Options options, String clientName) {
    IClientConfig requestConfig;
    if (options == DEFAULT_OPTIONS) {
        requestConfig = this.clientFactory.getClientConfig(clientName);
    }
    else {
        requestConfig = new FeignOptionsClientConfig(options);
    }
    return requestConfig;
}
  1. 调用SpringClientFactory#getClientConfig方法, 一直跳转到
org.springframework.cloud.context.named.NamedContextFactory#createContext
protected AnnotationConfigApplicationContext createContext(String name) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    if (this.configurations.containsKey(name)) {
        for (Class<?> configuration : this.configurations.get(name)
             .getConfiguration()) {
            context.register(configuration);
        }
    }
    // 将Specification注册到容器
    for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
        if (entry.getKey().startsWith("default.")) {
            for (Class<?> configuration : entry.getValue().getConfiguration()) {
                context.register(configuration);
            }
        }
    }
    //注册 配置解析器 以及 RibbonClientConfiguration
    context.register(PropertyPlaceholderAutoConfiguration.class,
                     this.defaultConfigType);
    // 添加一个属性源 
    // 示例结构
    // {
    //     "ribbon": {
    //         "ribbon.client.name": "test-service"
    //     }
    // }
    context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
        this.propertySourceName,
        Collections.<String, Object>singletonMap(this.propertyName, name)));

    if (this.parent != null) {
        context.setParent(this.parent);
        context.setClassLoader(this.parent.getClassLoader());
    }
    context.setDisplayName(generateDisplayName(name));
    context.refresh();
    return context;
}
  1. 第5步里this.configurations.entrySet()加载了一些配置类, 比如
// 使用nacos
com.alibaba.cloud.nacos.ribbon.NacosRibbonClientConfiguration

这些是通过org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration 以及org.springframework.cloud.netflix.ribbon.RibbonClientConfigurationRegistrar初始化的

public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(RibbonClients.class.getName(), true);
		if (attrs != null && attrs.containsKey("value")) {
			AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
			for (AnnotationAttributes client : clients) {
				registerClientConfiguration(registry, getClientName(client),
						client.get("configuration"));
			}
		}
		if (attrs != null && attrs.containsKey("defaultConfiguration")) {
			String name;
			if (metadata.hasEnclosingClass()) {
				name = "default." + metadata.getEnclosingClassName();
			}
			else {
				name = "default." + metadata.getClassName();
			}
             // 读取RibbonClients注解上defaultConfiguration的值
			registerClientConfiguration(registry, name,
					attrs.get("defaultConfiguration"));
		}
		Map<String, Object> client = metadata
				.getAnnotationAttributes(RibbonClient.class.getName(), true);
		String name = getClientName(client);
		if (name != null) {
			registerClientConfiguration(registry, name, client.get("configuration"));
		}
	}
    
    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
			Object configuration) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(RibbonClientSpecification.class);
		builder.addConstructorArgValue(name);
		builder.addConstructorArgValue(configuration);
        // 注册进容器
		registry.registerBeanDefinition(name + ".RibbonClientSpecification",
				builder.getBeanDefinition());
	}
    // ignore others code   
}
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(
        name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
        AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
        ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {

    //将标记了RibbonClients注解的类注入进来 就是上面动态注入的bean
    @Autowired(required = false)
    private List<RibbonClientSpecification> configurations = new ArrayList<>();


    @Bean
    @ConditionalOnMissingBean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
    }
}
  1. 最后将创建的上下文缓存起来, 并返回创建好的bean

org.springframework.cloud.context.named.NamedContextFactory#getContext

protected AnnotationConfigApplicationContext getContext(String name) {
    if (!this.contexts.containsKey(name)) {
        synchronized (this.contexts) {
            if (!this.contexts.containsKey(name)) {
                this.contexts.put(name, createContext(name));
            }
        }
    }
    return this.contexts.get(name);
}


Nacos服务发现客户端流程

基于nacos-client:1.4.2Nacos有推/拉两种模式1. 推由Nacos服务端主动发起, 通过UDP进行推送2. 拉由客户端发起启动时调用nacos接口拉取一次调用指定服务时拉取一次通过定时器定时拉取流程梳理如下流程图
2022-08-01

微服务架构中OpenFeign请求的流程

1. 代理目标方法注入容器 (OpenFeign)2. 创建请求对象, 组装参数 (OpenFeign)3. 维护服务注册表(Ribbon + Nacos), 且根据负载均衡算法(Ribbon)选取一个真实的服务地址将http://base-service/getInfo替换为http://127.
2022-08-01

Ribbon为服务创建独立上下文的流程

从Feign接口开始goodsFeignClient.getGoods(id) 进入代理类的拦截方法feign.ReflectiveFeign.FeignInvocationHandler#invoke feign.SynchronousMethodHandler#invoke 继续跳转, 默认使
2022-07-26

解决Eureka默认缓存配置导致服务无法被调用

开发环境下, 比如A服务调用B服务, B服务更改代码重启后, A服务足足要等上一两分钟才能正常调用到B服务无疑是浪费生命技术架构是Eureka+OpenFeign+RibbonRibbon配置Ribbon调用这个方法启动了一个定时器, 该定时器定期刷新ribbon内缓存的服务列表com.netfli
2022-07-24

SpringCloudAlibaba搭建过程踩坑记录

Nacos如果使用docker部署Nacos, 则需要设置容器环境变量NACOS_AUTH_ENABLE=true, 才能使nacos开启连接密码认证 spring: profiles: active: @profileActive@ application: name: w
2021-09-02

SpringCloud OpenFeign 微服务调用

现有xxx-common 和 xxx-user两个微服务, 现需要在common中调用user的服务添加依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-start
2021-04-10

SpringBoot Test bootstrap.yml读取不到指定配置

项目用了springcloud config, 所以用bootstrap读取项目基础配置.但是在单元测试下, 发现bootstrap内的值(@@application.name@@)没有被替换, 导致单元测试一直报错解决方法:在pom中的project->build节点下, 新增如下代码
2021-03-26

SpringCloud (一) : 创建工程

在idea中, 创建maven工程blog-parent引入基本依赖<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.o
2020-11-10

SpringCloud Bus 刷新不了配置

不要使用@Value("${config.test}") private String test; 改为使用@ConfigurationProperties(prefix = "config") @Component public class UserConfig{ private String
2020-11-09

freemarker 时间显示不正常 设置时区

项目在本地开发的时候显示正常,部署上服务器就一直差8个小时,最后发现freemarker官方文档有这样的说明time_zone:时区的名称来显示并格式化时间。 默认情况下,使用JVM的时区。 也可以是 Java 时区 API 接受的值,或者 "JVM default" (从 FreeMarker 2
2020-03-28
IDEA 2019.1 xml 不高亮

IDEA 2019.1 xml 不高亮

前几天更新了idea后,发现xml里的代码都没有了高亮,变得跟记事本一个德性了打开setting ,搜索 File Types,找到xml项, 查看下方的匹配格式,果然没有xml,(idea真是厉害)点击右方的+,输入*.xml,点击ok,解决问题
2020-03-28

npm install 淘宝镜像

npm install --registry=https://registry.npm.taobao.org
2020-03-28
Java中方法的参数传递机制

Java中方法的参数传递机制

来看一段代码 public class Man { private String name; private Integer age; public String getName() { return name; } publi
2020-03-28
基于自定义注解手写权限控制

基于自定义注解手写权限控制

方法一: AOP 方法二: 拦截器项目结构项目依赖<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-w
2020-03-28

Docker 部署 详细全过程 附代码

Docker 部署本站 全过程环境:CentOS7.61. 安装Docker其他版本CentOS可以参考这个https://help.aliyun.com/document_detail/187598.html查看本机内核版本,内核版本需高于 3.10uname -r 确保 yum 包最新yum u
2020-03-28

SpringBoot 启动普通java工程

引入依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.0.9</version> </dependency>
2020-03-28

Vue.js DOM操作

<template> <input type="button" @click="reply($event)" value="回复"> </template> export default { methods: { replyFun(e) {
2020-03-29
CentOS7编译调试OpenJDK12

CentOS7编译调试OpenJDK12

1. 下载源码https://hg.openjdk.java.net/jdk/jdk12点击左侧的browse,再点击zip,就可以下载zip格式的源码压缩包。unzip xxx.zip 解压文件2. 安装jdkyum install java-11-openjdk-devel -y3. 运行con
2020-04-23
编写自己的Spring Boot Starter

编写自己的Spring Boot Starter

1.新建一个maven项目命名规则统一是xxx-spring-boot-starter完整pom.xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"
2020-06-29