本文档详细阐述 ByteBuddy 自研 Agent 方案下 Sentinel 的无感接入架构,包括类加载隔离与桥接机制、各框架流控实现,以及与官方 Adapter 的差异点。
一、Sentinel Core 与 Adapter 类加载示意
1.1 ClassLoader 全景图

1.2 Agent 目录布局
sentinel-agent/
├── sentinel-agent-core.jar # -javaagent 指向此文件(含 shaded Byte Buddy)
├── bootstrap/
│ └── sentinel-agent-bootstrap.jar # 注入 BootstrapCL(SentinelClassBridge + SentinelAutoInjector)
├── lib/ # PluginClassLoader 的 classpath
│ ├── sentinel-core-1.8.6.jar
│ ├── sentinel-transport-common-1.8.6.jar
│ └── fastjson-xxx.jar ...
└── adapters/ # 各框架 Adapter JAR(运行时 addURL 到 App CL)
├── sentinel-apache-dubbo-adapter-1.8.6.jar
├── sentinel-mybatis-adapter.jar
├── sentinel-adapter-spring-webmvc-1.8.6.jar
├── sentinel-apache-httpclient-adapter-1.8.6.jar
├── sentinel-okhttp-adapter-1.8.6.jar
└── spring-cloud-starter-alibaba-sentinel-2022.0.0.0.jar # Feign + RestTemplate
1.3 premain 启动流程
// SentinelAgent.premain()
public static void premain(String args, Instrumentation inst) {
// 1. 注入 bootstrap JAR → BootstrapCL
injectBootstrapJar(agentDir, inst);
// 2. 创建隔离 PluginCL (parent=null),加载 sentinel-core
URLClassLoader pluginCl = createPluginClassLoader(agentDir);
// 3. 初始化 ClassBridge —— 注册桥接前缀 "com.alibaba.csp.sentinel."
initClassBridge(pluginCl);
// 4. 设置 adapter JAR 路径为系统属性
setAdapterPaths(agentDir);
// 5. 预初始化 Sentinel SPI (TCCL 临时设为 PluginCL)
preInitSentinel(pluginCl);
// 6. 安装 Byte Buddy 字节码增强(10 个 Transformer)
installTransformers(inst);
}
1.4 桥接机制 —— 类加载拦截与路由
Byte Buddy Advice 内联到 ClassLoader.loadClass(String, boolean) 方法入口:
// ClassLoaderLoadClassAdvice.java —— 内联到所有 ClassLoader 的 loadClass
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
public static Class<?> onEnter(@Advice.This ClassLoader self,
@Advice.Argument(0) String name) {
return SentinelClassBridge.loadClass(name, self);
}
@Advice.OnMethodExit
public static void onExit(@Advice.Enter Class<?> bridgedClass,
@Advice.Return(readOnly = false) Class<?> returnValue) {
if (bridgedClass != null) {
returnValue = bridgedClass;
}
}
// SentinelClassBridge.java —— 在 BootstrapCL 中,全局可见
public static Class<?> loadClass(String name, ClassLoader caller) {
if (name == null || pluginClassLoader == null) return null;
if (name.startsWith("java.") || name.startsWith("sun.") || name.startsWith("jdk.")) return null;
if (caller == pluginClassLoader) return null; // 防递归
for (String prefix : classPrefixes) { // "com.alibaba.csp.sentinel."
if (name.startsWith(prefix)) {
try { return pluginClassLoader.loadClass(name); }
catch (ClassNotFoundException e) { return null; }
}
}
return null;
}
桥接流程四步:
| 步骤 | 动作 | 说明 |
|---|---|---|
| ① | AppCL.loadClass("com.alibaba.csp.sentinel.SphU") |
Adapter/业务代码引用 Sentinel 类 |
| ② | Byte Buddy 内联代码 → SentinelClassBridge.loadClass(name, caller)
|
在 loadClass 方法入口拦截 |
| ③ |
name.startsWith("com.alibaba.csp.sentinel.") → pluginCL.loadClass(name)
|
前缀匹配 → 委托 PluginCL 加载 |
| ④ |
skipOn 生效 → onExit: returnValue = bridgedClass
|
替换返回值,跳过原方法体 |
1.5 Adapter "双头依赖" 解决路径
Adapter 类同时依赖 Sentinel Core 和 框架类(如 org.apache.dubbo.rpc.Filter),两者位于不同 ClassLoader。解决方案:
- Adapter JAR 通过
addURL()注入到 应用 ClassLoader - Adapter 类由 App CL 定义
- 引用 Sentinel Core →
App CL.loadClass("SphU")→ Bridge 拦截 →PluginCL.loadClass→ 找到 ✔ - 引用框架类 →
App CL.loadClass("Filter")→ 直接找到 ✔
Adapter JAR 不在 PluginCL 的
lib/中,Bridge 尝试加载时 CNFE → fallback 给 App CL 继续加载,确保 Adapter 由 App CL 定义。
二、HTTP 服务端流控 —— Spring WebMVC
2.1 整体方案

