戴老师的博客 - Spring Cloud
type: channel
link: https://1diff.fun/tag/Spring-Cloud/
items: FeignClient源码深度解析
items: 深入理解Zuul之源码解析
items: SpringCloud组件之Zuul
items: Zuul 动态路由源码及几种实现方式
items: HandlerMethodArgumentResolver用于统一获取当前登录用户

FeignClient源码深度解析
type: item
date: 2026-03-30T09:36:58+00:00
description: 概述Spring Cloud Feign 主要对 Netflix Feign 进行了增强和包装。本文将从源码角度梳理 Feign 的装配流程，揭开其底层实现机制。内容主要涵盖 Feign 与 Ribbon、Hystrix、Sleuth 的整合过程，以及生成的代理类最终注入 Spring 容器的完整链路。本文篇幅较长，建议耐心阅读。Feign 架构图以下是核心类及大致流程的架构图：大体步骤：注册 FeignClient 配置类和 FeignClient BeanDefinition实例化 Feign 上下文对象 FeignContext创建 Feign.builder 对象生成负载均衡代理类生成默认代理类注入到 Spring 容器源码分析本节将围绕上述六个步骤进行详细分析。一、注册 FeignClient 配置类和 FeignClient BeanDefinition从启动类注解开始，首先查看 @EnableFeignClients 注解：@EnableFeignClients public class MyApplication { }这是在启动类开启 Feign 装配的注解。跟进该注解，可以看到其导入了 FeignClientsRegistrar 类：@Import(FeignClientsRegistrar.class) public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware { // patterned after Spring Integration IntegrationComponentScanRegistrar // and RibbonClientsConfigurationRegistgrar private final Logger logger = LoggerFactory.getLogger(FeignClientsRegistrar.class); private ResourceLoader resourceLoader; private ClassLoader classLoader; public FeignClientsRegistrar() { } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 1、先注册默认配置 registerDefaultConfiguration(metadata, registry); // 2、注册所有的 FeignClient BeanDefinition registerFeignClients(metadata, registry); } //... }我们分别来看 registerBeanDefinitions 中的两个核心方法。1. 注册默认配置方法：registerDefaultConfigurationprivate void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { Map<String, Object> defaultAttrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName(), true); if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) { String name; if (metadata.hasEnclosingClass()) { name = "default." + metadata.getEnclosingClassName(); } else { name = "default." + metadata.getC…

深入理解Zuul之源码解析
type: item
date: 2026-03-30T09:34:45+00:00
description: Zuul 架构图在 Zuul 中，整个请求的处理流程如下：请求首先由 ZuulServlet 处理。ZuulServlet 中包含一个 ZuulRunner 对象，该对象初始化了 RequestContext。RequestContext 用于存储整个请求的相关数据，并被所有的 ZuulFilter 共享。ZuulRunner 中还包含 FilterProcessor，它是执行所有 ZuulFilter 的管理器。FilterProcessor 从 FilterLoader 中获取 ZuulFilter。ZuulFilter 由 FilterFileManager 加载，支持 Groovy 热加载（采用轮询方式）。加载过滤器后，ZuulServlet 依次执行 pre 类型过滤器、route 类型过滤器，最后执行 post 类型过滤器。若执行过程中发生错误，则会执行 error 类型过滤器。执行完毕后，将请求结果返回给客户端。Zuul 工作原理源码分析前文已介绍过 Zuul 的基本用法，其中不可缺少的步骤是在程序启动类上添加 @EnableZuulProxy 注解。该注解的代码如下：@EnableCircuitBreaker @EnableDiscoveryClient @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(ZuulProxyConfiguration.class) public @interface EnableZuulProxy { }其中，@Import 引入了 ZuulProxyConfiguration。跟踪该类可知，它注入了 DiscoveryClient、RibbonCommandFactoryConfiguration 用于负载均衡相关配置，并注入了一系列 Filters，例如 PreDecorationFilter、RibbonRoutingFilter、SimpleHostRoutingFilter 等。代码如下：@Bean public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) { return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(), this.zuulProperties, proxyRequestHelper); } // route filters @Bean public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper, RibbonCommandFactory<?> ribbonCommandFactory) { RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory, this.requestCustomizers); return filter; } @Bean public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties zuulProperties) { return new SimpleHostRoutingFilter(helper, zuulProperties); }其父类 ZuulConfiguration 引用了一些相关配置。在缺失 zuulServlet Bean 的情况下，它会注入 ZuulServlet，该类是 Zuul 的核心类：@Bean @ConditionalOnMissingBean(name = "zuulServlet") public ServletRegistrationBean zuulServlet() { ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(), this.zuulProperties.getServletPattern()); // The whole point of exposing this servlet is to provide a route that doesn't // buffer requests. servlet.add…

