source-code-hunter Documentation

从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等

23,127 stars Java Java 1 file · ~22,533 tokens #dubbo#mybatis#netty#redis#sourcecode-analysis#spring
RAW Doc

1. Project Overview & Quickstart (doocs/source-code-hunter)

File: README.md

互联网公司常用框架源码赏析






“技术深度” 与 “技术广度”是对开发者来说最为重要的两个维度,本项目致力于从源码层面,剖析和挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。

加入我们,一起通读互联网行业主流框架及中间件源码,成为强大的 “源码猎人”,目前开放的有 Spring 全家桶、Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等,让我们一起开拓新的领地,揭开这些源码的神秘面纱。

本项目主要用于记录框架及中间件源码的阅读经验、个人理解及解析,希望能够使阅读源码变成一件简单有趣,且有价值的事情,抽空更新中... (如果本项目对您有帮助,请 watch、star、fork 素质三连一波,鼓励一下作者,谢谢)

网站

https://schunter.doocs.org

Spring 系列

IoC 容器

AOP

SpringMVC

SpringJDBC

  • 努力编写中...

Spring 事务

Spring 源码故事(瞎编版)

Spring 整体脉络

Spring 类解析

Spring5 新特性

Spring RMI

Spring Message

SpringBoot

Spring Cloud

SpringSecurity

MyBatis

基础支持层

核心处理层

类解析

Netty

网络 IO 技术基础

JDK1.8 NIO 包 核心组件源码剖析

Netty 粘拆包及解决方案

Netty 多协议开发

基于 Netty 开发服务端及客户端

Netty 主要组件的源码分析

Netty 高级特性

Netty 技术细节源码分析

Dubbo

架构设计

SPI 机制

注册中心

远程通信

RPC

集群

Tomcat

Servlet 与 Servlet 容器

Web 容器

Redis

Nacos

Sentinel

RocketMQ

番外篇(JDK 1.8)

基础类库

集合

并发编程

学习心得

个人经验

设计模式

多线程


Stars 趋势

Doocs 社区优质项目

GitHub 技术社区 Doocs,致力于打造一个内容完整、持续成长的互联网开发者学习生态圈!以下是 Doocs 的一些优秀项目,欢迎各位开发者朋友持续保持关注。

# 项目 描述 热度
1 advanced-java 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识。
2 leetcode 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解。
3 source-code-hunter 互联网常用组件框架源码分析。
4 jvm Java 虚拟机底层原理知识总结。
5 coding-interview 代码面试题集,包括《剑指 Offer》、《编程之美》等。
6 md 一款高度简洁的微信 Markdown 编辑器。
7 technical-books 值得一看的技术书籍列表。

贡献者

感谢以下所有朋友对 GitHub 技术社区 Doocs 所做出的贡献,参与项目维护请戳这儿。

公众号

Doocs 技术社区旗下唯一公众号「Doocs」​,欢迎扫码关注,专注分享技术领域相关知识及行业最新资讯。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。

关注「Doocs」公众号,回复 PDF,即可获取 互联网 Java 工程师进阶知识完全扫盲 项目离线 PDF 文档(283 页精华),学习更加方便!


File: docs/SpringCloud/spring-cloud-commons-source-note.md

说明

Author: haitaoss

源码阅读仓库: spring-cloud-commons

参考资料和需要掌握的知识:

Spring Cloud 介绍

SpringCloud 是在 SpringBoot 的基础上构建的。Spring Cloud 以两个库的形式提供了许多特性:Spring Cloud Context 和 Spring Cloud Commons。Spring Cloud Context 为 SpringCloud 应用程序的 ApplicationContext 提供扩展机制(引导上下文、加密、刷新属性和环境端点)。Spring Cloud Commons 是一组抽象(服务注册、负载均衡、熔断器等 API) 和 通用类,用于不同的 Spring Cloud 实现(例如 Spring Cloud Netflix 和 Spring Cloud Consul)

是基于 Spring Boot 的自动装配原理实现的,其实就是定义了很多自动配置类,所以在 SpringCloud 的环境下 启动 SpringBoot 程序 会有很多功能。

核心功能源码分析

BootstrapApplicationListener (bootstrap.properties 读取原理)

前置知识:SprinBoot 加载 application.yml 的原理

示例代码

java
@EnableAutoConfiguration
public class Main {

	public static void main(String[] args) {
		// 是否创建 bootstrapContext
		System.setProperty("spring.cloud.bootstrap.enabled", "true");
		// 设置 bootstrapContext 中属性文件的搜索目录 或者是 属性文件
		System.setProperty("spring.cloud.bootstrap.location", "");
		System.setProperty("spring.cloud.bootstrap.additional-location",
				"optional:classpath:/config/haitao/,classpath:/haitao.properties");
		// 设置 bootstrapContext 默认属性文件的名字
		// System.setProperty("spring.cloud.bootstrap.name", "bootstrap-haitao");
		// 设置 profile
		// System.setProperty("spring.profiles.active", "haitao");
		// 测试读取属性
		ConfigurableApplicationContext context = SpringApplication.run(Main.class, args);
		ConfigurableEnvironment environment = context.getEnvironment();
		Stream.iterate(1, i -> i + 1).limit(5).map(i -> "p" + i).forEach(
				name -> System.out.println(String.format("key:%s \t valus: %s", name, environment.getProperty(name))));
	}

}

BootstrapApplicationListener 是用于完成 SpringCloud 的接入的,主要是完成 bootstrapContext 的创建、bootstrap 属性的加载、设置 bootstrapContext 为父容器。下面是 BootstrapApplicationListener 被触发的入口和核心逻辑

text
/* Detailed source-code truncated for AI context efficiency. */

BootstrapImportSelectorConfiguration

用来扩展 bootstrapContext 中的配置类

java
// 类的声明如下
@Configuration(proxyBeanMethods = false)
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {}
java
/**
 * BootstrapImportSelectorConfiguration 会通过 @Import 导入 {@link BootstrapImportSelector}
 * 其回调方法 {@link BootstrapImportSelector#selectImports(AnnotationMetadata)} 的逻辑是
 *  1. 读取 META-INF/spring.factories 获取key为BootstrapConfiguration的值
 *  2. 获取属性 spring.cloud.bootstrap.sources 的值
 *  3. 合并第一第二的值,然后排序
 *  4. 会将值注册到容器中,作为容器的配置类
 *
 * 而
 * spring-cloud-context.jar!/META-INF/spring.factories 中定义了
 *  org.springframework.cloud.bootstrap.BootstrapConfiguration = org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration
 *
 * 也就是 {@link PropertySourceBootstrapConfiguration} 会注册到 bootstrapContext 中,
 * 它是 ApplicationContextInitializer 类型的,最终会用来初始化 context
 * */

PropertySourceBootstrapConfiguration

示例代码

java
public class MyPropertySourceLocator implements PropertySourceLocator {

    public MyPropertySourceLocator() {
        System.out.println("MyPropertySourceLocator...构造器");
    }

    @Resource
    private ApplicationContext applicationContext;

    @Value("${dynamicConfigFile}")
    private String filePath;

    @Override
    public PropertySource<?> locate(Environment environment) {
        PropertySource<?> propertySource;
        try {
            // 也可以改成网络资源
            propertySource = new YamlPropertySourceLoader()
                    .load("haitao-propertySource", applicationContext.getResource(filePath)).get(0);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return propertySource;
    }

}

META-INF/spring.factories

properties
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
  cn.haitaoss.BootstrapProperties.BootstrapConfiguration.MyPropertySourceLocator
java
/**
 * PropertySourceBootstrapConfiguration 是用来初始化IOC容器的,其初始化逻辑是扩展IOC容器的Environment,
 * 可以自定义 PropertySourceLocator 用来扩展 Environment
 *
 * {@link PropertySourceBootstrapConfiguration#initialize(ConfigurableApplicationContext)}
 *
 * 1. 通过依赖注入对属性赋值
 *     @Autowired(required = false)
 *     private List propertySourceLocators = new ArrayList<>();
 *
 * 2. 排序
 *      AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
 *
 * 3. 遍历 propertySourceLocators , 然后回调方法得到 PropertySource 收集起来
 *      for (PropertySourceLocator locator : this.propertySourceLocators) {
 *          Collection> source = locator.locateCollection(environment);
 *          sourceList.addAll(source);
 *      }
 *
 * 4. 根据属性值决定插入到Environment的顺序
 *      spring.cloud.config.overrideSystemProperties 默认是 true
 *      spring.cloud.config.allowOverride 默认是 true
 *      spring.cloud.config.overrideNone 默认是 false
 *
 *       if !allowOverride || (!overrideNone && overrideSystemProperties)
 *          通过 PropertySourceLocator 得到的 PropertySource 会添加到最前面,也就是优先生效
 *       else if overrideNone
 *          通过 PropertySourceLocator 得到的 PropertySource 会添加到最后面,也就是兜底生效
 *       else if !overrideSystemProperties
 *          通过 PropertySourceLocator 得到的 PropertySource 会放在 systemEnvironment 的后面
 *       else if overrideSystemProperties
 *          通过 PropertySourceLocator 得到的 PropertySource 会放在 systemEnvironment 的前面
 *       else
 *          通过 PropertySourceLocator 得到的 PropertySource 会添加到最后面,也就是兜底生效
 *
 *      注:也就是可以通过这三个属性值,决定最终 Environment 属性的读取顺序
 *
 * */

@RefreshScope 和 @ConfigurationProperties bean 的更新

示例代码

java
@SpringBootApplication
public class Main {