| 维度 | 官方接入方式 | 无感接入方式 |
|---|---|---|
| 触发机制 | Spring Boot AutoConfiguration | Byte Buddy Advice 拦截 InterceptorRegistry.getInterceptors()
|
| Adapter JAR |
sentinel-spring-webmvc-adapter(Maven 依赖引入) |
同一 JAR(运行时 addURL 注入 App CL) |
| 拦截器注册 |
SentinelWebMvcConfigurer.addInterceptors() → Bean 方式注册 |
直接 interceptorList.add(0, webInterceptor) 插入到列表首位 |
| 配置方式 |
SentinelWebMvcConfig Bean + application.yml 属性 |
使用 SentinelWebInterceptor 默认构造器(无配置) |
2.2 官方接入方式
在 spring-cloud-starter-alibaba-sentinel 中,SentinelWebAutoConfiguration 自动注册:
// SentinelWebAutoConfiguration.java(官方 Spring Cloud Alibaba)
@Configuration
@ConditionalOnWebApplication(type = SERVLET)
@ConditionalOnClass(SentinelWebInterceptor.class)
public class SentinelWebAutoConfiguration {
@Bean
public SentinelWebInterceptor sentinelWebInterceptor(SentinelWebMvcConfig config) {
return new SentinelWebInterceptor(config); // ← 传入配置对象
}
@Bean
public SentinelWebMvcConfig sentinelWebMvcConfig() {
SentinelWebMvcConfig config = new SentinelWebMvcConfig();
config.setHttpMethodSpecify(properties.getHttpMethodSpecify());
config.setWebContextUnify(properties.getWebContextUnify());
// UrlCleaner, BlockExceptionHandler, RequestOriginParser 等可自定义
return config;
}
@Bean
public SentinelWebMvcConfigurer sentinelWebMvcConfigurer() {
// 通过 WebMvcConfigurer.addInterceptors() 注册
// 可配置 order 和 urlPatterns
}
}
官方 SentinelWebInterceptor 的流控核心(继承自 AbstractSentinelInterceptor):
// AbstractSentinelInterceptor.preHandle()
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String resourceName = getResourceName(request);
// 引用计数:防止 forward/include 重复流控
if (increaseReference(request, requestRefName, 1) != 1) return true;
String origin = parseOrigin(request); // RequestOriginParser
String contextName = getContextName(request);
ContextUtil.enter(contextName, origin);
Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
request.setAttribute(requestAttributeName, entry);
return true;
// catch BlockException → blockExceptionHandler.handle(request, response, resourceName, e)
}
资源名提取(SentinelWebInterceptor):
protected String getResourceName(HttpServletRequest request) {
// 使用 HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE(如 /api/users/{id})
Object resourceNameObject = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
String resourceName = (String) resourceNameObject;
// 可选:UrlCleaner 清洗
// 可选:httpMethodSpecify → "GET:/api/users/{id}"
return resourceName;
}
2.3 无感接入方式
拦截点: InterceptorRegistry.getInterceptors() 方法返回后
// SpringWebMvcGetInterceptorsAdvice.java
public class SpringWebMvcGetInterceptorsAdvice {
@Advice.OnMethodExit
public static void onExit(@Advice.This Object interceptorRegistry,
@Advice.Return Object interceptors) {
SentinelAutoInjector.afterGetInterceptors(interceptorRegistry, interceptors);
}
}
注入逻辑:
// SentinelAutoInjector.afterGetInterceptors()
public static void afterGetInterceptors(Object interceptorRegistry, Object interceptors) {
List<Object> interceptorList = (List<Object>) interceptors;
// 1. 幂等检查:已存在 SentinelWebInterceptor 则跳过
for (Object interceptor : interceptorList) {
if (interceptor.getClass().getName().contains("SentinelWebInterceptor")) return;
}
// 2. addURL 注入 sentinel-spring-webmvc-adapter JAR 到 App CL
injectSpringWebMvcAdapter(appCl);
// 3. 反射创建 SentinelWebInterceptor(默认构造器)
Class<?> interceptorClass = Class.forName(SENTINEL_WEB_INTERCEPTOR_CLASS, true, appCl);
Object webInterceptor = interceptorClass.getDeclaredConstructor().newInstance();
// 4. 插入到拦截器列表首位
interceptorList.add(0, webInterceptor);
}
2.4 ⚠️ 与官方 Adapter 的差异
| 差异点 | 官方方式 | 无感接入 | 影响 |
|---|---|---|---|
| SentinelWebMvcConfig | 通过 Bean 注入配置(UrlCleaner、BlockExceptionHandler、RequestOriginParser、httpMethodSpecify 等) |
使用默认构造器,无配置 | 无法自定义 URL 清洗、来源解析、HTTP 方法前缀等 |
| 拦截器 Order | 可通过 spring.cloud.sentinel.filter.order 配置(默认 HIGHEST_PRECEDENCE) |
固定插入列表首位 (add(0, ...)),无 Order 概念 |
与其他 HandlerInterceptor 的执行顺序可能不同 |
| URL Patterns | 可配置 spring.cloud.sentinel.filter.urlPatterns(默认 ["/**"]) |
拦截所有请求,无法配置路径过滤 | 无法排除健康检查等非业务路径 |
| BlockException 处理 | 自定义 BlockExceptionHandler Bean(默认返回 429) |
使用默认 handler(返回 429,DefaultBlockExceptionHandler) |
无法自定义限流返回内容 |
| webContextUnify | 可配置,控制是否统一 Web 上下文 |
使用默认值 (true) |
影响链路模式限流 |
| 引用计数(forward/include) | 框架内置引用计数,防止子请求重复流控 | ✅ 一致,AbstractSentinelInterceptor 内置引用计数机制 |
无差异 |
| 资源名 |
HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE(如 /api/users/{id}) |
✅ 一致,复用同一个 SentinelWebInterceptor
|
无差异 |
核心影响: 无感接入场景下 SentinelWebInterceptor 以默认配置运行,用户无法通过 application.yml 自定义 URL 清洗器、限流返回体、来源解析器等。如需精细配置,需额外实现配置注入逻辑。
三、HTTP 客户端流控
3.0 总览
| 客户端 | 注入方式 | 注入的 Adapter | 拦截点 | 资源名格式 |
|---|---|---|---|---|
| RestTemplate | Adapter JAR 注入 | spring-cloud-starter-alibaba-sentinel |
构造器 + setInterceptors(List)
|
GET:http://host:port/path |
| Apache HttpClient | Adapter JAR 注入 | sentinel-apache-httpclient-adapter |
HttpClientBuilder.decorateMainExec() |
httpclient:/path |
| OkHttp | Adapter JAR 注入 | sentinel-okhttp-adapter |
OkHttpClient.Builder.build() |
okhttp:GET:http://host/path |
| Feign | Adapter JAR 注入 | spring-cloud-starter-alibaba-sentinel |
Feign.Builder.build() |
GET:http://service/path |
| HttpURLConnection | 直接流控(无 Adapter) | — |
connect() + getInputStream()
|
HttpURLConnection_GET_/path |
3.1 RestTemplate 流控