SpringCloud组件之Zuul
type: item
date: 2026-03-30T09:32:11+00:00
description: Spring Cloud 组件之 ZuulZuul 是 Netflix 开源的微服务网关，可与 Eureka、Ribbon、Hystrix 等组件配合使用。Spring Cloud 对 Zuul 进行了整合与增强，其默认使用的 HTTP 客户端是 Apache HttpClient，同时也支持 RestClient 或 okhttp3.OkHttpClient。Zuul 的主要功能是路由转发和过滤器。路由功能是微服务架构的关键部分，例如将 /demo/test 请求转发到 demo 服务。Zuul 默认与 Ribbon 结合，实现了客户端负载均衡功能。本文主要介绍 Zuul 的工作原理、服务搭建流程以及相关核心知识点。一、工作原理Zuul 的核心是一系列的 Filters（过滤器），其作用类比 Servlet 框架的 Filter 或 AOP（面向切面编程）。在 Zuul 将请求路由到用户处理逻辑的过程中，这些 Filter 参与各种过滤处理，例如身份验证（Authentication）、负载削减（Load Shedding）等。Zuul 使用一系列不同类型的过滤器，使我们能够快速灵活地将功能应用于边缘服务。这些过滤器主要帮助执行以下功能：身份验证和安全性：确定每个资源的身份验证要求，并拒绝不满足这些要求的请求。洞察和监控：在边缘跟踪有意义的数据和统计数据，以便提供准确的生产视图。动态路由：根据需要动态地将请求路由到不同的后端群集。压力测试：逐渐增加群集的流量以衡量性能。负载削减（Load Shedding）：为每种类型的请求分配容量，并删除超过限制的请求。静态响应处理：直接在边缘构建一些响应，而不是将它们转发到内部集群。过滤器的生命周期二、Zuul 核心组件zuul-core：Zuul 核心库，包含编译和执行过滤器的核心功能。zuul-simple-webapp：Zuul Web 应用程序示例，展示了如何使用 zuul-core 构建应用程序。zuul-netflix：Lib 包，将其他 Netflix OSS 组件添加到 Zuul 中，例如使用 Ribbon 进行路由请求处理。zuul-netflix-webapp：Webapp 包，它将 zuul-core 和 zuul-netflix 封装成一个简易的 Web 应用工程包。三、搭建注册到 Eureka 的服务提供者1. 导入依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>2. 启动类/** * @author Gjing */ @SpringBootApplication @EnableEurekaClient public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }3. 配置文件server: port: 8090 spring: application: name: demo eureka: client: service-url: defaultZone: http://localhost:8761/eureka/4. 提供接口供外部访问/** * @author Gjing **/ @RestController public class TestController { @GetMapping("/test") public ResponseEntity<String> test() { return ResponseEntity.ok("ok"); } }四、搭建 Zuul 网关服务1. 导入 Zuul 和 Eureka 依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</g…