	/**
	 * 总结用法:
	 *
	 * 可以通过属性 spring.cloud.refresh.refreshable spring.cloud.refresh.extraRefreshable
	 * 代替 @RefreshScope
	 *
	 * 可以设置属性 spring.cloud.refresh.enabled=false 取消 @RefreshScope 的自动注入 是
	 * spring.cloud.refresh.never-refreshable 属性记录的类就不重会新绑定属性
	 */
	public static void main(String[] args) {
		// TODOHAITAO: 2023/4/6 访问验证属性更新 GET http://127.0.0.1:8080/actuator/refresh
		// 启用 bootstrap 属性的加载
		System.setProperty("spring.cloud.bootstrap.enabled", "true");

        // 通过配置属性的方式,扩展bean为 refresh scope 的
		System.setProperty("spring.cloud.refresh.refreshable",
				Arrays.asList(RefreshScopeBean1.class.getName(), RefreshScopeBean2.class.getName()).stream()
						.collect(Collectors.joining(",")));
		System.setProperty("spring.cloud.refresh.extraRefreshable",
				Arrays.asList(Object.class.getName()).stream().collect(Collectors.joining(",")));

        // 设置 bootstrapContext 会默认加载的 bean
        System.setProperty("spring.cloud.bootstrap.sources","cn.haitaoss.RefreshScope.config.MyPropertySourceLocator");
	}

}
java
/**
 * 只是列举了我觉得比较重要的,并不是全部内容
 * spring-cloud-context.jar!/META-INF/spring.factories
 *
 * org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
 * org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
 * org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\
 * org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration
 *
 * org.springframework.context.ApplicationListener=\
 * org.springframework.cloud.bootstrap.BootstrapApplicationListener
 *
 * org.springframework.cloud.bootstrap.BootstrapConfiguration=\
 * org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration
 * */

ConfigurationPropertiesRebinderAutoConfiguration

  1. 记录 @ConfigurationProperties 的 bean
  2. 接收 EnvironmentChangeEvent 事件,对记录的 bean 进行重新初始化从而完成属性的更新
java
/**
 * ConfigurationPropertiesRebinderAutoConfiguration
 *      注册 ConfigurationPropertiesBeans :
 *          - 是 BeanPostProcessor 其作用是记录有 @ConfigurationProperties注解 标注的bean
 *
 *      注册 ConfigurationPropertiesRebinder :
 *          - 实现了 ApplicationListener<EnvironmentChangeEvent> 接口,还依赖 ConfigurationPropertiesBeans。
 *          收到事件的处理逻辑是,遍历 ConfigurationPropertiesBeans 记录的 bean 对其进行重新绑定。其实就是 回调bean的销毁方法,然后对bean重新初始化而已,
 *          而不是直接从BeanFactory中删除bean。
 *
 *          注:
 *              1. 会过滤掉是 refresh scope 的bean,因为 refresh 作用域的bean由其他类进行刷新
 *              2. 是 spring.cloud.refresh.never-refreshable 属性记录的类就不要重新绑定属性
 *
 *      实现了 SmartInitializingSingleton ,会在 单例bean实例化完后被回调,其回调方法的逻辑是使用 ConfigurationPropertiesRebinder 对父容器中的bean进行重新绑定
 *
 * */

RefreshAutoConfiguration

  1. 注册 refresh scope 到 BeanFactory 中
  2. 接收 RefreshEvent 事件,更新 Environment 和 清空 refresh scope 中记录的 bean
  3. 使用 @RefreshScope 标注的 bean,最终生成的是代理对象,每次执行代理对象的方法,都会从 refresh scope 中获取 bean 得到调用方法的对象,从而能保证更新之后,获取的对象也是新的
text
/* Detailed source-code truncated for AI context efficiency. */

RefreshEndpointAutoConfiguration

java
/**
 * RefreshEndpointAutoConfiguration
 *      注册 RefreshEndpoint :
 *          只定义了一个 @WriteOperation , 该方法的逻辑是会执行 ContextRefresher#refresh 方法,从而 更新 Environment,刷新 refresh 作用域
 *          POST http://localhost:8080/actuator/refresh 可以触发该操作
 *
 *          注:得导入 spring-boot-starters-actuator 才会生效
 *
 *      ...还注册了很多 bean , 暂时不看了 , 用到再看吧
 * */

@EnableDiscoveryClient

java
/**
 * {@link EnableDiscoveryClient}
 *      @EnableDiscoveryClient(autoRegister=true)
 *      1. 的目的是将 META-INF/spring.factories 中 key 是 `EnableDiscoveryClient.class.getName()` 的类注册到 BeanFactory 中
 *      2. 若 autoRegister==true , 还会注册 AutoServiceRegistrationConfiguration 到 BeanFactory 中,
 *          这个类的目的很简单,就是注册 AutoServiceRegistrationProperties 到BeanFactory中
 *      3. 若 autoRegister==false, 会设置属性 spring.cloud.service-registry.auto-registration.enabled 为 false,
 *          这个为 false 就会导致 AutoServiceRegistrationAutoConfiguration 的 @conditional注解不匹配,从而不会生效
 *
 *      总结:注册 AutoServiceRegistrationProperties 和 META-INF/spring.factories 中的类 到容器中
 *
 *
 * 其实 @EnableDiscoveryClient 没啥用了,因为 spring-cloud-commons.jar!/META-INF/spring.factories 中声明了
 *      org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
 *          org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration
 * 也就是 AutoServiceRegistrationAutoConfiguration 会自动注入。但是需要 DiscoveryClient 的bean能被发现,
 * 必须得自己注册到BeanFactory中,不能使用 META-INF/spring.factories 设置 key 是 `EnableDiscoveryClient.class.getName()`
 *
 * {@link AutoServiceRegistrationAutoConfiguration}
 *      通过 @PostConstruct 的方式让校验方法被调用,是用来校验 AutoServiceRegistration 类型的bean是否存在,但是默认是不会报错的
 *      可以设置 spring.cloud.service-registry.auto-registration.failFast=true 的方式,启用校验。
 *
 * 总结:@EnableDiscoveryClient 的核心功能是可以将声明在 META-INF/spring.factories 中的类注册到容器中(key是 `EnableDiscoveryClient.class.getName()`)
 * */

@LoadBalanced

java
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface LoadBalanced {}
java
/**
 * @LoadBalanced 的作用
 *  1. 其本质是一个 @Qualifier 。在依赖注入的过滤候选bean时会校验 @Qualifier 的值
 *  2. 作为标记注解,比如 LoadBalancerWebClientBuilderBeanPostProcessor 会过滤出有 @LoadBalanced 的bean进行处理
 * */
java
public class Config {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public RestTemplate restTemplate2() {
        return new RestTemplate();
    }
}

public class LoadBalancerAutoConfiguration {

   // 只会注入 restTemplate 不会注入 restTemplate2
   @LoadBalanced
   @Autowired(required = false)
   private List<RestTemplate> restTemplates = Collections.emptyList();

}

@LoadBalancerClient 和@LoadBalancerClients

@LoadBalancerClient 映射成 LoadBalancerClientSpecification 然后注册到 BeanFactory 中。

@LoadBalancerClients 其实就是多个 @LoadBalancerClient

java
@Configuration(proxyBeanMethods = false)
@Import(LoadBalancerClientConfigurationRegistrar.class)
public @interface LoadBalancerClient {

	@AliasFor("name")
	String value() default "";

	@AliasFor("value")
	String name() default "";