3.1.1 官方接入方式
官方依赖 @SentinelRestTemplate 注解 + SentinelBeanPostProcessor 自动装配:
// 用户代码 —— 必须显式标注 @SentinelRestTemplate
@Bean
@SentinelRestTemplate(blockHandler = "handleBlock", blockHandlerClass = MyHandler.class,
fallback = "handleFallback", fallbackClass = MyHandler.class,
urlCleaner = "cleanUrl", urlCleanerClass = MyHandler.class)
public RestTemplate restTemplate() {
return new RestTemplate();
}
SentinelBeanPostProcessor 在 postProcessAfterInitialization 中扫描带 @SentinelRestTemplate 注解的 Bean,将 SentinelProtectInterceptor 添加到其拦截器链。
SentinelProtectInterceptor 的双资源策略:
// SentinelProtectInterceptor.intercept()
// 资源 1(主机级): GET:http://service-a:8080
Entry hostEntry = SphU.entry(hostResource, EntryType.OUT);
// 资源 2(路径级): GET:http://service-a:8080/api/users
Entry hostWithPathEntry = SphU.entry(hostWithPathResource, EntryType.OUT);
// 执行实际请求
response = execution.execute(request, body);
// BlockException 处理: 熔断走 fallback,限流走 blockHandler,默认返回 SentinelClientHttpResponse
3.1.2 无感接入方式
拦截点: RestTemplate 所有构造器 + setInterceptors(List) 方法
// RestTemplateConstructorAdvice.java
@Advice.OnMethodExit
public static void onExit(@Advice.This Object restTemplate) {
SentinelAutoInjector.ensureSentinelProtectInterceptorOnRestTemplate(restTemplate);
}
// RestTemplateSetInterceptorsAdvice.java
@Advice.OnMethodExit
public static void onExit(@Advice.This Object restTemplate) {
SentinelAutoInjector.ensureSentinelProtectInterceptorOnRestTemplate(restTemplate);
}
注入逻辑核心:
// SentinelAutoInjector.ensureSentinelProtectInterceptorOnRestTemplate()
public static void ensureSentinelProtectInterceptorOnRestTemplate(Object restTemplate) {
// 1. addURL 注入 spring-cloud-starter-alibaba-sentinel JAR
injectRestTemplateAdapter(appCl);
// 2. 通过 JDK 动态代理创建默认的 @SentinelRestTemplate 注解实例
// blockHandler=""、fallback=""、urlCleaner="" —— 全部为空
Object annotation = createDefaultSentinelRestTemplateAnnotation(annotationClass, appCl);
// 3. 反射创建 SentinelProtectInterceptor(annotation, restTemplate)
Object interceptor = interceptorCtor.newInstance(annotation, restTemplate);
// 4. 确保 SentinelProtectInterceptor 位于拦截器链首位
interceptors.add(0, interceptor);
}
动态代理注解关键实现:
// 所有自定义属性返回空值 —— 不支持 blockHandler/fallback/urlCleaner
public Object invoke(Object proxy, Method method, Object[] args) {
if ("blockHandler".equals(methodName) || "fallback".equals(methodName)
|| "urlCleaner".equals(methodName)) {
return ""; // ← 空字符串,不会触发自定义处理
}
if ("blockHandlerClass".equals(methodName) || "fallbackClass".equals(methodName)
|| "urlCleanerClass".equals(methodName)) {
return Void.TYPE; // ← 无处理类
}
// ...
}
3.1.3 ⚠️ 与官方 Adapter 的差异
| 差异点 | 官方方式 | 无感接入 | 影响 |
|---|---|---|---|
| 覆盖范围 |
仅 @SentinelRestTemplate 注解的 Bean |
所有 RestTemplate 实例(包括无注解的) |
非业务 RestTemplate(如框架内部用)也会被流控 |
| blockHandler | 可指定类和方法处理限流 | 不支持(注解属性为空) | 限流时使用默认 SentinelClientHttpResponse
|
| fallback | 可指定类和方法处理熔断 | 不支持(注解属性为空) | 熔断时使用默认 SentinelClientHttpResponse
|
| urlCleaner | 可配置 URL 清洗逻辑 | 不支持(注解属性为空) | 无法归并 RESTful 路径变量(如 /users/123 → /users/{id}) |
| 双资源策略 | ✅ 主机级 + 路径级 | ✅ 一致,复用 SentinelProtectInterceptor
|
无差异 |
| 资源名格式 | METHOD:scheme://host[:port]/path |
✅ 一致 | 无差异 |
| 注入时机 | Spring Bean 后置处理(postProcessAfterInitialization) |
构造器退出时 + setInterceptors 退出时 |
更早注入,确保不被后续 setInterceptors 覆盖 |
3.2 Apache HttpClient 流控

