文章目录
- 一、前言
- 二、子容器默认组件
- 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的消息转换器*/@Autowiredprivate ObjectFactory<HttpMessageConverters> messageConverters;/*** 注入解码器*/@Bean@ConditionalOnMissingBeanpublic Decoder feignDecoder(ObjectProvider<HttpMessageConverterCustomizer> customizers) {// 支持返回值类型 Optional<T> HttpEntity<> HttpEntity, 普通jsonreturn 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 {/*** 解码*/@Overridepublic 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 响应解析为指定的对象; 这里是Objectreturn 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 {@Overridepublic 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 {@Overridepublic 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>,返回PersonType 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@ConditionalOnMissingBeanpublic 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而不是emptyAssert.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 MethodRequestMethod[] 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, 否则返回falseif (!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;@Overridepublic Class<? extends Annotation> getAnnotationType() {return ANNOTATION;}@Overridepublic 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);// 参数是mapif (Map.class.isAssignableFrom(parameterType)) {// 给当前位置的参数添加处理器, 该处理器将map转为如k1=v1;k2=v2的字符串data.indexToExpander().put(parameterIndex, this::expandMap);}else {// 给当前位置的参数添加处理器, 该处理器将参数转成字符串 格式: ;{MatrixVariable.value}=object.toStringdata.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;@Overridepublic Class<? extends Annotation> getAnnotationType() {return ANNOTATION;}@Overridepublic 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}xyzString 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);}// 注意这里返回的是truereturn 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;@Overridepublic Class<? extends Annotation> getAnnotationType() {return ANNOTATION;}@Overridepublic boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {int parameterIndex = context.getParameterIndex();Class<?> parameterType = method.getParameterTypes()[parameterIndex];MethodMetadata data = context.getMethodMetadata();// 参数为mapif (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;@Overridepublic Class<? extends Annotation> getAnnotationType() {return ANNOTATION;}@Overridepublic 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;@Overridepublic Class<? extends Annotation> getAnnotationType() {return ANNOTATION;}@Overridepublic 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;@Overridepublic Class<? extends Annotation> getAnnotationType() {return ANNOTATION;}@Overridepublic 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);// 添加到formParamsdata.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;@Overridepublic Class<? extends Annotation> getAnnotationType() {return ANNOTATION;}@Overridepublic 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);// 请求头上的的CookieString 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);}// 替换请求头上的Cookiedata.template().removeHeader(HttpHeaders.COOKIE);data.template().header(HttpHeaders.COOKIE, cookieExpression);return true;}
}
- 添加cookie到请求头上
- 它将会被u8编码
- @CookieValue指定的cookie会覆盖请求头上的cookie
FormattingConversionService
/*** 默认格式转换器*/
@Bean
public FormattingConversionService feignConversionService() {// 默认格式转换器; 支持了number,datetime,dateFormattingConversionService conversionService = new DefaultFormattingConversionService();for (FeignFormatterRegistrar feignFormatterRegistrar : feignFormatterRegistrars) {feignFormatterRegistrar.registerFormatters(conversionService);}return conversionService;
}
它支持格式化与参数转换; 下面是几个案例
public class ConversionTest {@Testvoid 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>() {@Overridepublic Double parse(String text, Locale locale) throws ParseException {return Double.parseDouble(text.replace("$", "").replace(",", ""));}@Overridepublic 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.68Double 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() {@Overridepublic void continueOrPropagate(RetryableException e) {throw e;}@Overridepublic Retryer clone() {return this;}};
FeignLoggerFactory
@Autowired(required = false)
private Logger logger;/*** 日志工厂*/
@Bean
@ConditionalOnMissingBean(FeignLoggerFactory.class)
public FeignLoggerFactory feignLoggerFactory() {// 日志工厂, 默认是构建Slf4jLoggerreturn new DefaultFeignLoggerFactory(logger);
}public class DefaultFeignLoggerFactory implements FeignLoggerFactory {private final Logger logger;public DefaultFeignLoggerFactory(Logger logger) {this.logger = logger;}@Overridepublic Logger create(Class<?> type) {// 默认使用Slf4jLoggerreturn 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@ConditionalOnMissingBeanpublic 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);}@Beanpublic okhttp3.OkHttpClient okHttpClient(okhttp3.OkHttpClient.Builder builder, ConnectionPool connectionPool,FeignHttpClientProperties httpClientProperties) {// 是否随服务端重定向, 默认是trueboolean followRedirects = httpClientProperties.isFollowRedirects();// 连接超时时长int connectTimeout = httpClientProperties.getConnectionTimeout();// 默认是falseboolean disableSslValidation = httpClientProperties.isDisableSslValidation();// 读取超时Duration readTimeout = httpClientProperties.getOkHttp().getReadTimeout();// 协议List<Protocol> protocols = httpClientProperties.getOkHttp().getProtocols().stream().map(Protocol::valueOf).collect(Collectors.toList());// 禁用sslif (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