	Class<?>[] configuration() default {};
}
java
/**
 * LoadBalancerClientConfigurationRegistrar 是 ImportBeanDefinitionRegistrar 的实现类,所以IOC容器解析到 @Import(LoadBalancerClientConfigurationRegistrar.class)
 * 会回调其方法
 * {@link LoadBalancerClientConfigurationRegistrar#registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)}
 *  1. 获取注解的元数据信息
 *      Map<String, Object> client = metadata.getAnnotationAttributes(LoadBalancerClient.class.getName(), true);
 *
 *  2. 获取name,获取的是注解的value属性值或者是注解的name属性值
 *      String name = getClientName(client);
 *
 *  3. 映射成BeanDefinition然后注册到BeanFactory中
 *
 *      BeanDefinitionBuilder builder = BeanDefinitionBuilder
 *                .genericBeanDefinition(LoadBalancerClientSpecification.class);
 *      builder.addConstructorArgValue(name);
 *      builder.addConstructorArgValue(client.get("configuration"));
 *      registry.registerBeanDefinition(name + ".LoadBalancerClientSpecification", builder.getBeanDefinition());
 *
 * 注:LoadBalancerClientSpecification 是构造 LoadBalancerClientFactory 依赖的bean
 * */

DiscoveryClient

spring-cloud-commons.jar!/META-INF/spring.factories的部分内容

properties
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration,\
    org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration,\

DiscoveryClient 是用来获取注册中心注册了多少实例,单独看是没用的得结合 负载均衡的实现逻辑 才能明白。

java
/**
 * SimpleDiscoveryClientAutoConfiguration
 *      注册 SimpleDiscoveryProperties , 通过 @ConfigurationProperties 将配置文件中的信息绑定到属性中
 *      注册 SimpleDiscoveryClient 是 DiscoveryClient 接口的实现类, 其依赖 SimpleDiscoveryProperties , 其职责是根据 serviceId 返回 List<ServiceInstance>
 *
 * CompositeDiscoveryClientAutoConfiguration
 *      注册 CompositeDiscoveryClient 是 DiscoveryClient 的实现类,其作用是用来聚合 List<DiscoveryClient> 的
 **/

LoadBalancerAutoConfiguration

spring-cloud-commons.jar!/META-INF/spring.factories的部分内容

properties
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
   org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\

这个配置类主要是定义了 LoadBalancerInterceptor 用来拦截 RestTemplate 的执行,其拦截逻辑是委托给

LoadBalancerClient 来做。又定义了 LoadBalancerRequestFactory 用于生成 LoadBalancerClient 的参数,

而 LoadBalancerRequestFactory 会使用 LoadBalancerRequestTransformer 对 HttpRequest 进行增强,

所以我们可以自定义 LoadBalancerRequestTransformer 的 bean 对 负载均衡的请求 进行修改。

java
/**
 * org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration
 *      满足这个条件 @ConditionalOnBean(LoadBalancerClient.class) 配置类才会生效,而 LoadBalancerClient 唯一的实现在
 *      spring-cloud-loadbalancer.jar 中。LoadBalancerClient 是用来执行请求的,让请求变成负载均衡的方式
 *
 *      注册 SmartInitializingSingleton,其逻辑是使用 RestTemplateCustomizer 对 @LoadBalanced的RestTemplate 进行自定义
 *
 *      注册 LoadBalancerRequestFactory,其依赖 LoadBalancerClient 和 List<LoadBalancerRequestTransformer>
 *          是用来生成 LoadBalancerRequest 的(其实就是使用 LoadBalancerRequestTransformer 对 HttpRequest 进行增强)
 *            `LoadBalancerRequest<ClientHttpResponse> request = requestFactory.createRequest(rawRequest, body, execution);
 *             LoadBalancerClient.execute(serviceName, request);`
 *
 *      注册 LoadBalancerInterceptor 是 ClientHttpRequestInterceptor 接口的实现类,其依赖于 LoadBalancerClient、LoadBalancerRequestFactory
 *          ClientHttpRequestInterceptor 是用来拦截 RestTemplate 执行请求的
 *
 *      注册 RestTemplateCustomizer 依赖 LoadBalancerInterceptor , 会将 LoadBalancerInterceptor 设置给 RestTemplate
 *          List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
 *          list.add(loadBalancerInterceptor);
 *          restTemplate.setInterceptors(list);
 **/

LoadBalancerClient

示例代码

java
@EnableAutoConfiguration
@RestController
@Import({ LoadBalancerClientConfig.class, LoadBalancerOtherConfig.class })
public class Main extends BaseApp {

	public static void main(String[] args) {
		/**
		 * TODOHAITAO: 2023/4/7 验证方式 运行 Main、Client1、Client2 然后访问:
         * - 堵塞式 GET http://localhost:8080/s1
         * - 响应式 GET http://localhost:8080/2/s1
		 */
		// 采用那种方式对 RestTemplate 进行增强,看
		// org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration
		System.setProperty("spring.cloud.loadbalancer.retry.enabled", "false");
		System.setProperty("spring.profiles.active", "loadbalance");
		ConfigurableApplicationContext context = SpringApplication.run(Main.class);
	}

}

负载均衡会使用 LoadBalancerClient 来执行请求的,大致逻辑是通过 DiscoveryClient 得到 serviceId 有哪些实例,再通过负载均衡策略的逻辑筛选出唯一的实例,然后根据这个实例的 url 执行请求。

spring-cloud-loadbalancer.jar!/META-INF/spring.factories的部分内容

properties
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration,\
    org.springframework.cloud.loadbalancer.config.LoadBalancerStatsAutoConfiguration,\
    org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration

LoadBalancerAutoConfiguration

提供了@LoadBalancerClient 用于简易的注册 LoadBalancerClientSpecification

java
/**
 * org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration
 *
 *      注册 LoadBalancerZoneConfig , ZonePreferenceServiceInstanceListSupplier 会依赖这个bean来过滤出 List<ServiceInstance>
 *
 *      注册 LoadBalancerClientFactory 其继承 NamedContextFactory。
 *          会根据 name 创建一个IOC容器,IOC容器默认有两个配置类:PropertyPlaceholderAutoConfiguration、LoadBalancerClientConfiguration
 *          并依赖 LoadBalancerClientSpecification 用来扩展配置类,创建的IOC容器会缓存到Map中。
 *
 *          LoadBalancerClientConfiguration 其目的是注册了 ServiceInstanceListSupplier、ReactorLoadBalancer<ServiceInstance>
 *          这两个bean是用来实现负载均衡策略得到唯一的 ServiceInstance 的。而且都有 @ConditionalOnMissingBean 条件,若我们想自定义
 *          可以设置 LoadBalancerClientSpecification 扩展配置类。
 *
 *          可以使用 @LoadBalancerClient 或者直接注册 LoadBalancerClientSpecification 类型的bean到容器中,
 **/

LoadBalancerStatsAutoConfiguration

一般来说 LoadBalancerClient 执行负载均衡请求时会 回调 LoadBalancerLifecycle 接口的方法

java
/**
 * LoadBalancerStatsAutoConfiguration
 *      注册 MicrometerStatsLoadBalancerLifecycle 其实现了 LoadBalancerLifecycle 接口。比如 BlockingLoadBalancerClient
 *      执行请求时会回调 LoadBalancerLifecycle 的方法做通知
 * */

BlockingLoadBalancerClientAutoConfiguration

注册 LoadBalancerClient 的实现类到容器中

java
/**
 * BlockingLoadBalancerClientAutoConfiguration
 *      注册 BlockingLoadBalancerClient 其实现了LoadBalancerClient接口,依赖 LoadBalancerClientFactory 和 properties
 *          BlockingLoadBalancerClient 的核心逻辑是接收 HttpRequest 解析 Uri 得到 serviceId ,然后使用 serviceId 负载均衡得到唯一的 serviceInstance
 *          然后再执行 HttpRequest
 *
 * */

负载均衡的 RestTemplate 执行请求的流程

使用 restTemplate 发送请求,最终会委托给 ClientHttpRequestInterceptor 执行请求

properties
# 大致是这么个流程,可以细看下面的代码分析
RestTemplate -> (RetryLoadBalancerInterceptor|LoadBalancerInterceptor)
 -> LoadBalancerClientFactory ->  BlockingLoadBalancerClient
 -> ReactorLoadBalancer -> ServiceInstanceListSupplier -> DiscoveryClient
 -> LoadBalancerRequestTransformer
 -> 执行请求
java
/**
 * 例如:
 * restTemplate.getForEntity("http://serviceName/xx", String.class)
 *
 * {@link RestTemplate#getForEntity(String, Class, Object...)}
 * {@link RestTemplate#execute(String, HttpMethod, RequestCallback, ResponseExtractor, Object...)}
 * {@link RestTemplate#doExecute(URI, HttpMethod, RequestCallback, ResponseExtractor)}
 * {@link AbstractClientHttpRequest#execute()}
 * {@link AbstractBufferingClientHttpRequest#executeInternal(HttpHeaders)}
 * {@link InterceptingClientHttpRequest#executeInternal(HttpHeaders, byte[])}
 * {@link InterceptingClientHttpRequest.InterceptingRequestExecution#execute(HttpRequest, byte[])}
 *      在这里回调 ClientHttpRequestInterceptor 的方法,从而实现拦截请求的执行
 **/
java
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
    if (this.iterator.hasNext()) {
        ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
        // 委托给迭代器执行逻辑。比如 LoadBalancerInterceptor 其实就是修改 request 然后又递归回调该方法
        return nextInterceptor.intercept(request, body, this);
    }
    else {
        HttpMethod method = request.getMethod();
        Assert.state(method != null, "No standard HTTP method");
        ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
        // 拷贝请求头内容
        request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
        // 执行请求
        return delegate.execute();
    }
}

LoadBalancerInterceptor

默认注入的是 RetryLoadBalancerInterceptor 而不是 LoadBalancerInterceptor。可以设置 spring.cloud.loadbalancer.retry.enabled=false 让 LoadBalancerInterceptor 生效