3.2.1 官方接入方式
官方要求用户替换 HttpClientBuilder 为 SentinelApacheHttpClientBuilder:
// 官方用法 —— 需要修改代码
CloseableHttpClient httpClient = new SentinelApacheHttpClientBuilder()
.build();
SentinelApacheHttpClientBuilder 重写 decorateMainExec(ClientExecChain),用匿名 ClientExecChain 包装原始链:
// SentinelApacheHttpClientBuilder.decorateMainExec()
@Override
protected ClientExecChain decorateMainExec(final ClientExecChain mainExec) {
return new ClientExecChain() {
public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request, ...) {
String name = config.getExtractor().extractor(request); // 默认: request.getRequestLine().getUri()
if (!StringUtil.isEmpty(config.getPrefix())) {
name = config.getPrefix() + name; // 默认前缀: "httpclient:"
}
Entry entry = SphU.entry(name, ResourceTypeConstants.COMMON_WEB, EntryType.OUT);
return mainExec.execute(route, request, clientContext, execAware);
// catch BlockException → config.getFallback().handle(request, e)
// finally: entry.exit()
}
};
}
3.2.2 无感接入方式
拦截点: HttpClientBuilder.decorateMainExec(ClientExecChain) 返回后
// HttpClientDecorateMainExecAdvice.java
@Advice.OnMethodExit
public static void onExit(@Advice.This Object builder,
@Advice.Return(readOnly = false) Object returnValue) {
returnValue = SentinelAutoInjector.wrapHttpClientExecChain(returnValue, builder);
}
注入逻辑:
// SentinelAutoInjector.wrapHttpClientExecChain()
public static Object wrapHttpClientExecChain(Object originalChain, Object builder) {
// 1. 幂等检查:已经是 SentinelApacheHttpClientBuilder 则跳过
if (builder.getClass().getName().contains("SentinelApacheHttpClientBuilder")) return originalChain;
// 2. addURL 注入 sentinel-apache-httpclient-adapter JAR 到 App CL
injectHttpClientAdapter(appCl);
// 3. 反射创建 SentinelApacheHttpClientBuilder 实例
// 4. 调用其 decorateMainExec(originalChain) 包装原始执行链
Object wrappedChain = holder.decorateMainExecMethod.invoke(holder.sentinelBuilder, originalChain);
return wrappedChain;
}
设计类比:
TTL Agent: $1 = TtlTransformletHelper.doAutoWrap($1) → 包装 Runnable 参数
HttpClient Agent: ret = wrapHttpClientExecChain(ret, builder) → 包装 ClientExecChain 返回值
3.2.3 ⚠️ 与官方 Adapter 的差异
| 差异点 | 官方方式 | 无感接入 | 影响 |
|---|---|---|---|
| 接入方式 | 需将 HttpClientBuilder 替换为 SentinelApacheHttpClientBuilder
|
零修改,自动包装所有 HttpClientBuilder
|
完全透明 |
| 覆盖范围 | 仅手动替换的 Builder |
所有 HttpClientBuilder.build() 创建的客户端 |
非业务 HttpClient 也被流控(如框架内部 HTTP 调用) |
| SentinelApacheHttpClientConfig | 可自定义 prefix、extractor、fallback
|
使用默认配置 | 资源前缀固定 httpclient:,提取器固定 getRequestLine().getUri()
|
| Fallback | 可自定义 ApacheHttpClientFallback
|
默认 DefaultApacheHttpClientFallback(抛 SentinelRpcException) |
限流异常类型为 SentinelRpcException
|
| 资源名 |
httpclient: + URI |
✅ 一致 | 无差异 |
| EntryType | OUT |
✅ 一致 | 无差异 |
3.3 OkHttp 流控

3.3.1 官方接入方式
官方要求手动添加 Interceptor:
// 官方用法 —— 需要修改代码
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new SentinelOkHttpInterceptor()) // ← 手动添加
.build();
SentinelOkHttpInterceptor 作为 Application Interceptor,同时覆盖同步 execute() 和异步 enqueue():
// SentinelOkHttpInterceptor.intercept()
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String name = config.getResourceExtractor().extract(request, chain.connection());
// 默认: request.method() + ":" + request.url().toString()
if (StringUtil.isNotBlank(config.getResourcePrefix())) {
name = config.getResourcePrefix() + name; // 默认前缀: "okhttp:"
}
Entry entry = SphU.entry(name, ResourceTypeConstants.COMMON_WEB, EntryType.OUT);
return chain.proceed(request);
// catch BlockException → config.getFallback().handle(request, connection, e)
// finally: entry.exit()
}
3.3.2 无感接入方式
拦截点: OkHttpClient.Builder.build() 方法入口
// OkHttpClientBuildAdvice.java
@Advice.OnMethodEnter
public static void onEnter(@Advice.This Object builder) {
SentinelAutoInjector.beforeOkHttpClientBuild(builder);
}
注入逻辑:
// SentinelAutoInjector.beforeOkHttpClientBuild()
public static void beforeOkHttpClientBuild(Object builder) {
// 1. addURL 注入 sentinel-okhttp-adapter JAR 到 App CL
injectOkHttpAdapter(appCl);
// 2. 幂等检查:反射读取 Builder.interceptors 列表,已存在 SentinelOkHttpInterceptor 则跳过
List<Object> interceptors = (List<Object>) interceptorsField.get(builder);
for (Object interceptor : interceptors) {
if (interceptor.getClass().getName().equals(SENTINEL_OKHTTP_INTERCEPTOR_CLASS)) return;
}
// 3. 反射创建 SentinelOkHttpInterceptor(默认构造器)
Object sentinelInterceptor = interceptorClass.getDeclaredConstructor().newInstance();
// 4. 调用 builder.addInterceptor(sentinelInterceptor)
addInterceptor.invoke(builder, sentinelInterceptor);
}
3.3.3 ⚠️ 与官方 Adapter 的差异
| 差异点 | 官方方式 | 无感接入 | 影响 |
|---|---|---|---|
| 接入方式 | 手动 addInterceptor(new SentinelOkHttpInterceptor())
|
零修改,自动注入到所有 Builder | 完全透明 |
| 覆盖范围 | 仅手动添加的 Builder |
所有 OkHttpClient.Builder.build()
|
非业务 OkHttp 也被流控 |
| SentinelOkHttpConfig | 可自定义 resourcePrefix、resourceExtractor、fallback
|
使用默认配置 | 前缀固定 okhttp:,提取器固定 method:url
|
| Fallback | 可自定义 OkHttpFallback
|
默认 DefaultOkHttpFallback(抛 SentinelRpcException) |
限流异常为 SentinelRpcException
|
| 资源名 |
okhttp: + method:url
|
✅ 一致 | 无差异 |
| 同步/异步覆盖 | ✅ 同步 execute + 异步 enqueue | ✅ 一致,Application Interceptor 天然覆盖两者 | 无差异 |
3.4 Feign 流控

