本文收录于专栏 Nacos
推荐阅读:Nacos 架构 & 原理
前言
阅读Nacos
源码时,经常看到 Controller
中有@ExtractorManager.Extractor
这么一个注解。那么它的作用是什么?
一、@ExtractorManager.Extractor
Module: core
我们来逐步看下源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Extractor {
Class<? extends AbstractHttpParamExtractor> httpExtractor() default DefaultHttpExtractor.class;
Class<? extends AbstractRpcParamExtractor> rpcExtractor() default DefaultGrpcExtractor.class;
}
- 这段代码定义了
Extractor
注解,允许在类或方法上配置自定义的参数提取器。 - @Target 指定了该注解可以应用于类型(类、接口等)或方法。
- @Retention 表明注解在运行时可用。
- @Documented 表示该注解可以包含在 javadoc 中。
httpExtractor
和rpcExtractor
分别指定了默认的 HTTP 和 gRPC 参数提取器类。
public static class DefaultHttpExtractor extends AbstractHttpParamExtractor {
@Override
public List<ParamInfo> extractParam(HttpServletRequest params) {
return Collections.emptyList();
}
}
public static class DefaultGrpcExtractor extends AbstractRpcParamExtractor {
@Override
public List<ParamInfo> extractParam(Request request) {
return Collections.emptyList();
}
}
DefaultHttpExtractor
和DefaultGrpcExtractor
是httpExtractor
和rpcExtractor
的默认参数提取器。- 默认不提取,直接返回空集合。
AbstractHttpParamExtractor
和AbstractRpcParamExtractor
是ParamExtractor
的抽象实现,ParamExtractor
中只有一个方法,定义extractParam
方法提取参数,这里不做展开。
private static HashMap<Class<? extends AbstractRpcParamExtractor>, AbstractRpcParamExtractor> rpcManager = new HashMap<>();
private static HashMap<Class<? extends AbstractHttpParamExtractor>, AbstractHttpParamExtractor> httpManager = new HashMap<>();
static {
NacosServiceLoader.load(AbstractHttpParamExtractor.class).forEach(checker -> {
httpManager.put(checker.getClass(), checker);
});
NacosServiceLoader.load(AbstractRpcParamExtractor.class).forEach(checker -> {
rpcManager.put(checker.getClass(), checker);
});
}
- 这两个静态哈希表分别用于存储和管理 gRPC 和 HTTP 参数提取器实例。
- 通过类类型作为键,实例作为值,以便快速检索和避免重复创建。
- 静态代码块在类加载时执行。
NacosServiceLoader.load
用于加载所有实现了AbstractHttpParamExtractor
和AbstractRpcParamExtractor
的类,并将它们的实例添加到相应的哈希表中。
public static AbstractRpcParamExtractor getRpcExtractor(Extractor extractor) {
return rpcManager.computeIfAbsent(extractor.rpcExtractor(), (key) -> new DefaultGrpcExtractor());
}
public static AbstractHttpParamExtractor getHttpExtractor(Extractor extractor) {
return httpManager.computeIfAbsent(extractor.httpExtractor(), (key) -> new DefaultHttpExtractor());
}
- 这两个方法根据
Extractor
注解中的配置,获取对应的参数提取器实例。
总结
ExtractorManager
类的主要用途是为不同类型的请求(HTTP 和 gRPC)动态提供合适的参数提取器。通过注解方式,调用者可以灵活地在不同的处理器方法或类上指定不同的提取器(class),增强了代码的可配置性和可扩展性。
二、CheckConfiguration
@Configuration
public class CheckConfiguration {
@Bean
public FilterRegistrationBean<ParamCheckerFilter> checkerFilterRegistration(ParamCheckerFilter checkerFilter) {
FilterRegistrationBean<ParamCheckerFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(checkerFilter);
registration.addUrlPatterns("/*");
registration.setName("checkerFilter");
registration.setOrder(8);
return registration;
}
@Bean
public ParamCheckerFilter checkerFilter(ControllerMethodsCache methodsCache) {
return new ParamCheckerFilter(methodsCache);
}
}
checkerFilterRegistration
方法- 这是一个
@Bean
方法,用于创建并注册ParamCheckerFilter
的FilterRegistrationBean
。 FilterRegistrationBean
:这是Spring
提供的一个类,用于在应用程序中注册过滤器。
- 这是一个
checkerFilter
方法- 这是另一个
@Bean
方法,用于创建ParamCheckerFilter
实例。 - 参数
ControllerMethodsCache methodsCache
(不做展开):这是一个依赖注入的对象,提供对控制器方法的缓存支持。
- 这是另一个
三、ParamCheckerFilter
我们看下这个参数校验过滤器中的核心代码:
//从方法中获取Extractor
ExtractorManager.Extractor extractor = method.getAnnotation(ExtractorManager.Extractor.class);
if (extractor == null) {
//如果方法上没有,尝试从类上获取
extractor = method.getDeclaringClass().getAnnotation(ExtractorManager.Extractor.class);
if (extractor == null) {
chain.doFilter(request, response);
return;
}
}
//ExtractorManager中获取@ExtractorManager.Extractor中指定的提取器实现
AbstractHttpParamExtractor httpParamExtractor = ExtractorManager.getHttpExtractor(extractor);
//参数提取
List<ParamInfo> paramInfoList = httpParamExtractor.extractParam(req);
ParamCheckerManager paramCheckerManager = ParamCheckerManager.getInstance();
//获取参数校验实现类
AbstractParamChecker paramChecker = paramCheckerManager.getParamChecker(ServerParamCheckConfig.getInstance().getActiveParamChecker());
//执行参数校验
ParamCheckResponse paramCheckResponse = paramChecker.checkParamInfoList(paramInfoList);
if (paramCheckResponse.isSuccess()) {
chain.doFilter(req, resp);
} else {
Loggers.CONTROL.info("Param check invalid,{},url:{}", paramCheckResponse.getMessage(), req.getRequestURI());
generate400Response(resp, paramCheckResponse.getMessage());
}
总结
ExtractorManager
- 管理了不同类型的参数提取器 (
AbstractHttpParamExtractor
和AbstractRpcParamExtractor
) 的实例化和获取。 - 使用了自定义的
@Extractor
注解来指定类或方法应使用的提取器,支持灵活配置。 - 通过静态初始化块和
NacosServiceLoader
,它加载并管理所有可用的提取器实现,避免了手动实例化和配置。
- 管理了不同类型的参数提取器 (
ParamCheckerFilter
ParamCheckerFilter
是一个实现了Filter
接口的过滤器类,用于拦截 HTTP 请求并进行参数校验。- 它依赖于 ControllerMethodsCache 类来获取请求方法,以及使用
ExtractorManager
和ParamCheckerManager
来选择和应用合适的参数提取器和校验器。 - 根据配置,它可以启用或禁用参数校验功能,确保在必要时进行有效的参数验证。
- 当参数校验失败时,它能够生成 HTTP 400 响应,并记录相关的错误日志信息。
CheckConfiguration
CheckConfiguration
是一个 Spring 配置类,负责将ParamCheckerFilter
注册为 Spring Bean,并设置其在过滤器链中的顺序和作用路径。- 使用了
FilterRegistrationBean
来配置和注册过滤器,确保其在应用中正确地拦截和处理 HTTP 请求。