java
/**
 * LoadBalancerInterceptor 的执行逻辑
 * {@link LoadBalancerInterceptor#intercept(HttpRequest, byte[], ClientHttpRequestExecution)}
 *  1. 获取serviceName
 *      final URI originalUri = request.getURI();
 *      String serviceName = originalUri.getHost();
 *
 *  2. 使用 LoadBalancerRequestFactory 构造出 LoadBalancerRequest,构造逻辑其实就是使用 LoadBalancerRequestTransformer 对 HttpRequest 进行增强
 *     LoadBalancerRequest lbRequest = requestFactory.createRequest(request, body, execution);
 *
 *  3. 委托给 LoadBalancerClient 执行请求
 *      loadBalancerClient.execute(serviceName, lbRequest)
 *      默认是这个实现类的方法 {@link BlockingLoadBalancerClient#execute(String, LoadBalancerRequest)}
 *
 **/

RetryLoadBalancerInterceptor

整体逻辑和 LoadBalancerInterceptor 是一样的,只不过是使用 RetryTemplate 来执行,根据重试策略重复执行而已。

会使用 LoadBalancedRetryFactory 来生成 LoadBalancedRetryPolicy、BackOffPolicy、RetryListener 这三个东西是用来决定该如何重试,默认是有一个 BlockingLoadBalancedRetryPolicy 会根据属性信息生成 LoadBalancedRetryPolicy、BackOffPolicy,若我们有需要可以自定义 LoadBalancedRetryFactory bean 注册到容器中,因为

java
@Configuration
public class BlockingLoadBalancerRetryConfig {
  @Bean
  @ConditionalOnMissingBean
  LoadBalancedRetryFactory loadBalancedRetryFactory(LoadBalancerProperties properties) {
    return new BlockingLoadBalancedRetryFactory(properties);
  }
}
properties
# 指示应在同一ServiceInstance 上重试请求的次数(对每个选定实例单独计数)
spring.cloud.loadbalancer.retry.maxRetriesOnSameServiceInstance=1
# 指示新选择的 ServiceInstance 应重试请求的次数
spring.cloud.loadbalancer.retry.maxRetriesOnNextServiceInstance=1
# 总是重试失败请求的状态代码
spring.cloud.loadbalancer.retry.retryableStatusCodes=1
# 设置最小回退持续时间(默认为5毫秒)
spring.cloud.loadbalancer.retry.backoff.minBackoff=1
# 设置最大回退持续时间(默认为最大长值毫秒)
spring.cloud.loadbalancer.retry.backoff.maxBackoff=1
# 设置用于计算每个调用的实际回退持续时间的抖动(默认为0.5)
spring.cloud.loadbalancer.retry.backoff.jitter=1
text
/* Detailed source-code truncated for AI context efficiency. */

BlockingLoadBalancerClient#execute

  1. 执行请求过程中会回调 LoadBalancerLifecycle 生命周期方法
  2. 负载均衡得到 serviceInstance 构造出 HttpRequest 后,会使用 LoadBalancerRequestTransformer 对 HttpRequest 进行增强
text
注:不建议将 LoadBalancerLifecycle、ServiceInstanceListSupplier、ReactorLoadBalancer 注册到应用程序中,而是通过 LoadBalancerClientSpecification 的方式为每一个 serviceInstance 设置独立的bean,从而实现不同的 serviceInstance 使用不同的 负载均衡策略
text
/* Detailed source-code truncated for AI context efficiency. */

ReactorLoadBalancer

示例代码

java
@LoadBalancerClient(name = "s1", configuration = { MyLoadBalancer.class, MyServiceInstanceListSupplier.class })
@LoadBalancerClients({ @LoadBalancerClient(name = "s2", configuration = MyRandomLoadBalancer.class),
		@LoadBalancerClient(name = "s3", configuration = MyRoundRobinLoadBalancer.class), })
public class LoadBalancerClientConfig {

}
java
/**
 * ReactorLoadBalancer 是一个负载均衡器,会根据其负载均衡逻辑从 ServiceInstanceListSupplier 返回的 List<ServiceInstance> 中筛选出唯一的 ServiceInstance。
 *
 * 再执行负载均衡请求时会用到 ReactorLoadBalancer
 *      {@link BlockingLoadBalancerClient#execute(String, LoadBalancerRequest)}
 *      ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
 *
 * 而 LoadBalancerClientFactory 继承 NamedContextFactory,获取实例的特点是 每个serviceId对应一个IOC容器,实例是从对应的IOC容器中得到的,
 * LoadBalancerClientFactory 构造的IOC容器默认会注册配置类 LoadBalancerClientConfiguration,
 * 而 LoadBalancerClientConfiguration 其目的是注册了 ServiceInstanceListSupplier、ReactorLoadBalancer<ServiceInstance>
 * 而且都有 @ConditionalOnMissingBean 条件,若我们想自定义可以给IOC容器设置配置类,从而让 @ConditionalOnMissingBean 不匹配。
 * 可以使用 @LoadBalancerClient 或者直接注册 LoadBalancerClientSpecification 类型的bean到容器中。
 * */
java
public interface ReactorLoadBalancer<T> extends ReactiveLoadBalancer<T> {

   /**
    * Choose the next server based on the load balancing algorithm.
    * @param request - an input request
    * @return - mono of response
    */
   @SuppressWarnings("rawtypes")
   Mono<Response<T>> choose(Request request);

   default Mono<Response<T>> choose() {
      return choose(REQUEST);
   }

}
java
// 这只是伪代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class LoadBalancerClientConfiguration {

   @Bean
   @ConditionalOnMissingBean
   public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
         LoadBalancerClientFactory loadBalancerClientFactory) {
      String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
      return new RoundRobinLoadBalancer(
            loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
   }

   @Bean
   @ConditionalOnMissingBean
   public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
       return ServiceInstanceListSupplier.builder().withDiscoveryClient().withCaching().build(context);
   }

}

ServiceInstanceListSupplier

示例代码

java
public class MyServiceInstanceListSupplier {

    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                //.withDiscoveryClient() // 通过 ReactiveDiscoveryClient 获取 List<ServiceInstance>
                .withBlockingDiscoveryClient() // 通过 DiscoveryClient 获取 List<ServiceInstance>
                // 下面配置的是通过什么方式 过滤 List<ServiceInstance>
                // .withZonePreference() // spring.cloud.loadbalancer.zone" 属性值与 serviceInstance.getMetadata().get("zone") 进行匹配
                // .withBlockingHealthChecks() // spring.cloud.loadbalancer.healthCheck.* 属性定义的的规则来过滤
                // .withRequestBasedStickySession() spring.cloud.loadbalancer.stickySession.instanceIdCookieName 属性值过滤 serviceInstance.getInstanceId()
                // .withSameInstancePreference()
                .withCaching() // 会使用到 LoadBalancerCacheManager 缓存 List<ServiceInstance>
                .build(context);
    }

}
java
/**
 * ServiceInstanceListSupplier 是用来返回 List<ServiceInstance> 的。一般是根据 ReactiveDiscoveryClient 或者 DiscoveryClient 得到 serviceId 注册的 List<ServiceInstance>,
 * 然后根据其逻辑过滤掉不满足的,再返回最终的 List<ServiceInstance>
 *
 * 提供了Builder快速构建
 *      响应式(获取 ReactiveDiscoveryClient 的bean得到有哪些服务):ServiceInstanceListSupplier.builder().withDiscoveryClient().build(context);
 *      非响应式(获取 DiscoveryClient 的bean得到有哪些服务):ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().build(context);
 *
 *
 * 这个 LoadBalancerClientConfiguration 配置类,定义了很多种 ServiceInstanceListSupplier。可以通过设置属性值决定应用哪种
 *      spring.cloud.loadbalancer.configurations=[default | zone-preference | health-check | request-based-sticky-session | same-instance-preference]
 * */
java
public interface ServiceInstanceListSupplier extends Supplier<Flux<List<ServiceInstance>>> {

    String getServiceId();

    default Flux<List<ServiceInstance>> get(Request request) {
        return get();
    }

    static ServiceInstanceListSupplierBuilder builder() {
        return new ServiceInstanceListSupplierBuilder();
    }

}

WebClient.Builder 实现负载均衡

WebClient.Builder 是执行响应式请求的工具类。下面是让 WebClient.Builder 具有负载均衡能力的实现逻辑。

spring-cloud-commons.jar!/META-INF/spring.factories的部分内容

properties
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
	org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration,\
	org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration
java
/**
 * {@link LoadBalancerBeanPostProcessorAutoConfiguration}
 *      注册 LoadBalancerWebClientBuilderBeanPostProcessor 其依赖 DeferringLoadBalancerExchangeFilterFunction。
 *          其是一个后置处理器,会在 postProcessBeforeInitialization 时过滤类型是 WebClient.Builder 且有 @LoadBalanced 的bean ,为 bean 增加filter
 *          `((WebClient.Builder) bean).filter(DeferringLoadBalancerExchangeFilterFunction)`
 *
 *      注册 DeferringLoadBalancerExchangeFilterFunction 其依赖 LoadBalancedExchangeFilterFunction,没啥用具体逻辑是委托给 LoadBalancedExchangeFilterFunction 执行的。
 *
 * {@link ReactorLoadBalancerClientAutoConfiguration}
 *      注册 ReactorLoadBalancerExchangeFilterFunction 其依赖 ReactiveLoadBalancer.Factory、LoadBalancerProperties。
 *          其定义了负载均衡的实现逻辑,比如要回调 LoadBalancerLifecycle 的方法,要通过 ReactiveLoadBalancer.Factory 负载均衡得到 ServiceInstance
 *
 *          Tips:LoadBalancerClientFactory 是 ReactiveLoadBalancer.Factory  的实现类。
 *
 *      注册 RetryableLoadBalancerExchangeFilterFunction 其依赖 ReactiveLoadBalancer.Factory、LoadBalancerProperties、LoadBalancerRetryPolicy。
 *          大致逻辑和 ReactorLoadBalancerExchangeFilterFunction 一样,只不过增加了重试的实现
 *
 *      注册 LoadBalancerRetryPolicy 其依赖 LoadBalancerProperties。
 *          就是重试策略,没啥特别的
 *
 *
 * 总结:通过 LoadBalancerWebClientBuilderBeanPostProcessor 给 WebClient.Builder 增加 Filter,所以使用 WebClient.Builder 执行请求时会执行 Filter 的逻辑。
 *      DeferringLoadBalancerExchangeFilterFunction 的逻辑是 回调 LoadBalancerLifecycle 的方法,使用 LoadBalancerClientFactory 生成的负载均衡器得到唯一的 ServiceInstance,
 *      根据 ServiceInstance 的信息 修改请求的信息,从而实现负载均衡请求。
 *
 * WebClient.Builder -> ReactorLoadBalancerExchangeFilterFunction -> LoadBalancerLifecycle -> LoadBalancerClientFactory
 * */

File: docs/SpringCloud/spring-cloud-gateway-source-note.md

说明

Author: haitaoss

源码阅读仓库: spring-cloud-gateway

参考资料和需要掌握的知识:

Spring Cloud Gateway 介绍

功能:接收请求并根据匹配的路由进行转发。

术语:

  • Route: 是路由规则的描述。它由 ID、目标 URI、Predicate 集合和Filter 集合组成。如果 Predicate 为真,则路由匹配。
  • Predicate: 这是一个 Java 8 函数接口。输入类型是 ServerWebExchange ,所以可以匹配 HTTP 请求中的任何内容,例如 Header 或参数。
  • Filter: 这些是使用特定工厂构建的 GatewayFilter 的实例。使用这个可以在发送下游请求之前或之后修改请求和响应。

Spring Cloud Gateway 是基于 Spring WebFlux 实现的,是通过注册 WebFlux 的生命周期组件实现控制请求执行。

properties
# Spring WebFlux 处理请求的生命周期
客户端请求 -> WebFlux服务 -> WebFilter -> DispatcherHandler -> HandlerMapping -> HandlerAdapter -> 执行Handler方法

Gateway 通过注册 RoutePredicateHandlerMapping 实现核心逻辑

Gateway 自动装配

spring-cloud-gateway-server.jar!META-INF/spring.factories的部分内容

properties
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration,\
    org.springframework.cloud.gateway.config.GatewayAutoConfiguration,\
    org.springframework.cloud.gateway.config.GatewayResilience4JCircuitBreakerAutoConfiguration,\
    org.springframework.cloud.gateway.config.GatewayNoLoadBalancerClientAutoConfiguration,\
    org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration,\
    org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration,\
    org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration,\
    org.springframework.cloud.gateway.config.SimpleUrlHandlerMappingGlobalCorsAutoConfiguration,\
    org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration,\
    org.springframework.cloud.gateway.config.GatewayReactiveOAuth2AutoConfiguration

spring-cloud-gateway-webflux.jar!META-INF/spring.factories的内容

properties
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.gateway.webflux.config.ProxyResponseAutoConfiguration

org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebFlux=\
org.springframework.cloud.gateway.webflux.config.ProxyResponseAutoConfiguration

spring-cloud-gateway-mvc.jar!META-INF/spring.factories的内容

properties
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.gateway.mvc.config.ProxyResponseAutoConfiguration

org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc=\
org.springframework.cloud.gateway.mvc.config.ProxyResponseAutoConfiguration

GatewayClassPathWarningAutoConfiguration

作用:检验启动环境不能是 SpringMVC

java
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
public class GatewayClassPathWarningAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    // SpringMVC 会存在这个类,所以条件会满足,这个类就会注册到BeanFactory中
    @ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet")
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    protected static class SpringMvcFoundOnClasspathConfiguration {

        public SpringMvcFoundOnClasspathConfiguration() {
            // 实例化就直接抛出异常
            throw new MvcFoundOnClasspathException();
        }

    }
}

GatewayAutoConfiguration

类图

RouteLocator 是为了得到Flux<Route>,可以使用 RouteLocatorBuilder 很方便的生成 RouteLocator。

RouteDefinitionRouteLocator 是会根据 RouteDefinition 生成 Route ,而 RouteDefinition 是由 RouteDefinitionLocator 生成的。

Route 是由 AsyncPredicate 和 GatewayFilter 组成的。而 AsyncPredicate 由 RoutePredicateFactory 生成,GatewayF 创建 ilter 由 GatewayFilterFactory

RoutePredicateHandlerMapping 通过 RouteLocator 得到的 Flux<Route> ,遍历执行Route.getPredicate().apply(ServerWebExchange) 返回true说明命中了路由规则,将命中的 Route 存到 ServerWebExchange 中,然后执行 FilteringWebHandler 。

FilteringWebHandler 的逻辑就是执行 GlobalFilter + GatewayFilter

源码

可以自定义这些类型的 bean 实现功能的扩展:RouteLocator、HttpHeaderFilter、GlobalFilter 、GatewayFilterFactory、RoutePredicateFactory

默认通过 @Bean 注册了很多的 GlobalFilter、GatewayFilterFactory、RoutePredicateFactory 且都是有条件注解的,可以通过设置属性不进行默认注册。主要是有这三个条件注解

text
/* Detailed source-code truncated for AI context efficiency. */

GatewayResilience4JCircuitBreakerAutoConfiguration

java
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@ConditionalOnClass({ DispatcherHandler.class, ReactiveResilience4JAutoConfiguration.class,
        ReactiveCircuitBreakerFactory.class, ReactiveResilience4JCircuitBreakerFactory.class })
public class GatewayResilience4JCircuitBreakerAutoConfiguration {

    /**
     * SpringCloudCircuitBreakerResilience4JFilterFactory 实现 GatewayFilterFactory 接口,
     * 其核心逻辑是使用 ReactiveCircuitBreaker 来执行业务逻辑,当 出现异常 或者 路由请求返回的状态码是期望值 就
     * 直接使用 DispatcherHandler 来执行 fallbackUrl,可以理解成使用 fallbackUrl 重新执行一次请求。
     * 并且会往 ServerWebExchange 设置一个key记录异常对象。
     */
    @Bean
    @ConditionalOnBean(ReactiveResilience4JCircuitBreakerFactory.class)
    @ConditionalOnEnabledFilter
    public SpringCloudCircuitBreakerResilience4JFilterFactory springCloudCircuitBreakerResilience4JFilterFactory(
            ReactiveResilience4JCircuitBreakerFactory reactiveCircuitBreakerFactory,
            ObjectProvider<DispatcherHandler> dispatcherHandler) {
        return new SpringCloudCircuitBreakerResilience4JFilterFactory(reactiveCircuitBreakerFactory, dispatcherHandler);
    }

    /**
     * FallbackHeadersGatewayFilterFactory 实现 GatewayFilterFactory 接口,
     * 其核心逻辑:如果请求是 fallbackUrl 执行的(通过异常key判断),那就设置一些请求头
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnEnabledFilter
    public FallbackHeadersGatewayFilterFactory fallbackHeadersGatewayFilterFactory() {
        return new FallbackHeadersGatewayFilterFactory();
    }

}

GatewayNoLoadBalancerClientAutoConfiguration

text
/* Detailed source-code truncated for AI context efficiency. */

GatewayMetricsAutoConfiguration

text
/* Detailed source-code truncated for AI context efficiency. */

GatewayRedisAutoConfiguration

java
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(ReactiveRedisTemplate.class)
@ConditionalOnClass({ RedisTemplate.class, DispatcherHandler.class })
@ConditionalOnProperty(name = "spring.cloud.gateway.redis.enabled", matchIfMissing = true)
class GatewayRedisAutoConfiguration {

    /**
     * RedisRouteDefinitionRepository 实现 RouteDefinitionRepository 接口。
     * 使用 redis 作为缓存层,存储 RouteDefinition
     */
    @Bean
    @ConditionalOnProperty(value = "spring.cloud.gateway.redis-route-definition-repository.enabled",
            havingValue = "true")
    @ConditionalOnClass(ReactiveRedisTemplate.class)
    public RedisRouteDefinitionRepository redisRouteDefinitionRepository(
            ReactiveRedisTemplate<String, RouteDefinition> reactiveRedisTemplate) {
        return new RedisRouteDefinitionRepository(reactiveRedisTemplate);
    }

    // ...
}

GatewayDiscoveryClientAutoConfiguration

text
/* Detailed source-code truncated for AI context efficiency. */

SimpleUrlHandlerMappingGlobalCorsAutoConfiguration

java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SimpleUrlHandlerMapping.class)
@ConditionalOnProperty(name = "spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping",
        matchIfMissing = false)