3.4.1 官方接入方式
官方通过 Spring Boot AutoConfiguration:
# application.yml
feign:
sentinel:
enabled: true # ← 触发 SentinelFeignAutoConfiguration
// SentinelFeignAutoConfiguration.java
@Bean
@Scope("prototype")
@ConditionalOnProperty(name = "feign.sentinel.enabled")
public Feign.Builder feignSentinelBuilder() {
return SentinelFeign.builder(); // 返回自定义的 Builder
}
SentinelFeign.Builder.build() 内部:
// SentinelFeign.Builder (官方)
super.invocationHandlerFactory(new InvocationHandlerFactory() {
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
// 获取 FallbackFactory(如果用户配置了的话)
Object fallbackFactory = ...;
return new SentinelInvocationHandler(target, dispatch, fallbackFactory);
}
});
super.contract(new SentinelContractHolder(contract));
SentinelInvocationHandler.invoke() 流控逻辑:
// SentinelInvocationHandler.invoke()
ContextUtil.enter(resourceName);
Entry entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
Object result = methodHandler.invoke(args);
// catch BlockException → 若有 fallbackFactory 走 fallback;否则直接抛
// finally: entry.exit(1, args); ContextUtil.exit();
资源名由 SentinelContractHolder 收集的 MethodMetadata 构建:METHOD: + hardCodedTarget.url() + methodMetadata.template().path()。
3.4.2 无感接入方式
拦截点: Feign.Builder.build() 方法入口
// FeignBuilderBuildAdvice.java
@Advice.OnMethodEnter
public static void onEnter(@Advice.This Object builder) {
SentinelAutoInjector.beforeFeignBuild(builder);
}
注入逻辑:
// SentinelAutoInjector.beforeFeignBuild()
public static void beforeFeignBuild(Object builder) {
// 幂等检查:已经是 SentinelFeign 则跳过
if (builderClassName.contains("SentinelFeign")) return;
// 1. addURL 注入 spring-cloud-starter-alibaba-sentinel JAR 到 App CL
injectFeignAdapter(appCl);
// 2. 用 SentinelContractHolder 包装原始 contract
Object originalContract = contractField.get(builder);
Object sentinelContract = contractHolderClass
.getDeclaredConstructor(contractInterface).newInstance(originalContract);
contractField.set(builder, sentinelContract);
// 3. 准备双构造器:无 fallback + 有 fallback
Constructor<?> handlerCtorNoFallback = sentinelHandlerClass
.getDeclaredConstructor(targetClass, Map.class);
Constructor<?> handlerCtorWithFallback = null;
Class<?> sentinelFfClass = null;
try {
sentinelFfClass = Class.forName(
"org.springframework.cloud.openfeign.FallbackFactory", true, appCl);
handlerCtorWithFallback = sentinelHandlerClass
.getDeclaredConstructor(targetClass, Map.class, sentinelFfClass);
} catch (Throwable ignored) {}
// 4. JDK 动态代理实现 InvocationHandlerFactory(支持 fallback 解析)
Object sentinelFactory = Proxy.newProxyInstance(appCl,
new Class[]{ invHandlerFactoryClass },
(proxy, method, args) -> {
if ("create".equals(method.getName())) {
// 尝试从 @FeignClient 注解解析 fallbackFactory / fallback
if (handlerCtorWithFallback != null) {
Object ff = resolveFeignFallbackFactory(
args[0]/*target*/, sentinelFfClass, appCl);
if (ff != null) {
return handlerCtorWithFallback.newInstance(args[0], args[1], ff);
}
}
return handlerCtorNoFallback.newInstance(args[0], args[1]);
}
// ...
});
factoryField.set(builder, sentinelFactory);
}
Fallback 解析机制: 在 InvocationHandlerFactory.create(target, dispatch) 时自动解析:
// resolveFeignFallbackFactory() — 从 @FeignClient 注解解析降级配置
private static Object resolveFeignFallbackFactory(Object target, Class<?> sentinelFfClass, ClassLoader appCl) {
// 1. target.type() → @FeignClient 接口(兼容 OpenFeign + Netflix 两种包路径)
// 2. 读取 fallbackFactory / fallback 属性
// 3. 通过无参构造器直接实例化
// 4. 适配为 org.springframework.cloud.openfeign.FallbackFactory
// (feign.hystrix.FallbackFactory → JDK 动态代理适配,方法签名兼容 create(Throwable))
// 5. 解析失败 → return null → 静默降级为无 fallback 模式
}
3.4.3 ⚠️ 与官方 Adapter 的差异
| 差异点 | 官方方式 | 无感接入 | 影响 |
|---|---|---|---|
| 接入方式 |
feign.sentinel.enabled=true(配置属性) |
零配置,自动拦截所有 Feign.Builder.build()
|
完全透明 |
| FallbackFactory | 支持 @FeignClient(fallbackFactory = ...)
|
✅ 支持(自动解析注解 + 直接实例化) | 简单 fallback 可正常降级 |
| Fallback | 支持 @FeignClient(fallback = ...)
|
✅ 支持(包装为 FallbackFactory.Default) |
同上 |
| Fallback 实例化 | 从 Spring 容器获取(支持 @Autowired 注入) |
无参构造器直接实例化(不经过 Spring 容器) | 依赖 Spring 注入的复杂 fallback 字段为 null |
| FallbackFactory 兼容 | 仅 org.springframework.cloud.openfeign.FallbackFactory
|
✅ 兼容两种(自动适配 feign.hystrix.FallbackFactory) |
Netflix 1st Gen 也支持 |
| 覆盖范围 | 所有由 Spring 容器创建的 FeignClient |
所有 Feign.Builder.build() 调用 |
非 Spring 管理的 Feign 也被流控 |
| 资源名 | METHOD:url/path |
✅ 一致,复用 SentinelContractHolder + SentinelInvocationHandler
|
无差异 |
| ContextUtil.enter | ✅ | ✅ 一致 | 无差异 |
| Tracer.traceEntry | ✅ 业务异常追踪 | ✅ 一致 | 无差异 |
核心差异: Fallback 类通过无参构造器直接实例化,不经过 Spring 容器。对于仅使用 Logger 等静态资源的简单 fallback 类(如大部分业务 FallbackFactory 实现)可正常工作。依赖 @Autowired 注入的复杂 fallback 需业务配置 feign.sentinel.enabled=true 走官方链路。
3.5 JDK HttpURLConnection 流控