Zuul 动态路由源码及几种实现方式
type: item
date: 2026-03-30T09:27:58+00:00
description: 本文介绍 Zuul 路由的源码解析以及实现动态路由的几种常见方式，路由信息可以来自 Properties 文件、数据库、Apollo 配置中心等。建议先阅读 Spring Cloud 源码学习之 Zuul 简要了解路由源码流程。版本说明：本文基于 Spring Cloud Finchley.SR1，Spring Boot 2.0.6.RELEASE。路由源码本文基于下图场景做演示，文中代码来自源码，但存在大幅删减。请求达到 ZuulServlet 后，ZuulServlet 组织了路由的处理逻辑，如下：public void service(ServletRequest servletRequest, ServletResponse servletResponse) { // 执行 "pre" 类型 ZuulFilter preRoute(); // 执行 "route" 类型 ZuulFilter route(); // 执行 "post" 类型 ZuulFilter postRoute(); }预处理在 pre 类型的 ZuulFilter 中，PreDecorationFilter 会根据路由信息进行预处理，其处理结果决定了使用哪个 route 类型的 ZuulFilter 来实际处理请求。先看看 route 类型的 SimpleHostRoutingFilter、RibbonRoutingFilter 的处理条件（shouldFilter），它们负责实际的请求转发：// SimpleHostRoutingFilter public boolean shouldFilter() { return RequestContext.getCurrentContext().getRouteHost() != null && RequestContext.getCurrentContext().sendZuulResponse(); } // RibbonRoutingFilter public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); return (ctx.getRouteHost() == null && ctx.get("serviceId") != null && ctx.sendZuulResponse()); }相同点：都需要满足 sendZuulResponse()，即需要将 Response 反馈给客户端。不同点：SimpleHostRoutingFilter 需要 RouteHost 不为空；RibbonRoutingFilter 需要 serviceId 不为空而且 RouteHost 为空。下面是 Zuul application.yml 中的配置示例：zuul: servlet-path: routes: service1: path: /api/service1/** serviceId: service1 github: path: /github/** url: https://github.com/routes 中 service1 有 serviceId，github 有 url（host）。再看看 PreDecorationFilter 是如何预处理得到 RouteHost、serviceId 的，下面是其 run() 方法逻辑：@Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); final String requestURI = 根据 request 提取 requestURI; // 根据 requestURI 获取路由信息 Route route = this.routeLocator.getMatchingRoute(requestURI); if (route != null) { String location = route.getLocation(); if (location != null) { // 以 http 或 https 开头，设置 RouteHost if (location.startsWith("http:") || location.startsWith("https:")) { ctx.setRouteHost(getUrl(location)); } // 以 forward: 开头 else if (location.startsWith("forward:")) { ctx.setRouteHost(nu…

HandlerMethodArgumentResolver用于统一获取当前登录用户
type: item
date: 2026-03-30T09:26:14+00:00
description: 背景与需求环境：Spring Boot 2.0.4.RELEASE需求：多个 Controller 方法需要在请求入口获取当前登录用户信息，以便进行后续业务操作。准备工作：前端每次请求携带 Token，后端封装工具方法 tokenUtils.getUserByToken(token)，用于解析 Token 获取当前用户信息 (CurrentUserInfo)。这是一个常见的业务场景，为实现该需求，通常有以下几种解决方案。目录一、最原始直接二、AOP三、拦截器 + 方法参数解析器3.1 自定义权限拦截器3.2 自定义参数注解3.3 自定义方法参数解析器3.4 配置 MVC一、最原始直接即在每个 Controller 方法开始时，手动调用 tokenUtils.getUserByToken(token)。这种方式代码重复度高，不够优雅，维护成本较大。二、AOP使用 AOP（Aspect-Oriented Programming）可以解决很多切面类问题，例如将 currentUser 放入 request 中。但相比拦截器方案，AOP 的配置稍显厚重，且在某些场景下灵活性略低。三、拦截器 + 方法参数解析器使用 Spring MVC 拦截器 (HandlerInterceptor) 配合 方法参数解析器 (HandlerMethodArgumentResolver) 是最合适的方案。Spring MVC 提供了 HandlerInterceptor 接口，包含以下 3 个核心方法：preHandlepostHandleafterCompletionHandlerInterceptor 常用于拦截请求事件，如用户鉴权等。此外，Spring 还提供了多种解析器（Resolver），例如用于统一处理异常的 HandlerExceptionResolver，以及本文的主角 HandlerMethodArgumentResolver。HandlerMethodArgumentResolver 是用于处理方法参数解析的接口，包含以下 2 个核心方法：supportsParameter：判断是否支持该参数（满足某种要求返回 true，方可进入 resolveArgument 做参数处理）。resolveArgument：执行具体的参数解析逻辑。知识储备已到位，接下来着手实现，主要分为三步：自定义权限拦截器 AuthenticationInterceptor，拦截所有请求，将 Token 解析为 currentUser 并存入 request 中。自定义参数注解 @CurrentUser，添加至 Controller 的方法参数 user 之上。自定义方法参数解析器 CurrentUserMethodArgumentResolver，从 request 中取出 user，并赋值给添加了 @CurrentUser 注解的参数。3.1 自定义权限拦截器自定义权限拦截器 AuthenticationInterceptor，需实现 HandlerInterceptor 接口。在 preHandle 方法中调用 tokenUtils.getUserByToken(token) 获取当前用户，最后存入 request 属性中。代码如下：import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import edp.core.utils.TokenUtils; import edp.core.consts.Consts; import edp.davinci.model.User; public class AuthenticationInterceptor implements HandlerInterceptor { @Autowired private TokenUtils tokenUtils; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = requ…