public class SimpleUrlHandlerMappingGlobalCorsAutoConfiguration {

    @Autowired
    private GlobalCorsProperties globalCorsProperties;

    @Autowired
    private SimpleUrlHandlerMapping simpleUrlHandlerMapping;

    /**
     * 为 SimpleUrlHandlerMapping 配置 跨域配置信息
     */
    @PostConstruct
    void config() {
        simpleUrlHandlerMapping.setCorsConfigurations(globalCorsProperties.getCorsConfigurations());
    }

}

GatewayReactiveLoadBalancerClientAutoConfiguration

java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ReactiveLoadBalancer.class, LoadBalancerAutoConfiguration.class, DispatcherHandler.class })
@EnableConfigurationProperties(GatewayLoadBalancerProperties.class)
public class GatewayReactiveLoadBalancerClientAutoConfiguration {

    /**
     * ReactiveLoadBalancerClientFilter 实现 GlobalFilter 接口。
     * 作用:url 没有 lb 协议 就放行,有 lb 就使用 LoadBalancerClientFactory 得到负载均衡后的 uri,修改 ServerWebExchange 放行filter
     */
    @Bean
    @ConditionalOnBean(LoadBalancerClientFactory.class)
    @ConditionalOnMissingBean(ReactiveLoadBalancerClientFilter.class)
    @ConditionalOnEnabledGlobalFilter
    public ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory,
                                                                            GatewayLoadBalancerProperties properties) {
        return new ReactiveLoadBalancerClientFilter(clientFactory, properties);
    }

    /**
     * LoadBalancerServiceInstanceCookieFilter 实现 GlobalFilter 接口
     * 作用:是负载均衡的路由,就添加一个Cookie键值对
     */
    @Bean
    @ConditionalOnBean({ ReactiveLoadBalancerClientFilter.class, LoadBalancerClientFactory.class })
    @ConditionalOnMissingBean
    @ConditionalOnEnabledGlobalFilter
    public LoadBalancerServiceInstanceCookieFilter loadBalancerServiceInstanceCookieFilter(
            LoadBalancerClientFactory loadBalancerClientFactory) {
        return new LoadBalancerServiceInstanceCookieFilter(loadBalancerClientFactory);
    }

}

GatewayReactiveOAuth2AutoConfiguration

这是 Spring Security 的组件,没研究过,不知道具体的作用是啥,暂时不管了。

ProxyResponseAutoConfiguration

ProxyResponseAutoConfiguration 是 HandlerMethodArgumentResolver 接口的实现类,是用来解析方法参数的。它支持解析 ProxyExchange 类型的参数。ProxyExchange 可用来执行 Http 请求,感觉好鸡肋...

又因为 SpringWebFlux 和 SpringMVC 的执行流程是类似的,定义的类名也是一样的(包不同),所以就搞了两套。

注:HandlerMethodArgumentResolver 是 SpringMVC 、SpringWebFlux 的内容,不细说了。

核心源码

@ConditionalOnEnabledGlobalFilter、@ConditionalOnEnabledFilter、@ConditionalOnEnabledPredicate

类的定义

java
@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.TYPE, ElementType.METHOD })
@Conditional(OnEnabledGlobalFilter.class)
public @interface ConditionalOnEnabledGlobalFilter {
    Class<? extends GlobalFilter> value() default OnEnabledGlobalFilter.DefaultValue.class;
}

@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.TYPE, ElementType.METHOD })
@Conditional(OnEnabledFilter.class)
public @interface ConditionalOnEnabledFilter {
    Class<? extends GatewayFilterFactory<?>> value() default OnEnabledFilter.DefaultValue.class;
}

@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.TYPE, ElementType.METHOD })
@Conditional(OnEnabledPredicate.class)
public @interface ConditionalOnEnabledPredicate {
    Class<? extends RoutePredicateFactory<?>> value() default OnEnabledPredicate.DefaultValue.class;
}

因为 @ConditionalOnEnabledGlobalFilter 上标注了 @Conditional,所以在 ConfigurationClassPostProcessor 解析配置类时,会执行 OnEnabledGlobalFilter#matches(ConditionContext,AnnotatedTypeMetadata) 结果是true才会将 bean 注册到 BeanFactory 中

java
/**
 * {@link Condition#matches(ConditionContext, AnnotatedTypeMetadata)}
 * {@link SpringBootCondition#matches(ConditionContext, AnnotatedTypeMetadata)}
 *      {@link OnEnabledComponent#getMatchOutcome(ConditionContext, AnnotatedTypeMetadata)}
 *          1. 拿到类型。若注解的 value 不是默认值就返回value值,否则就拿到方法的返回值类型。
 *              Class<? extends T> candidate = getComponentType(annotationClass(), context, metadata);
 *          2. 确定匹配结果。前缀 + 类处理后的值 + 后缀 作为key,从 Environment 获取值,值是false则不匹配,否则匹配
 *              determineOutcome(candidate, context.getEnvironment())
 *
 *      Tips: OnEnabledComponent 定义了三个抽象方法,由子类决定返回值是啥
 *             normalizeComponentName() 得到 类处理后的值
 *             annotationClass()   得到 注解
 *             defaultValueClass() 得到 默认值
 * */

可以通过这种方式让默认注入的失效。

properties
spring.cloud.gateway.global-filter.XX.enabled=false
spring.cloud.gateway.filter.XX.enabled=false
spring.cloud.gateway.predicate.XX.enabled=false

NettyRoutingFilter

是非常重要的 GlobalFilter。Gateway 是通过它执行 http、https 协议的请求,依赖 HttpClient 执行请求。

java
public class NettyRoutingFilter implements GlobalFilter, Ordered {

    @Override
    public int getOrder() {
        return Integer.MAX_VALUE; // 最大值,说明这是最后要执行的 GatewayFilter
    }

    @Override
    @SuppressWarnings("Duplicates")
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);

        String scheme = requestUrl.getScheme();
        // 已经路由 或者 不是 http、https 就放行
        if (isAlreadyRouted(exchange) || (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme))) {
            return chain.filter(exchange);
        }
        // 设置一个属性,标记 已经路由了
        setAlreadyRouted(exchange);

        /**
         * 遍历执行所有的 HttpHeadersFilter 得到 HttpHeaders。
         * 也就是说可以对最终要执行的 请求头 进行加工
         *
         * 注:HttpHeadersFilter 是从BeanFactory中获取的,所以我们可以自定义 HttpHeadersFilter 达到扩展的目的
         * */
        HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);

        // 根据 Route 的元数据构造 HttpClient 然后执行请求
        Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange)
                .headers()
                .responseConnection((res, connection) -> {});

        // 放行
        return responseFlux.then(chain.filter(exchange));
    }

}

WebsocketRoutingFilter

是非常重要的 GlobalFilter。Gateway 是通过它执行 ws、wss 协议的请求,依赖 WebSocketService 执行请求。

java
public class WebsocketRoutingFilter implements GlobalFilter, Ordered {
    @Override
    public int getOrder() {
        // 在 NettyRoutingFilter 之前执行
        return Integer.MAX_VALUE - 1;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 有请求头 upgrade=WebSocket ,那就将 http、https 转成 ws、wss 协议
        changeSchemeIfIsWebSocketUpgrade(exchange);

        URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
        String scheme = requestUrl.getScheme();

        // 已经路由过 或者 不是 ws、wss 协议 就放行
        if (isAlreadyRouted(exchange) || (!"ws".equals(scheme) && !"wss".equals(scheme))) {
            return chain.filter(exchange);
        }
        // 标记路由了
        setAlreadyRouted(exchange);

        HttpHeaders headers = exchange.getRequest().getHeaders();
        /**
         * 遍历执行所有的 HttpHeadersFilter 得到 HttpHeaders。
         * 也就是说可以对最终要执行的 请求头 进行加工
         *
         * 注:HttpHeadersFilter 是从BeanFactory中获取的,所以我们可以自定义 HttpHeadersFilter 达到扩展的目的
         * */
        HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);

        List<String> protocols = getProtocols(headers);

        // 使用 webSocketService 执行请求。且不在执行后续的filter
        return this.webSocketService.handleRequest(exchange,
                new ProxyWebSocketHandler(requestUrl, this.webSocketClient, filtered, protocols));
    }
}

RoutePredicateHandlerMapping

RoutePredicateHandlerMapping 是由 GatewayAutoConfiguration 注册的。

大致流程:由 RouteLocator 得到 Flux<Route> ,遍历执行 Route.getPredicate().apply(exchange) == true

就将 Route 存到 exchange 中然后返回 FilteringWebHandler,最后会由 HandlerAdapter 执行 FilteringWebHandler 。

text
/* Detailed source-code truncated for AI context efficiency. */

RouteLocator

RouteLocator 是由 GatewayAutoConfiguration 注册的,因为标注了 @Primary,所以 RoutePredicateHandlerMapping 通过依赖注入拿到的是 CachingRouteLocator。