3.5.1 概述
java.net.HttpURLConnection(实现类 sun.net.www.protocol.http.HttpURLConnection)是 JDK 内置 HTTP 客户端,Sentinel 官方没有提供对应 Adapter。无感接入采用直接流控模式——在 Advice 中直接反射调用 SphU.entry()/entry.exit()。
3.5.2 双拦截点模型
connect() ──── 流控主入口:创建 Entry + 阻断检查(TCP 建连之前阻断)
Entry 存入 ThreadLocal
getInputStream() ──── 响应阶段:关闭 Entry + 异常追踪
从 ThreadLocal 取出 Entry 并 exit
设计原因: HttpURLConnection 的 API 模型是 connect() → getInputStream(),流控 Entry 的生命周期需要跨越两次方法调用。使用 ThreadLocal 在两个拦截点之间传递 Entry。
3.5.3 connect() 拦截
// HttpURLConnectionConnectAdvice.java
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
public static int onEnter(@Advice.This Object conn) {
return SentinelAutoInjector.beforeUrlConnectionConnect(conn);
// 返回 0 = 放行,1 = 被 Sentinel 阻断
}
@Advice.OnMethodExit(onThrowable = Throwable.class)
public static void onExit(@Advice.Enter int blocked, @Advice.Thrown(readOnly = false) Throwable thrown) {
if (blocked != 0) {
thrown = new java.io.IOException("Blocked by Sentinel (flow limiting)");
SentinelAutoInjector.afterUrlConnectionConnect(thrown);
} else if (thrown != null) {
SentinelAutoInjector.afterUrlConnectionConnect(thrown);
}
}
流控逻辑:
// SentinelAutoInjector.beforeUrlConnectionConnect()
public static int beforeUrlConnectionConnect(Object conn) {
// 1. 防重入:ThreadLocal 已有 Entry 则跳过
if (CURRENT_URLCONN_ENTRY.get() != null) return 0;
// 2. 获取 ClassLoader:HttpURLConnection 由 BootstrapCL 加载,conn.getClass().getClassLoader() == null
// 使用 TCCL 获取 App CL,TCCL 为 null 时回退 SentinelClassBridge.getPluginClassLoader()
ClassLoader appCl = Thread.currentThread().getContextClassLoader();
if (appCl == null) appCl = SentinelClassBridge.getPluginClassLoader();
// 3. 构建资源名: HttpURLConnection_{METHOD}_{path}
String resourceName = buildUrlConnectionResourceName(method, urlStr);
// 4. 反射调用 SphU.entry(resourceName, EntryType.OUT)
Object entry = sentinelEntry(appCl, resourceName, 0);
CURRENT_URLCONN_ENTRY.set(entry);
return 0;
// catch: 若 BlockException → return 1 (阻断)
}
3.5.4 getInputStream() 拦截
// HttpURLConnectionGetInputStreamAdvice.java
@Advice.OnMethodExit(onThrowable = Throwable.class)
public static void onExit(@Advice.Thrown Throwable thrown) {
SentinelAutoInjector.afterUrlConnectionGetInputStream(thrown);
}
// SentinelAutoInjector.afterUrlConnectionGetInputStream()
public static void afterUrlConnectionGetInputStream(Throwable error) {
Object entry = CURRENT_URLCONN_ENTRY.get();
if (entry == null) return;
CURRENT_URLCONN_ENTRY.remove();
if (error != null) traceException(entry, error);
exitEntry(entry);
}
3.5.5 BootstrapCL 类加载问题
sun.net.www.protocol.http.HttpURLConnection 由 BootstrapClassLoader 加载。Advice 内联代码引用的 SentinelAutoInjector 必须对 BootstrapCL 可见。
ByteBuddy 方案的天然优势: SentinelAutoInjector 打包在 agent-bootstrap.jar 中,已通过 inst.appendToBootstrapClassLoaderSearch() 注入 BootstrapCL,无需额外 Spy 桥接层。
独立 AgentBuilder 的必要性:AgentBuilder.Default() 默认 ignore Bootstrap 类。HttpURLConnection 使用独立 AgentBuilder 并覆盖 ignore 规则:
new AgentBuilder.Default()
.ignore(nameStartsWith("net.bytebuddy.")) // 仅 ignore ByteBuddy 自身
.type(named("sun.net.www.protocol.http.HttpURLConnection"))
.transform(...)
.installOn(inst);
3.5.6 资源名构建
private static String buildUrlConnectionResourceName(String method, String urlStr) {
String path = new URL(urlStr).getPath(); // 提取 path 部分
if (path == null || path.isEmpty()) path = "/";
return "HttpURLConnection_" + method + "_" + path;
// 示例: HttpURLConnection_POST_/api/submit
}
3.5.7 特殊场景
| 场景 | 处理方式 |
|---|---|
| connect() 成功 + getInputStream() 未调用(fire-and-forget POST) | Entry 留在 ThreadLocal,等待线程复用时被新 connect() 的防重入机制跳过 |
| connect() 抛异常 |
onExit 中 afterUrlConnectionConnect(thrown) 立即关闭 Entry |
| Sentinel 阻断 |
onEnter 返回 1 → skipOn 跳过原 connect() → onExit 抛 IOException("Blocked by Sentinel")
|
| TCCL 为 null | 回退到 SentinelClassBridge.getPluginClassLoader()
|
四、MyBatis 流控
4.1 概述
Sentinel 官方不提供 MyBatis Adapter。 无感接入方案自研了 sentinel-mybatis-adapter,基于 MyBatis Interceptor 机制实现 SQL 级别流控。
4.2 Adapter 实现
4.2.1 SentinelMyBatisMapperInterceptor
// SentinelMyBatisMapperInterceptor.java
@Intercepts({
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update",
args = {MappedStatement.class, Object.class})
})
public class SentinelMyBatisMapperInterceptor implements Interceptor {
public static final String MYBATIS_PREFIX = "mybatis:";
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
String mapperId = ms.getId(); // 如: com.example.mapper.UserMapper.selectById
Entry entry = null;
try {
entry = SphU.entry(MYBATIS_PREFIX + mapperId,
ResourceTypeConstants.COMMON_DB_SQL, EntryType.OUT);
return invocation.proceed();
} catch (BlockException ex) {
throw new MyBatisSentinelBlockException(ex);
} catch (Throwable t) {
Tracer.traceEntry(t, entry);
throw t;
} finally {
if (entry != null) entry.exit();
}
}
}
资源名格式: mybatis:com.example.mapper.UserMapper.selectById
资源类型: ResourceTypeConstants.COMMON_DB_SQL
EntryType: OUT(作为下游资源)
4.2.2 MyBatisSentinelBlockException
public class MyBatisSentinelBlockException extends RuntimeException {
private final BlockException blockException;
public MyBatisSentinelBlockException(BlockException cause) {
super(cause);
this.blockException = cause;
}
public BlockException getBlockException() {
return blockException;
}
}
4.3 注入方式
拦截点: org.apache.ibatis.plugin.InterceptorChain.pluginAll(Object)
// MyBatisPluginAllAdvice.java
@Advice.OnMethodEnter
public static void onEnter(@Advice.This Object interceptorChain) {
SentinelAutoInjector.beforeMyBatisPluginAll(interceptorChain);
}
注入逻辑:
// SentinelAutoInjector.beforeMyBatisPluginAll()
public static void beforeMyBatisPluginAll(Object interceptorChain) {
// 1. addURL 注入 sentinel-mybatis-adapter JAR 到 App CL
File adapterJar = findMyBatisAdapterJar();
tryAddUrl(appCl, adapterJar.toURI().toURL());
// 2. 幂等检查:检查 InterceptorChain 中是否已有 SentinelMyBatisMapperInterceptor
if (hasInterceptor(interceptorChain, interceptorClazz.getName())) return;
// 3. 反射创建 SentinelMyBatisMapperInterceptor 实例
Object interceptor = interceptorClazz.getDeclaredConstructor().newInstance();
// 4. 反射调用 InterceptorChain.addInterceptor(Interceptor) 注入
Method addInterceptor = interceptorChain.getClass().getMethod("addInterceptor", interceptorType);
addInterceptor.invoke(interceptorChain, interceptor);
}
时序: 在 MyBatis 初始化 Configuration.newExecutor() → InterceptorChain.pluginAll(executor) 调用前注入拦截器,确保后续所有 Executor 实例都经过 Sentinel 流控包装。
4.4 流控效果
| 维度 | 说明 |
|---|---|
| 粒度 | Mapper 方法级(mybatis: + mapperId) |
| 覆盖 |
Executor.query(所有 SELECT)+ Executor.update(INSERT/UPDATE/DELETE) |
| 阻断 | 抛出 MyBatisSentinelBlockException(RuntimeException 子类,内含 BlockException) |
| 异常追踪 | 业务 SQL 异常通过 Tracer.traceEntry(t, entry) 记录 |
| 资源类型 |
COMMON_DB_SQL(在 Sentinel Dashboard 可按类型过滤) |
4.5 注意事项
-
官方无对标 Adapter:此为完全自研实现,资源名格式
mybatis:{mapperId}无官方先例 - 粒度较细:每个 Mapper 方法一个资源,大型项目可能产生大量资源条目
-
BlockException 包装:MyBatis 的
Interceptor.intercept()签名为throws Throwable,但BlockException是受检异常。为避免上层调用方(如 Spring 事务管理器)误处理,将其包装为MyBatisSentinelBlockException(RuntimeException)
五、Sentinel API 反射缓存机制
所有直接流控模式(HttpURLConnection)和 Adapter 创建逻辑均依赖反射调用 Sentinel API。为避免重复反射查找,使用 SentinelApiHolder 按 ClassLoader 缓存:
private static class SentinelApiHolder {
final Method entryMethod; // SphU.entry(String, EntryType)
final Object entryTypeIn; // EntryType.IN
final Object entryTypeOut; // EntryType.OUT
final Method exitMethod; // Entry.exit()
final Method traceMethod; // Tracer.trace(Throwable)
SentinelApiHolder(ClassLoader appCl) throws Throwable {
// 通过 appCl 加载 → ClassBridge 路由到 PluginCL
Class<?> sphUClass = Class.forName("com.alibaba.csp.sentinel.SphU", true, appCl);
// ...
}
}
private static final Map<ClassLoader, SentinelApiHolder> API_CACHE =
Collections.synchronizedMap(new WeakHashMap<>());
WeakHashMap 设计:key 为 ClassLoader,当 ClassLoader 被 GC 回收时(如 WAR 热部署),缓存条目自动清除,防止内存泄漏。
六、与官方差异汇总
6.1 注入官方 Adapter JAR 的框架
| 框架 | 注入的官方 Adapter | 核心差异 |
|---|---|---|
| Spring WebMVC | sentinel-spring-webmvc-adapter |
无 SentinelWebMvcConfig,不支持 UrlCleaner/BlockExceptionHandler/httpMethodSpecify 等配置 |
| RestTemplate | spring-cloud-starter-alibaba-sentinel |
覆盖所有实例(不仅 @SentinelRestTemplate 标注的),不支持 blockHandler/fallback/urlCleaner
|
| Apache HttpClient | sentinel-apache-httpclient-adapter |
覆盖所有 Builder,不支持自定义 prefix/extractor/fallback
|
| OkHttp | sentinel-okhttp-adapter |
覆盖所有 Builder,不支持自定义 resourcePrefix/resourceExtractor/fallback
|
| Feign | spring-cloud-starter-alibaba-sentinel |
覆盖所有 Feign.Builder,支持 Fallback(自动解析 @FeignClient 注解,通过无参构造器实例化;依赖 Spring 注入的复杂 fallback 需走官方链路) |
6.2 通用差异模式
| 差异维度 | 原因 | 影响 |
|---|---|---|
| 覆盖范围更广 | 字节码增强拦截所有实例构造/方法调用,而非仅 Spring Bean | 非业务组件(框架内部 HTTP 调用、健康检查等)也被流控 |
| 不支持自定义配置 | 通过反射默认构造器创建 Adapter,无法传入 Config Bean | 资源名提取器、限流返回、URL 清洗等均为默认值 |
| Fallback 部分支持 | Feign 自动解析 @FeignClient 注解属性(通过无参构造器实例化);RestTemplate 的 fallback 依赖注解属性或 Spring Bean |
Feign 简单 fallback 可正常降级;RestTemplate 限流/熔断时使用默认 SentinelClientHttpResponse
|
| 资源名完全一致 | 复用官方 Adapter 的资源名生成逻辑 | 与官方接入的 Sentinel Dashboard 规则兼容 |
6.3 自研 Adapter(无官方对标)
| 框架 | 说明 |
|---|---|
| MyBatis | 官方无 MyBatis Adapter。自研 SentinelMyBatisMapperInterceptor,资源名 mybatis:{mapperId}
|
| HttpURLConnection | 官方无此 Adapter。直接流控模式,资源名 HttpURLConnection_{METHOD}_{path}
|
七、Byte Buddy Transformer 注册清单
| # | 目标类 | 拦截方法 | Advice 类 | 模式 |
|---|---|---|---|---|
| 1 | ClassLoader |
loadClass(String, boolean) |
ClassLoaderLoadClassAdvice |
类桥接 |
| 2 | o.a.dubbo...ExtensionLoader |
loadDirectory(Map, String, String) |
ApacheDubboLoadDirectoryAdvice |
Adapter JAR 注入 |
| 3 | c.a.dubbo...ExtensionLoader |
loadDirectory(Map, String) |
AlibabaDubboLoadDirectoryAdvice |
Adapter JAR 注入 |
| 4 | o.a.ibatis...InterceptorChain |
pluginAll(Object) |
MyBatisPluginAllAdvice |
Adapter JAR 注入 |
| 5 | o.s...InterceptorRegistry |
getInterceptors() |
SpringWebMvcGetInterceptorsAdvice |
Adapter JAR 注入 |
| 6 | o.s...RestTemplate |
<init>() + setInterceptors(List)
|
RestTemplateConstructorAdvice / ...SetInterceptorsAdvice
|
Adapter JAR 注入 |
| 7 | o.a.http...HttpClientBuilder |
decorateMainExec(ClientExecChain) |
HttpClientDecorateMainExecAdvice |
Adapter JAR 注入 |
| 8 | okhttp3.OkHttpClient$Builder |
build() |
OkHttpClientBuildAdvice |
Adapter JAR 注入 |
| 9 | feign.Feign$Builder |
build() |
FeignBuilderBuildAdvice |
Adapter JAR 注入 |
| 10 | sun...HttpURLConnection |
connect() + getInputStream()
|
HttpURLConnectionConnectAdvice / ...GetInputStreamAdvice
|
直接流控 |