文章目录
- 一、前言
- 二、子容器默认组件
- FeignClientsConfiguration
- Decoder的注入
- Contract约定
- 对注解的支持
- 对类上注解的支持
- 对方法上注解的支持
- 对参数上注解的支持
- @MatrixVariable
- @PathVariable
- @RequestParam
- @RequestHeader
- @SpringQueryMap
- @RequestPart
- @CookieValue
- FormattingConversionService
- Retryer
- FeignLoggerFactory
- 属性文件开关
- FeignAutoConfiguration
- okHttp
- 三、总结
一、前言
通过前面的学习, 我们知道了
-
springcloud_openfeign的
@EnableFeignClients
注解, 使用@Import注解引入了FeignClientsRegistrar
对象, FeignClientsRegistrar是个ImportBeanDefinitionRegistrar
类型的对象 -
在registerBeanDefinitions方法中会将
EnableFeignClients#defaultConfiguration
和FeignClient#configuration
封装成FeignClientSpecification
注入到容器中 -
自动装配引入了
FeignClientsConfiguration
类, 它将注入到容器中的FeignClientSpecification
注入到了创建的FeignClientFactory
对象中, 而FeignClientFactory是springcloud的父子容器工厂, 它会将注入的对象按照容器名称添加到不容的子容器中(**dafult.**开头的会注册到所有子容器中), 并且会将FeignClientsConfiguration
最为defaultConfigType
注入到所有子容器中
那么这个FeignClientsConfiguration
都包含哪些内容呢, 这将是本章我们即将讨论的重点。
二、子容器默认组件
FeignClientsConfiguration
入口
/**
* 实例化feign子容器工厂对象
*/
@Bean
public FeignClientFactory feignContext() {
FeignClientFactory context = new FeignClientFactory();
// 设置子容器实例对象
context.setConfigurations(this.configurations);
return context;
}
public class FeignClientFactory extends NamedContextFactory<FeignClientSpecification> {
public FeignClientFactory() {
this(new HashMap<>());
}
public FeignClientFactory(
Map<String, ApplicationContextInitializer<GenericApplicationContext>> applicationContextInitializers) {
// 配置文件类
super(FeignClientsConfiguration.class, "spring.cloud.openfeign", "spring.cloud.openfeign.client.name",
applicationContextInitializers);
}
}
注意这里FeignClientFactory
的构造器中super(FeignClientsConfiguration.class...
, 这里就是给子容器注入FeignClientsConfiguration
配置文件
Decoder
@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {
/**
* springboot的消息转换器
*/
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
/**
* 注入解码器
*/
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder(ObjectProvider<HttpMessageConverterCustomizer> customizers) {
// 支持返回值类型 Optional<T> HttpEntity<> HttpEntity, 普通json
return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(messageConverters, customizers)));
}
}
messageConverters的默认实现如下图
-
ByteArrayHttpMessageConverter: web模块
-
StringHttpMessageConverter: web模块 用来处理`ISO_8859_1字符编码
-
StringHttpMessageConverter: web模块 用来处理
UTF-8
字符编码 -
ResourceHttpMessageConverter: web模块, 用来处理请求内容编码; 例如
applicatoin/json
; 用于将 HTTP 响应直接转换为一个完整的 Resource 对象(例如文件、URL 资源等),或者将 Resource 对象写入 HTTP 响应。 -
ResourceRegionHttpMessageConverter: web模块, 用于处理 ResourceRegion 对象,将资源的特定片段(区域)写入 HTTP 响应。它主要用于支持分块传输(如 HTTP 范围请求 Range),在视频流、文件分段下载等场景下很有用
-
AllEncompassingFormHttpMessageConverter: web模块, 是 Spring 框架中用于处理表单数据(application/x-www-form-urlencoded)和文件上传(multipart/form-data)的核心类。它是一个多功能的 HttpMessageConverter,支持以下两种常见的表单提交方式:
- application/x-www-form-urlencoded:普通表单提交
- multipart/form-data:文件上传表单提交。
- MappingJackson2HttpMessageConverter: web模块, 用于将 Java 对象和 JSON 数据之间相互转换。它基于 Jackson 库实现,是 Spring MVC 和 Spring Boot 中处理 JSON 数据的核心组件。
同时也支持我们自定义HttpMessageConverterCustomizer
, 注意ObjectProvider
的使用方法, 它是一个ObjectFactory
, 允许注入的对象是为空, 使用getObject
或者getIfAvailable
方法可以获取到实例对象。和@Autowired(required = false)
的区别是ObjectProvider
属于懒加载模式。
Decoder的注入
SpringDecoder
public class SpringDecoder implements Decoder {
/**
* 解码
*/
@Override
public Object decode(final Response response, Type type) throws IOException, FeignException {
// 返回值类型是 1.原始类型 2.泛型参数类型 3.通配符类型
if (type instanceof Class || type instanceof ParameterizedType || type instanceof WildcardType) {
List<HttpMessageConverter<?>> converters = messageConverters.getObject().getConverters();
customizers.forEach(customizer -> customizer.accept(converters));
@SuppressWarnings({ "unchecked", "rawtypes" })
// http数据转换器
HttpMessageConverterExtractor<?> extractor = new HttpMessageConverterExtractor(type, converters);
// 将 HTTP 响应解析为指定的对象; 这里是Object
return extractor.extractData(new FeignResponseAdapter(response));
}
throw new DecodeException(response.status(), "type is not an instance of Class or ParameterizedType: " + type,
response.request());
}
}
它提供了对多种不同类型返回值的转换, 例如json, 文件传输等
- 它只支持返回类型为
- 原始类型(Class), 例如Person
- 泛型参数类型(ParameterizedType), 例如 List
- 通配符类型(WildcardType), 例如 List<?>
- 使用转换器将返回数据
Response
转换成指定的Type类型
这里@SuppressWarnings({ “unchecked”, “rawtypes” })的作用
- unchecked: 用于抑制未进行泛型类型检查的警告。例如,当对一个未经检查的转换进行操作时(如从 Object 转为 List)
- rawtypes: 用于抑制"原始类型"相关的警告。即,当使用未指定泛型参数的集合类(例如 List、Map 等)
这里关于HttpMessageConverter的组装方法, 使用的是访问者模式, 是23中设计模式中不常用的一种
ResponseEntityDecoder
public class ResponseEntityDecoder implements Decoder {
@Override
public Object decode(final Response response, Type type) throws IOException, FeignException {
// 如果返回类型为HttpEntity<XXX>的参数泛型类型
if (isParameterizeHttpEntity(type)) {
// 获取参数泛型类型
type = ((ParameterizedType) type).getActualTypeArguments()[0];
// 用decoder解码
Object decodedObject = this.decoder.decode(response, type);
// 构建ResponseEntity对象
return createResponse(decodedObject, response);
}
// 返回类型是HttpEntity原始类型, 即不带参数泛型
else if (isHttpEntity(type)) {
// 直接丢弃数据, 即不支持返回类型为HttpEntity的情况
return createResponse(null, response);
}
else {
// 其它类型直接用decoder解码
return this.decoder.decode(response, type);
}
}
}
提供了返回值为HttpEntity
类型的支持
- 如果返回值类型为
HttpEntity<XXX>
的参数泛型类型, 那么将返回值解码成具体的泛型类型, 并封装成ResponseEntity
返回 - 如果返回类型是不带泛型的
HttpEntity
对象, 只返回响应状态和响应头, 返回的具体数据就直接丢弃了 - 其它类型的话当前Decoder类不处理, 直接执行包装的目标对象(即不处理)
OptionalDecoder
public final class OptionalDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException {
// 返回值不是Optional类型,直接执行包装的目标对象(即不处理)
if (!isOptional(type)) {
return delegate.decode(response, type);
}
// 404(找不到目标内容)和204(返回内容为空)状态码,直接返回Optional.empty()
if (response.status() == 404 || response.status() == 204) {
return Optional.empty();
}
// 获取Optional类型中泛型变量的上界类型; 例如 Optional<? extends Person>,返回Person
Type enclosedType = Util.resolveLastTypeParameter(type, Optional.class);
// 将返回值解码为Optional类型
return Optional.ofNullable(delegate.decode(response, enclosedType));
}
}
提供了对返回值为Optional的支持, 将返回值解析成Optional中泛型参数的类型, 然后封装成Optional返回
Decoder小结
- SpringDecoder提供了对常用返回类型的转换, 例如json, multipart/form-data内容格式
- ResponseEntityDecoder提供了对返回值为
HttpEntity
类型数据的支持 - OptionalDecoder提供了对返回值为Optional类型数据的支持
Contract约定
@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {
/**
* 自定义参数解析器
*/
@Autowired(required = false)
private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
/**
* springcloud_openfign的默认注解约定解析器
* @param feignConversionService 内容转换器
*/
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
// url分隔符是否解码, 为true时将斜杆转义符转换为/
boolean decodeSlash = feignClientProperties == null || feignClientProperties.isDecodeSlash();
return new SpringMvcContract(parameterProcessors, feignConversionService, decodeSlash);
}
}
springcloud_openfeign默认提供了一个SpringMvcContract覆盖默认的Contract.Default
SpringMvcContract
public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors,
ConversionService conversionService, boolean decodeSlash) {
// 参数注解解析器不能为null; 这里是判null而不是empty
Assert.notNull(annotatedParameterProcessors, "Parameter processors can not be null.");
// 消息转换器不能为空
Assert.notNull(conversionService, "ConversionService can not be null.");
// 获取默认的注解解析转换器
List<AnnotatedParameterProcessor> processors = getDefaultAnnotatedArgumentsProcessors();
processors.addAll(annotatedParameterProcessors);
// 将添加到map中{注解, 解析器}
annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors);
// 消息转换器
this.conversionService = conversionService;
// 创建Param.Expander的工厂
convertingExpanderFactory = new ConvertingExpanderFactory(conversionService);
// 是否将斜杆转义符转换为/
this.decodeSlash = decodeSlash;
}
}
这是它最大的一个构造器, 初始化了一些依赖项, 其中注解处理器processors
和消息转换器conversionService
比较重要
getDefaultAnnotatedArgumentsProcessors
private List<AnnotatedParameterProcessor> getDefaultAnnotatedArgumentsProcessors() {
List<AnnotatedParameterProcessor> annotatedArgumentResolvers = new ArrayList<>();
// 对@MatrixVariable注解的支持
annotatedArgumentResolvers.add(new MatrixVariableParameterProcessor());
// 对@PathVariable注解的支持; restful风格
annotatedArgumentResolvers.add(new PathVariableParameterProcessor());
// 对@RequestParam注解的支持; form表达参数的支持
annotatedArgumentResolvers.add(new RequestParamParameterProcessor());
// 对@RequestHeader注解的支持; 请求头参数的支持
annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor());
// 对@SpringQueryMap注解的支持; 请求参数的集合支持; 对应feign原来的@QueryMap注解
annotatedArgumentResolvers.add(new QueryMapParameterProcessor());
// 对@RequestPart注解的支持; 允许body参数平铺
annotatedArgumentResolvers.add(new RequestPartParameterProcessor());
// 对@CookieValue注解的支持; cookie参数的支持
annotatedArgumentResolvers.add(new CookieValueParameterProcessor());
return annotatedArgumentResolvers;
}
这里添加了7个默认的参数注解处理器
对注解的支持
对类上注解的支持
@Override
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
// 获取类上的RequestMapping注解
RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class);
if (classAnnotation != null) {
LOG.error("Cannot process class: " + clz.getName()
+ ". @RequestMapping annotation is not allowed on @FeignClient interfaces.");
throw new IllegalArgumentException("@RequestMapping annotation not allowed on @FeignClient interfaces");
}
// 类上的CollectionFormat注解
CollectionFormat collectionFormat = findMergedAnnotation(clz, CollectionFormat.class);
if (collectionFormat != null) {
// 设置get请求的集合数据分割符
data.template().collectionFormat(collectionFormat.value());
}
}
- feign接口类上不支持
@RequestMapping
注解 - 仅支持
@CollectionFormat
注解, 用来设置当前接口中所有方法的集合参数添加到url上作为参数时的分隔符(默认是&)
不支持类级别的请求头了…
对方法上注解的支持
@Override
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
// 方法上的CollectionFormat注解
if (methodAnnotation instanceof CollectionFormat) {
CollectionFormat collectionFormat = findMergedAnnotation(method, CollectionFormat.class);
// 设置get请求的集合数据分割符
data.template().collectionFormat(collectionFormat.value());
}
// 判断注解是否是RequestMapping注解, 方法上的非RequestMapping注解直接不处理
if (!(methodAnnotation instanceof RequestMapping)
&& !methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {
return;
}
RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
// HTTP Method
RequestMethod[] methods = methodMapping.method();
// 默认使用GET请求
if (methods.length == 0) {
methods = new RequestMethod[] { RequestMethod.GET };
}
// 只能定义一个请求方式
checkOne(method, methods, "method");
data.template().method(Request.HttpMethod.valueOf(methods[0].name()));
// path
// @RequestMapping(value = "") 就是@RequestLine中的路径部分
checkAtMostOne(method, methodMapping.value(), "value");
if (methodMapping.value().length > 0) {
// 只取第一个路径参数
String pathValue = emptyToNull(methodMapping.value()[0]);
if (pathValue != null) {
// 从环境变量中替换path中的占位符
pathValue = resolve(pathValue);
// Append path from @RequestMapping if value is present on method
// 添加前缀/
if (!pathValue.startsWith("/") && !data.template().path().endsWith("/")) {
pathValue = "/" + pathValue;
}
// 追加uri的path部分
data.template().uri(pathValue, true);
// 是否将斜杆转义符转换为/
if (data.template().decodeSlash() != decodeSlash) {
data.template().decodeSlash(decodeSlash);
}
}
}
// produces
// 设置客户端支持的返回数据类型
parseProduces(data, method, methodMapping);
// consumes
// 设置当前方法支持的请求数据类型
parseConsumes(data, method, methodMapping);
// headers
// 设置请求头
parseHeaders(data, method, methodMapping);
// params
// 设置请求参数;RequestMapping注解上的params属性, 追加到url请求参数上
parseParams(data, method, methodMapping);
// 参数扩展为空
data.indexToExpander(new LinkedHashMap<>());
}
方法小结
- 方法上支持
@CollectionFormat
注解, 用来设置当前方法的集合参数添加到url上作为参数时的分隔符(默认是&) - 方法上支持
@RequestMapping
注解, 并且请求方式(GET/POST/PUT…)只能有一个, @RequestMapping(value = “”) 就是@RequestLine中的路径部分, 也支持使用占位符, 可以从环境上下文中获取值去替换该占位符 @RequestMapping#produces
属性实质就是添加的Accept
请求头, 用于告诉服务端当前请求需要返回的数据类型, 例如application/json
@RequestMapping#consumes
属性实质是添加Content-Type
请求头, 用于高速服务端当前请求的参数类型, 例如application/json
@RequestMapping#headers
设置当前方法级别的请求头@RequestMapping#params
设置添加到url上的参数, 该参数可以使用占位符, 从环境上下文中获取值
对参数上注解的支持
@Override
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
boolean isHttpAnnotation = false;
try {
// 分页参数
if (Pageable.class.isAssignableFrom(data.method().getParameterTypes()[paramIndex])) {
// do not set a Pageable as QueryMap if there's an actual QueryMap param
// 如果方法的某个参数上有@RequestParam,@SpringQueryMap,@QueryMap注解, 返回true, 否则返回false
if (!queryMapParamPresent(data)) {
// 设置当前参数为queryMap参数, 放在url上
data.queryMapIndex(paramIndex);
return false;
}
}
}
catch (NoClassDefFoundError ignored) {
// Do nothing; added to avoid exceptions if optional dependency not present
}
AnnotatedParameterProcessor.AnnotatedParameterContext context = new SimpleAnnotatedParameterContext(data,
paramIndex);
Method method = processedMethods.get(data.configKey());
// 遍历方法的参数注解
for (Annotation parameterAnnotation : annotations) {
// 获取合适的参数注解处理器
AnnotatedParameterProcessor processor = annotatedArgumentProcessors
.get(parameterAnnotation.annotationType());
if (processor != null) {
Annotation processParameterAnnotation;
// 创建新的注解 并 支持别名
processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue(parameterAnnotation,
method, paramIndex);
// 参数注解处理器; 这里 |= 等价于 isHttpAnnotation = isHttpAnnotation || processor.processArgument(context,)
isHttpAnnotation |= processor.processArgument(context, processParameterAnnotation, method);
}
}
// 1.非multipart/form-data类型 2.http注解 3.当前参数没有增强器
if (!isMultipartFormData(data) && isHttpAnnotation && data.indexToExpander().get(paramIndex) == null) {
// 获取参数类型描述符;
// 如果是数组,则返回数组元素类型描述符;
// 如果是集合,则返回集合元素类型描述符;
// 如果是Stream,则返回Stream元素类型描述符;
// 如果是iterable,则返回iterable元素类型描述符;
// 其它类型返回该类型的类型描述符
TypeDescriptor typeDescriptor = createTypeDescriptor(method, paramIndex);
// 判断是否能转换成String类型
if (conversionService.canConvert(typeDescriptor, STRING_TYPE_DESCRIPTOR)) {
// 获取该类型的扩展器
Param.Expander expander = convertingExpanderFactory.getExpander(typeDescriptor);
if (expander != null) {
// 设置当前参数的扩展器
data.indexToExpander().put(paramIndex, expander);
}
}
}
return isHttpAnnotation;
}
方法小结
这里不介绍feign接口有关分页的部分
- 依次用注解处理器对参数注解进行处理, 只要有一个返回true(isHttpAnnotation为true), 那么它将不会被当做body字段被解析(这里说的是直接把参数变量当body参数,而非form参数当body)
- 满足一下条件,会给参数添加处理器,该处理器会将参数转成字符串
- 请求头Content-Type是multipart/form-data
- 参数处理器返回true(isHttpAnnotation为true), 表示它是一个http注解
- 该参数上没有参数处理器
返回isHttpAnnotation为true的注解有: @MatrixVariable
,@PathVariable
,RequestParam
,RequestHeader
,SpringQueryMap
,RequestPart
,CookieValue
; 下面分别介绍它们
@MatrixVariable
MatrixVariableParameterProcessor
用来处理参数上的MatrixVariable
注解
public class MatrixVariableParameterProcessor implements AnnotatedParameterProcessor {
private static final Class<MatrixVariable> ANNOTATION = MatrixVariable.class;
@Override
public Class<? extends Annotation> getAnnotationType() {
return ANNOTATION;
}
@Override
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
int parameterIndex = context.getParameterIndex();
// 参数类型
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
MethodMetadata data = context.getMethodMetadata();
// 注解value值
String name = ANNOTATION.cast(annotation).value();
checkState(emptyToNull(name) != null, "MatrixVariable annotation was empty on param %s.",
context.getParameterIndex());
context.setParameterName(name);
// 参数是map
if (Map.class.isAssignableFrom(parameterType)) {
// 给当前位置的参数添加处理器, 该处理器将map转为如k1=v1;k2=v2的字符串
data.indexToExpander().put(parameterIndex, this::expandMap);
}
else {
// 给当前位置的参数添加处理器, 该处理器将参数转成字符串 格式: ;{MatrixVariable.value}=object.toString
data.indexToExpander().put(parameterIndex, object -> ";" + name + "=" + object.toString());
}
// 注意这里返回的是true, 表示当前是http注解, 不会被当做body参数处理
return true;
}
/**
* 将map转为字符串; 格式为 ;k1=v1;k2=v2
*/
@SuppressWarnings("unchecked")
private String expandMap(Object object) {
Map<String, Object> paramMap = (Map) object;
return paramMap.keySet().stream().filter(key -> paramMap.get(key) != null)
.map(key -> ";" + key + "=" + paramMap.get(key).toString()).collect(Collectors.joining());
}
}
它处理了@MatrixVariable
注解, 支持矩阵参数, 例如 ;name=小杜;age=18
- 如果参数是map, 那么将该map参数平铺转为;k1=v1;k2=v2形式的字符串
- 其它类型的参数直接转为字符串类型
- 这里处理完成之后返回了一个true, 表示当前参数的注解是http注解, 它将不会被当做body处理, 并且该参数将会被忽略,仅用来替换占位符
MatrixVariableParameterProcessor给标有@MatrixVariable注解的参数添加了参数处理器
需要注意的是, feign默认对占位符的值进行了u8编码, 而springmvc的@MatrixVariable不支持编码的特殊符号,例如;=, 需要先处理
@PathVariable
PathVariableParameterProcessor
用来处理参数上的@PathVariable
注解
public class PathVariableParameterProcessor implements AnnotatedParameterProcessor {
private static final Class<PathVariable> ANNOTATION = PathVariable.class;
@Override
public Class<? extends Annotation> getAnnotationType() {
return ANNOTATION;
}
@Override
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
String name = ANNOTATION.cast(annotation).value();
checkState(emptyToNull(name) != null, "PathVariable annotation was empty on param %s.",
context.getParameterIndex());
context.setParameterName(name);
MethodMetadata data = context.getMethodMetadata();
String varName = '{' + name + '}';
// [^}]: 匹配任意不是右花括号 } 的字符
// 例如: abc{username:admin}xyz
String varNameRegex = ".*\\{" + name + "(:[^}]+)?\\}.*";
// 1.url中不包含占位符路径,也就不需要替换 2.参数不包含变量中的占位符,也就替换不了 3.参数不包含请求头上的占位符, 也就是替换不了请求头上的内容
if (!data.template().url().matches(varNameRegex) && !containsMapValues(data.template().queries(), varName)
&& !containsMapValues(data.template().headers(), varName)) {
// 添加为form参数; 不能用来处理url中的参数、header中的参数、url上的参数变量(例如?a={a}) 只能当为form参数
data.formParams().add(name);
}
// 注意这里返回的是true
return true;
}
private <K, V> boolean containsMapValues(Map<K, Collection<V>> map, V search) {
Collection<Collection<V>> values = map.values();
if (values == null) {
return false;
}
for (Collection<V> entry : values) {
if (entry.contains(search)) {
return true;
}
}
return false;
}
}
如果@PathVariable标识的参数不能用来替换url变量、参数变量、请求头上的参数, 那么它将作为form参数, 当做body参数
@RequestParam
RequestParamParameterProcessor
用来处理参数上的@RequestParam
注解
public class RequestParamParameterProcessor implements AnnotatedParameterProcessor {
private static final Class<RequestParam> ANNOTATION = RequestParam.class;
@Override
public Class<? extends Annotation> getAnnotationType() {
return ANNOTATION;
}
@Override
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
int parameterIndex = context.getParameterIndex();
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
MethodMetadata data = context.getMethodMetadata();
// 参数为map
if (Map.class.isAssignableFrom(parameterType)) {
// 只能有一个map 参数
checkState(data.queryMapIndex() == null, "Query map can only be present once.");
// 设置queryMap参数的索引
data.queryMapIndex(parameterIndex);
return true;
}
RequestParam requestParam = ANNOTATION.cast(annotation);
String name = requestParam.value();
// @RequestParam的value属性不能为空
checkState(emptyToNull(name) != null, "RequestParam.value() was empty on parameter %s of method %s",
parameterIndex, method.getName());
context.setParameterName(name);
// 给name变量对应的值添加"{name}"占位符
Collection<String> query = context.setTemplateParameter(name, data.template().queries().get(name));
data.template().query(name, query);
// 这里返回的true, 它不会被当做body参数来处理
return true;
}
}
- RequestParamParameterProcessor会将@RequestParam标识的参数用占位符的形式添加到请求url上, 例如
@RequestParam("name") String name
, 此时请求url上会有?name={name}的参数, 然后将实际的参数经过编码后替换这个占位符。 - 如果@RequestParam注解标识的参数是个map, 那么它将会把参数都添加都url上; 此时与feign原生注解@QueryMap以及springcloud_openfeign的@SpringQueryMap作用一样
- 它用来给url添加单个参数。
- 解析该注解返回的isHttpAnnotation为true, 并且没有加入到form参数中, 所以它不会被解析成body参数
@RequestHeader
RequestHeaderParameterProcessor
用来处理@RequestHeader注解
public class RequestHeaderParameterProcessor implements AnnotatedParameterProcessor {
private static final Class<RequestHeader> ANNOTATION = RequestHeader.class;
@Override
public Class<? extends Annotation> getAnnotationType() {
return ANNOTATION;
}
@Override
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
int parameterIndex = context.getParameterIndex();
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
MethodMetadata data = context.getMethodMetadata();
// @RequestHeader Map 这种格式
if (Map.class.isAssignableFrom(parameterType)) {
// @RequestHeader Map参数只能有一个
checkState(data.headerMapIndex() == null, "Header map can only be present once.");
data.headerMapIndex(parameterIndex);
return true;
}
String name = ANNOTATION.cast(annotation).value();
checkState(emptyToNull(name) != null, "RequestHeader.value() was empty on parameter %s", parameterIndex);
context.setParameterName(name);
// 添加到请求头 "{name}"到请求头集合中
Collection<String> header = context.setTemplateParameter(name, data.template().headers().get(name));
data.template().header(name, header);
// 这里返回的true, 它不会被当做body参数来处理
return true;
}
}
处理逻辑与@RequestParam
一样
- 如果
@RequestHeader
标识的参数是map, 那么它与feign原生的@HeaderMap注解一样, 将map中的参数都添加到请求头上 - 如果是单个请求头, 那么会给请求头添加一个占位符的值对象, 然后用该值经过u8编码后替换它
- 解析该注解返回的isHttpAnnotation为true, 并且没有加入到form参数中, 所以它不会被解析成body参数
@SpringQueryMap
QueryMapParameterProcessor
用来处理@SpringQueryMap注解
public class QueryMapParameterProcessor implements AnnotatedParameterProcessor {
private static final Class<SpringQueryMap> ANNOTATION = SpringQueryMap.class;
@Override
public Class<? extends Annotation> getAnnotationType() {
return ANNOTATION;
}
@Override
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
int paramIndex = context.getParameterIndex();
MethodMetadata metadata = context.getMethodMetadata();
if (metadata.queryMapIndex() == null) {
metadata.queryMapIndex(paramIndex);
}
return true;
}
}
这个注解就比较简单了, 完全是用来替代feign的@QueryMap注解的, 用来将map参数添加到请求url上;
需要注意controller中get请求的参数接受方式, 可以用实体对象批量接收, 也可以用@RequestParam注解单个接收
@RequestPart
RequestPartParameterProcessor
用来处理@RequestPart注解
public class RequestPartParameterProcessor implements AnnotatedParameterProcessor {
private static final Class<RequestPart> ANNOTATION = RequestPart.class;
@Override
public Class<? extends Annotation> getAnnotationType() {
return ANNOTATION;
}
@Override
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
int parameterIndex = context.getParameterIndex();
MethodMetadata data = context.getMethodMetadata();
String name = ANNOTATION.cast(annotation).value();
// @RequestPart注解的value()不能为空
checkState(emptyToNull(name) != null, "RequestPart.value() was empty on parameter %s", parameterIndex);
context.setParameterName(name);
// 添加到formParams
data.formParams().add(name);
// 添加一个 {name}到集合中
Collection<String> names = context.setTemplateParameter(name, data.indexToName().get(parameterIndex));
// 索引对参数名的映射
data.indexToName().put(parameterIndex, names);
return true;
}
}
- @RequestPart注解直接将参数添加到了form中, 那么它将被当做body参数来传递
- 内容会经过u8编码传递
@CookieValue
CookieValueParameterProcessor
用来处理@CookieValue注解
public class CookieValueParameterProcessor implements AnnotatedParameterProcessor {
private static final Class<CookieValue> ANNOTATION = CookieValue.class;
@Override
public Class<? extends Annotation> getAnnotationType() {
return ANNOTATION;
}
@Override
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
int parameterIndex = context.getParameterIndex();
MethodMetadata data = context.getMethodMetadata();
CookieValue cookie = ANNOTATION.cast(annotation);
String name = cookie.value().trim();
// @CookieValue注解的value()不能为空
checkState(emptyToNull(name) != null, "Cookie.name() was empty on parameter %s", parameterIndex);
// 索引和名称的映射
context.setParameterName(name);
// 请求头上的的Cookie
String cookieExpression = data.template().headers()
.getOrDefault(HttpHeaders.COOKIE, Collections.singletonList("")).stream().findFirst().orElse("");
// 请求头上没有Cookie; 添加占位符的cookie name={name}
if (cookieExpression.length() == 0) {
cookieExpression = String.format("%s={%s}", name, name);
}
else {
// 追加Cookie 例如 session=abc; name={name}
cookieExpression += String.format("; %s={%s}", name, name);
}
// 替换请求头上的Cookie
data.template().removeHeader(HttpHeaders.COOKIE);
data.template().header(HttpHeaders.COOKIE, cookieExpression);
return true;
}
}
- 添加cookie到请求头上
- 它将会被u8编码
- @CookieValue指定的cookie会覆盖请求头上的cookie
FormattingConversionService
/**
* 默认格式转换器
*/
@Bean
public FormattingConversionService feignConversionService() {
// 默认格式转换器; 支持了number,datetime,date
FormattingConversionService conversionService = new DefaultFormattingConversionService();
for (FeignFormatterRegistrar feignFormatterRegistrar : feignFormatterRegistrars) {
feignFormatterRegistrar.registerFormatters(conversionService);
}
return conversionService;
}
它支持格式化与参数转换; 下面是几个案例
public class ConversionTest {
@Test
void conversionTest() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
// 基础类型转换
Integer number = conversionService.convert("123", Integer.class);
System.out.println(number); // 输出:123
// 日期类型转换
LocalDate date = conversionService.convert("2024-12-25", LocalDate.class);
System.out.println(date); // 输出:2024-12-25
// map中的value转为整数
Map<String, String> sourceMap = new HashMap<>();
sourceMap.put("key1", "1");
sourceMap.put("key2", "2");
TypeDescriptor sourceMapType = TypeDescriptor.map(Map.class, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(String.class));
TypeDescriptor targetMapType = TypeDescriptor.map(Map.class, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class));
Object convertedMap = conversionService.convert(sourceMap, sourceMapType, targetMapType);
System.out.println("Converted Map: " + convertedMap); // 输出: {key1=1, key2=2}
Method method = ClassUtils.getMethod(ConversionTest.class, "bb", Integer.class);
Parameter parameter = method.getParameters()[0];
MethodParameter methodParameter = MethodParameter.forParameter(parameter);
TypeDescriptor typeDescriptor = new TypeDescriptor(methodParameter);
Object person = conversionService.convert(20, typeDescriptor, TypeDescriptor.valueOf(String.class));
System.out.println("convert methodParam:" + person);
}
public void bb(Integer age) {
}
}
格式化
@Test
void formatTest1() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
// 注册数字格式化器
NumberStyleFormatter numberFormatter = new NumberStyleFormatter();
numberFormatter.setPattern("#,###.##");
conversionService.addFormatter(numberFormatter);
// 转换数字字符串为数字
String numberStr = "123,456.78";
Locale locale = Locale.US; // 使用美国区域
// 设置全局区域
Locale.setDefault(locale);
Object number = conversionService.convert(numberStr, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Number.class));
System.out.println("解析后的数字: " + number); // 输出:123456.78
}
自定义格式化器
/**
* 自定义格式化器
*/
@Test
void formatTest2() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
// 自定义格式化器:将数字格式化为货币
conversionService.addFormatter(new Formatter<Double>() {
@Override
public Double parse(String text, Locale locale) throws ParseException {
return Double.parseDouble(text.replace("$", "").replace(",", ""));
}
@Override
public String print(Double object, Locale locale) {
return String.format(locale, "$%,.2f", object);
}
});
// 测试格式化器
String formatted = conversionService.convert(12345.678, String.class);
System.out.println("Formatted value: " + formatted); // 输出:$12,345.68
Double parsed = conversionService.convert("$12,345.68", Double.class);
System.out.println("Parsed value: " + parsed); // 输出:12345.68
}
Retryer
springcloud_openfeign默认不允许重试, 可以自定义重试机制
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
Retryer NEVER_RETRY = new Retryer() {
@Override
public void continueOrPropagate(RetryableException e) {
throw e;
}
@Override
public Retryer clone() {
return this;
}
};
FeignLoggerFactory
@Autowired(required = false)
private Logger logger;
/**
* 日志工厂
*/
@Bean
@ConditionalOnMissingBean(FeignLoggerFactory.class)
public FeignLoggerFactory feignLoggerFactory() {
// 日志工厂, 默认是构建Slf4jLogger
return new DefaultFeignLoggerFactory(logger);
}
public class DefaultFeignLoggerFactory implements FeignLoggerFactory {
private final Logger logger;
public DefaultFeignLoggerFactory(Logger logger) {
this.logger = logger;
}
@Override
public Logger create(Class<?> type) {
// 默认使用Slf4jLogger
return this.logger != null ? this.logger : new Slf4jLogger(type);
}
}
可以看出, springcloud_openfeign默认使用的slf4j作为日志框架, 我们在使用的时候配置logback.xml文件即可
属性文件开关
/**
* 是否启用全局属性文件配置(即spring.cloud.openfeign.client.config), 默认是true
*/
@Bean
@ConditionalOnMissingBean(FeignClientConfigurer.class)
public FeignClientConfigurer feignClientConfigurer() {
return new FeignClientConfigurer() {
};
}
可以使用spring.cloud.openfeign.client.config=true/false
来禁用或者启用yaml/yml/properties 配置文件中springcloud_openfeign的相关配置项(用作给feign接口定制参数)
FeignAutoConfiguration
okHttp
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
@ConditionalOnProperty("spring.cloud.openfeign.okhttp.enabled")
protected static class OkHttpFeignConfiguration {
private okhttp3.OkHttpClient okHttpClient;
@Bean
@ConditionalOnMissingBean
public okhttp3.OkHttpClient.Builder okHttpClientBuilder() {
return new okhttp3.OkHttpClient.Builder();
}
/**
* 连接池配置;
*/
@Bean
@ConditionalOnMissingBean(ConnectionPool.class)
public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties) {
// 最大连接数
int maxTotalConnections = httpClientProperties.getMaxConnections();
// 连接保活时间
long timeToLive = httpClientProperties.getTimeToLive();
// 连接保活时间单位
TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
return new ConnectionPool(maxTotalConnections, timeToLive, ttlUnit);
}
@Bean
public okhttp3.OkHttpClient okHttpClient(okhttp3.OkHttpClient.Builder builder, ConnectionPool connectionPool,
FeignHttpClientProperties httpClientProperties) {
// 是否随服务端重定向, 默认是true
boolean followRedirects = httpClientProperties.isFollowRedirects();
// 连接超时时长
int connectTimeout = httpClientProperties.getConnectionTimeout();
// 默认是false
boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
// 读取超时
Duration readTimeout = httpClientProperties.getOkHttp().getReadTimeout();
// 协议
List<Protocol> protocols = httpClientProperties.getOkHttp().getProtocols().stream().map(Protocol::valueOf)
.collect(Collectors.toList());
// 禁用ssl
if (disableSslValidation) {
disableSsl(builder);
}
this.okHttpClient = builder.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
.followRedirects(followRedirects).readTimeout(readTimeout).connectionPool(connectionPool)
.protocols(protocols).build();
return this.okHttpClient;
}
}
使用spring.cloud.openfeign.okhttp.enabled=true/false
开启或者禁用okhttp作为请求客户端, 使用spring.cloud.openfeign.httpclient
配置相关属性
三、总结
- FeignClientsConfiguration中添加了子容器工厂FeignClientFactory,并添加了子容器默认的组件FeignClientsConfiguration
- springcloud_openfeign对于支持的参数注解的对象, 可以转为字符串的,都通过ConversionService转成字符串
- 对返回值为原始类型,Optional,HttpEntity,HttpEntity的支持; 使用HttpMessageConverter对返回值进行转换
- 仅支持feign接口上的@CollectionFormat注解, 特别地,如果接口上有@RequestMapping注解将会报错
- 方法上支持@CollectionFormat和@RequestMapping注解
- @RequestMapping#produces属性实质就是添加的Accept请求头, 用于告诉服务端当前请求需要返回的数据类型, 例如application/json
- @RequestMapping#consumes属性实质是添加Content-Type请求头, 用于高速服务端当前请求的参数类型, 例如application/json
- @RequestMapping#headers设置当前方法级别的请求头
- @RequestMapping#params设置添加到url上的参数, 该参数可以使用占位符, 从环境上下文中获取值
- 方法参数上支持
- @MatrixVariable: 矩阵参数; 例如 ;name=小杜;age=18
- @PathVariable: path路径参数
- @RequestParam: url参数(一次一个)
- @RequestHeader: 请求头
- @SpringQueryMap: url参数(一次多个)
- @RequestPart: 当做form参数, 以body传递
- @CookieValue: cookie参数
别着急,下篇有完整demo