CachingRouteLocator 是对 CompositeRouteLocator 的代理。功能:

  • 使用 ConcurrentHashMap 缓存结果。
  • 它实现 ApplicationListener<RefreshRoutesEvent> 接口,接收事件的处理逻辑是 更新缓存结果,然后发布 RefreshRoutesResultEvent 事件

CompositeRouteLocator 是用来聚合容器中所有的 RouteLocator 的,默认的 RouteLocator 是 RouteDefinitionRouteLocator

Tips:若想通过编码的方式生成 RouteLocator 可以使用 RouteLocatorBuilder。

java
public class CachingRouteLocator
        implements Ordered, RouteLocator, ApplicationListener<RefreshRoutesEvent>, ApplicationEventPublisherAware {

    private final RouteLocator delegate;
    private final Map<String, List> cache = new ConcurrentHashMap<>();

    private ApplicationEventPublisher applicationEventPublisher;

    public CachingRouteLocator(RouteLocator delegate) {
        // 是这个 CompositeRouteLocator
        this.delegate = delegate;
        // 使用 ConcurrentHashMap 缓存 Route,缓存中没有就执行 fetch 方法得到
        routes = CacheFlux.lookup(cache, CACHE_KEY, Route.class).onCacheMissResume(this::fetch);
    }

    private Flux<Route> fetch() {
        // 通过 delegate 得到,然后再对 Route 进行排序
        return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE);
    }

    @Override
    public Flux<Route> getRoutes() {
        return this.routes;
    }

    public Flux<Route> refresh() {
        // 清空缓存
        this.cache.clear();
        return this.routes;
    }

    @Override
    public void onApplicationEvent(RefreshRoutesEvent event) {
        // 收到事件,就执行 fetch() 也就是会会重新解析得到 List<Route>
        fetch().collect(Collectors.toList()).subscribe(
                list -> Flux.fromIterable(list).materialize().collect(Collectors.toList()).subscribe(signals -> {
                    // 发布 RefreshRoutesResultEvent 事件
                    applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this));
                    // 重新设置缓存内容
                    cache.put(CACHE_KEY, signals);
                }, this::handleRefreshError), this::handleRefreshError);
    }

}

RouteDefinitionRouteLocator

RouteDefinitionRouteLocator 是由 GatewayAutoConfiguration 注册的。它依赖 RouteDefinitionLocator 得到 Flux<RouteDefinition>。根据 RouteDefinition 记录的 PredicateDefinition 的 name 得到 RoutePredicateFactory<Config>, 使用 ConfigurationService 用来 实例化、属性绑定、属性校验得到泛型 Config 的实例对象,最后 RoutePredicateFactory 根据 Config 生成 AsyncPredicate。

根据 RouteDefinition 记录的 FilterDefinition 的 name 得到 GatewayFilterFactory<Config>, 使用 ConfigurationService 用来 实例化、属性绑定、属性校验得到泛型 Config 的实例对象,最后 GatewayFilterFactory 根据 Config 生成 GatewayFilter。

然后使用 AsyncPredicate、GatewayFilter 构造出 Route 实例

text
/* Detailed source-code truncated for AI context efficiency. */

RouteDefinitionLocator

RouteDefinitionLocator 是由 GatewayAutoConfiguration 注册的,因为标注了 @Primary,所以 RouteDefinitionRouteLocator 通过依赖注入拿到的是 CompositeRouteDefinitionLocator。

CompositeRouteDefinitionLocator 的作用是聚合容器中所有的 RouteDefinitionLocator。默认是注册了 PropertiesRouteDefinitionLocator 和 InMemoryRouteDefinitionRepository

PropertiesRouteDefinitionLocator

PropertiesRouteDefinitionLocator 是由 GatewayAutoConfiguration 注册的。

java
public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {
    public PropertiesRouteDefinitionLocator(GatewayProperties properties) {
        /**
         * 依赖注入得到的
         *
         * GatewayProperties 标注了 @ConfigurationProperties("spring.cloud.gateway")
         * 所以会通过属性绑定设置值
         * */
        this.properties = properties;
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        // 直接返回 properties.getRoutes()
        return Flux.fromIterable(this.properties.getRoutes());
    }
}

看 PredicateDefinition、FilterDefinition 的构造器,就能明白属性文件为啥可以写 Weight=group1,8

InMemoryRouteDefinitionRepository

InMemoryRouteDefinitionRepository 是由 GatewayAutoConfiguration 注册的。它主要是实现了 RouteDefinitionRepository 接口,而 RouteDefinitionRepository 继承 RouteDefinitionLocator,RouteDefinitionWriter 接口。

RouteDefinitionRepository 的职责是通过缓存的方式记录 RouteDefinition,而不是通过属性 映射成 RouteDefinition。而 AbstractGatewayControllerEndpoint 会依赖 RouteDefinitionWriter 的实例,用来缓存通过接口方式注册的 RouteDefinition。

java
public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
    // 线程安全的
    private final Map<String, RouteDefinition> routes = synchronizedMap(new LinkedHashMap<String, RouteDefinition>());

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        /**
         * Gateway Endpoint 会依赖 RouteDefinitionRepository 类型的bean 记录通过 Endpoint 动态添加的 RouteDefinition
         *
         * 源码在这里
         *        {@link AbstractGatewayControllerEndpoint#save(String, RouteDefinition)}
         * */
        return route.flatMap(r -> {
            if (ObjectUtils.isEmpty(r.getId())) {
                return Mono.error(new IllegalArgumentException("id may not be empty"));
            }
            // 存到缓存中
            routes.put(r.getId(), r);
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            if (routes.containsKey(id)) {
                // 从缓存中移除
                routes.remove(id);
                return Mono.empty();
            }
            return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));
        });
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        // 返回缓存中的值
        Map<String, RouteDefinition> routesSafeCopy = new LinkedHashMap<>(routes);
        return Flux.fromIterable(routesSafeCopy.values());
    }

}

AbstractGatewayControllerEndpoint

GatewayControllerEndpoint 和 GatewayLegacyControllerEndpoint 是由 GatewayAutoConfiguration 注册的,默认是注册 GatewayControllerEndpoint ,可以设置属性spring.cloud.gateway.actuator.verbose.enabled=false 变成让 GatewayLegacyControllerEndpoint 生效。

主要是提供这些功能:查看 RouteDefinitions、Routes、GlobalFilters、routefilters、routepredicates、更新或者新增 RouteDefinition、刷新 RouteDefinition。

更新或新增 RouteDefinition 是依赖 RouteDefinitionRepository 进行缓存。

刷新 RouteDefinition 是会发布 RefreshRoutesEvent 事件,该事件会有 CachingRouteLocator 处理

RouteRefreshListener

RouteRefreshListener 是由 GatewayAutoConfiguration 注册的

java
public class RouteRefreshListener implements ApplicationListener<ApplicationEvent> {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        // 这是 IOC 容器 refresh 的最后阶段会发布的事件
        if (event instanceof ContextRefreshedEvent) {
            ContextRefreshedEvent refreshedEvent = (ContextRefreshedEvent) event;
            // 不是
            if (!WebServerApplicationContext.hasServerNamespace(refreshedEvent.getApplicationContext(), "management")) {
                /**
                 * 重设
                 *
                 * 其实就是发布一个 RefreshRoutesEvent 事件,
                 * 该事件会由 CachingRouteLocator 接收,会对 List<Route> 进行缓存
                 *        {@link CachingRouteLocator#onApplicationEvent(RefreshRoutesEvent)}
                 * */
                reset();
            }
        }
        else if (event instanceof RefreshScopeRefreshedEvent || event instanceof InstanceRegisteredEvent) {
            reset();
        }
        else if (event instanceof ParentHeartbeatEvent) {
            ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
            resetIfNeeded(e.getValue());
        }
        else if (event instanceof HeartbeatEvent) {
            HeartbeatEvent e = (HeartbeatEvent) event;
            resetIfNeeded(e.getValue());
        }
    }

    private void resetIfNeeded(Object value) {
        if (this.monitor.update(value)) {
            reset();
        }
    }

    private void reset() {
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

}

FilteringWebHandler

FilteringWebHandler 是由 GatewayAutoConfiguration 注册的。

拿到容器中的 List<GlobaFilter> + Route.getFilters() 对 Filter 进行排序,紧接着按顺序执行所有的 GatewayFilter。这么说是不准确的,只有每个 filter 都执行chain.fiter 才会执行所有的 GatewayFilter,这其实就是责任链模式。

优先级最高(最后执行) 的 Filter 是 NettyRoutingFilter ,它是用来执行 http、https 请求的,也就是完成路由的职责。

text
/* Detailed source-code truncated for AI context efficiency. */

2. Official Technical Reference & Guides (doocs/doocs.github.io)

File: README.md

Introduction






Hello, everyone! Welcome to the Doocs Open Source community. We are currently focusing on sharing various development-related knowledge.

See the 中文文档 for Chinese README.

Sites

https://doocs.org

Owner

Yang Libin, focus on the inner workings of web applications to build out the "server-side" of web applications.

Projects

Doocs now has the following hot projects:

# Project Description Popularity
1 advanced-java Core interview questions & answers for experienced Java(Backend) developers.
2 leetcode LeetCode solutions in any programming language.
3 source-code-hunter Source code analysis of frameworks and components.
4 jvm A summary of the underlying principles of Java Virtual Machine.
5 coding-interview A collection of interview questions for programmers. The main contents include ‘Coding Interviews: Questions, Analysis and Solutions’, ‘Beauty of Programming’, etc.
6 md An online wechat markdown editor.
7 technical-books Awesome programming books list.

How to join

If you want to go fast, go alone. If you want to go far, go together.

I believe many of you have a deep understanding that participating in open source projects is very helpful for personal growth. If you love open source or you are curious about it, join Doocs, work with us, maintain our projects and grow together. You can join us in the following two ways:

By default, your Doocs membership will be hidden from view aftering joining us. If you'd like to display the organization icon in your profile, you can switch from "Private" to "Public" here: https://github.com/orgs/doocs/people. We also recommend setting it to Public.

Once a member of the community, the member can fork any project of interest and make modifications, and then submit a pull request for review. If the submission is deemed to be in compliance with the project's standards, the member may be added as a collaborator to the project.

It is worth noting that initially, new members may not have direct write access to the projects, as the maintainers need to assess the quality of the submissions. Therefore, it is recommended that new members make submissions regularly to become familiar with the GitHub workflow and to improve their submissions over time.

You can refer to this article if you are a new open-source contributor.

Overall, the Doocs community seems to be a valuable resource for programmers interested in learning about internet backend development and open-source development. The community offers various resources and projects that can help programmers improve their skills and contribute to the open-source community.

Doocs is looking forward to your joining.

Contributors

This organization exists thanks to all the people who contribute.

Contact


File: docs/en/index.md


https://vitepress.dev/reference/default-theme-home-page

layout: home

hero:
name: "Doocs"
text: "Developer-friendly Open-source Community"
actions:
- theme: alt
text: GitHub
link: https://github.com/doocs
- theme: brand
text: Enter
link: /en/intro

features:



File: docs/en/intro.md

Introduction






Hello, everyone! Welcome to the Doocs Open Source community. We are currently focusing on sharing various development-related knowledge.

Sites

https://doocs.org

Owner

Yang Libin, focus on the inner workings of web applications to build out the "server-side" of web applications.

Projects

Doocs now has the following hot projects:

# Project Description Popularity
1 advanced-java Core interview questions & answers for experienced Java(Backend) developers.
2 leetcode LeetCode solutions in any programming language.
3 source-code-hunter Source code analysis of frameworks and components.
4 jvm A summary of the underlying principles of Java Virtual Machine.
5 coding-interview A collection of interview questions for programmers. The main contents include ‘Coding Interviews: Questions, Analysis and Solutions’, ‘Beauty of Programming’, etc.
6 md An online wechat markdown editor.
7 technical-books Awesome programming books list.

How to join

If you want to go fast, go alone. If you want to go far, go together.

I believe many of you have a deep understanding that participating in open source projects is very helpful for personal growth. If you love open source or you are curious about it, join Doocs, work with us, maintain our projects and grow together. You can join us in the following two ways:

  • Comment on the relevant issue on GitHub to express interest in joining the community.
  • Send an email to [email protected] with the GitHub ID to request to join the community.

By default, your Doocs membership will be hidden from view aftering joining us. If you'd like to display the organization icon in your profile, you can switch from "Private" to "Public" here: https://github.com/orgs/doocs/people. We also recommend setting it to Public.

Once a member of the community, the member can fork any project of interest and make modifications, and then submit a pull request for review. If the submission is deemed to be in compliance with the project's standards, the member may be added as a collaborator to the project.

It is worth noting that initially, new members may not have direct write access to the projects, as the maintainers need to assess the quality of the submissions. Therefore, it is recommended that new members make submissions regularly to become familiar with the GitHub workflow and to improve their submissions over time.

You can refer to this article if you are a new open-source contributor.

Overall, the Doocs community seems to be a valuable resource for programmers interested in learning about internet backend development and open-source development. The community offers various resources and projects that can help programmers improve their skills and contribute to the open-source community.

Doocs is looking forward to your joining.

Contributors

This organization exists thanks to all the people who contribute.

Contact


File: docs/index.md


https://vitepress.dev/reference/default-theme-home-page

layout: home

hero:
name: "Doocs"
text: "开发者友好的开源社区"
actions:
- theme: alt
text: GitHub
link: https://github.com/doocs
- theme: brand
text: 进入
link: /intro

features:



File: docs/intro.md

介绍






你好呀,欢迎来到 Doocs 开源社区。我们是一个非常友好的技术社区,目前专注于分享技术领域相关知识。

站点

https://doocs.org

发起人

杨立滨,专注于互联网后端领域。

项目

目前 Doocs 有以下多个热门项目,欢迎 Star ⭐ 关注~

# 项目 描述 热度
1 advanced-java 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识。
2 leetcode 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解。
3 source-code-hunter 互联网常用组件框架源码分析。
4 jvm Java 虚拟机底层原理知识总结。
5 coding-interview 代码面试题集,包括《剑指 Offer》、《编程之美》等。
6 md 一款高度简洁的微信 Markdown 编辑器。
7 technical-books 值得一看的技术书籍列表。

如何加入

要走得快,就一个人走;要走得远,就一起走。

参与开源项目,对于个人的成长是有很大帮助的,相信很多小伙伴都有体会。

如果你热爱开源,或者抱着一颗好奇的心,不妨加入 Doocs 组织,和我们一起维护项目,共同成长。可以通过以下两种方式加入:

默认情况下,在你加入我们之后,你作为 GitHub Doocs 组织成员的信息是处于隐藏状态的。如果你希望在你的个人 GitHub 资料页上展示 Doocs 组织,你可以在此链接 https://github.com/orgs/doocs/people 处将你的信息从 private “私有”改为 public “公开”。当然,我们推荐设置为公开。

注意,刚开始加入,你暂时不会有项目的直接 write 权限,因为我们不确定你的提交是否符合项目规范。

你可以 fork 任何一个感兴趣的项目到你的个人 GitHub 帐户下,对项目作出修改后,提交你的 PR。Doocs 维护者会对你的提交内容进行 review。或许你最初的提交并不规范,这也没关系,改进之后,慢慢的,整个规范性就有了。当你的提交一直很符合项目的规范性,Doocs 维护者会将你添加到对应项目的 Collaborators 列表中,共同维护好项目。

如果你不熟悉 GitHub 操作流程,可以参考这篇文章。

Doocs 期待你的加入。

贡献者列表

感谢以下所有为此开源组织做出贡献的开发者朋友们。

联系我

GitHub 技术社区 Doocs 旗下唯一公众号「Doocs」​,欢迎扫码关注,专注分享技术领域相关知识及行业最新资讯。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。


File: README_CN.md

介绍






你好呀,欢迎来到 Doocs 开源社区。我们是一个非常友好的技术社区,目前专注于分享技术领域相关知识。

站点

https://doocs.org

发起人

杨立滨,专注于互联网后端领域。

项目

目前 Doocs 有以下多个热门项目,欢迎 Star ⭐ 关注~

# 项目 描述 热度
1 advanced-java 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识。
2 leetcode 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解。
3 source-code-hunter 互联网常用组件框架源码分析。
4 jvm Java 虚拟机底层原理知识总结。
5 coding-interview 代码面试题集,包括《剑指 Offer》、《编程之美》等。
6 md 一款高度简洁的微信 Markdown 编辑器。
7 technical-books 值得一看的技术书籍列表。

如何加入

要走得快,就一个人走;要走得远,就一起走。

参与开源项目,对于个人的成长是有很大帮助的,相信很多小伙伴都有体会。

如果你热爱开源,或者抱着一颗好奇的心,不妨加入 Doocs 组织,和我们一起维护项目,共同成长。可以通过以下两种方式加入:

默认情况下,在你加入我们之后,你作为 GitHub Doocs 组织成员的信息是处于隐藏状态的。如果你希望在你的个人 GitHub 资料页上展示 Doocs 组织,你可以在此链接 https://github.com/orgs/doocs/people 处将你的信息从 private “私有”改为 public “公开”。当然,我们推荐设置为公开。

注意,刚开始加入,你暂时不会有项目的直接 write 权限,因为我们不确定你的提交是否符合项目规范。

你可以 fork 任何一个感兴趣的项目到你的个人 GitHub 帐户下,对项目作出修改后,提交你的 PR。Doocs 维护者会对你的提交内容进行 review。或许你最初的提交并不规范,这也没关系,改进之后,慢慢的,整个规范性就有了。当你的提交一直很符合项目的规范性,Doocs 维护者会将你添加到对应项目的 Collaborators 列表中,共同维护好项目。

如果你不熟悉 GitHub 操作流程,可以参考这篇文章。

Doocs 期待你的加入。

贡献者列表

感谢以下所有为此开源组织做出贡献的开发者朋友们。

联系我

GitHub 技术社区 Doocs 旗下唯一公众号「Doocs」​,欢迎扫码关注,专注分享技术领域相关知识及行业最新资讯。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。