您的位置:首页 > 游戏 > 游戏 > app开发合同模板最新版_承德建站公司_网络营销推广技巧_晚上必备免费软件大全苹果

app开发合同模板最新版_承德建站公司_网络营销推广技巧_晚上必备免费软件大全苹果

2025/1/6 18:31:11 来源:https://blog.csdn.net/joeyhhhhh/article/details/144770023  浏览:    关键词:app开发合同模板最新版_承德建站公司_网络营销推广技巧_晚上必备免费软件大全苹果
app开发合同模板最新版_承德建站公司_网络营销推广技巧_晚上必备免费软件大全苹果

文章目录

  • 一、前言
  • 二、子容器默认组件
    • FeignClientsConfiguration
      • Decoder的注入
      • Contract约定
    • 对注解的支持
      • 对类上注解的支持
      • 对方法上注解的支持
      • 对参数上注解的支持
        • @MatrixVariable
        • @PathVariable
        • @RequestParam
        • @RequestHeader
        • @SpringQueryMap
        • @RequestPart
        • @CookieValue
      • FormattingConversionService
      • Retryer
      • FeignLoggerFactory
      • 属性文件开关
    • FeignAutoConfiguration
      • okHttp
  • 三、总结

一、前言

通过前面的学习, 我们知道了

  1. springcloud_openfeign的@EnableFeignClients注解, 使用@Import注解引入了FeignClientsRegistrar对象, FeignClientsRegistrar是个ImportBeanDefinitionRegistrar类型的对象

  2. 在registerBeanDefinitions方法中会将EnableFeignClients#defaultConfigurationFeignClient#configuration封装成FeignClientSpecification注入到容器中

  3. 自动装配引入了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的默认实现如下图
在这里插入图片描述

  1. ByteArrayHttpMessageConverter: web模块

  2. StringHttpMessageConverter: web模块 用来处理`ISO_8859_1字符编码

  3. StringHttpMessageConverter: web模块 用来处理UTF-8字符编码

  4. ResourceHttpMessageConverter: web模块, 用来处理请求内容编码; 例如applicatoin/json; 用于将 HTTP 响应直接转换为一个完整的 Resource 对象(例如文件、URL 资源等),或者将 Resource 对象写入 HTTP 响应。

  5. ResourceRegionHttpMessageConverter: web模块, 用于处理 ResourceRegion 对象,将资源的特定片段(区域)写入 HTTP 响应。它主要用于支持分块传输(如 HTTP 范围请求 Range),在视频流、文件分段下载等场景下很有用

  6. AllEncompassingFormHttpMessageConverter: web模块, 是 Spring 框架中用于处理表单数据(application/x-www-form-urlencoded)和文件上传(multipart/form-data)的核心类。它是一个多功能的 HttpMessageConverter,支持以下两种常见的表单提交方式:

  • application/x-www-form-urlencoded:普通表单提交
  • multipart/form-data:文件上传表单提交。
  1. 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, 文件传输等

  1. 它只支持返回类型为
  • 原始类型(Class), 例如Person
  • 泛型参数类型(ParameterizedType), 例如 List
  • 通配符类型(WildcardType), 例如 List<?>
  1. 使用转换器将返回数据Response转换成指定的Type类型

这里@SuppressWarnings({ “unchecked”, “rawtypes” })的作用

  1. unchecked: 用于抑制未进行泛型类型检查的警告。例如,当对一个未经检查的转换进行操作时(如从 Object 转为 List)
  2. 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类型的支持

  1. 如果返回值类型为HttpEntity<XXX>的参数泛型类型, 那么将返回值解码成具体的泛型类型, 并封装成ResponseEntity返回
  2. 如果返回类型是不带泛型的HttpEntity对象, 只返回响应状态和响应头, 返回的具体数据就直接丢弃了
  3. 其它类型的话当前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小结

  1. SpringDecoder提供了对常用返回类型的转换, 例如json, multipart/form-data内容格式
  2. ResponseEntityDecoder提供了对返回值为HttpEntity类型数据的支持
  3. 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());}
}
  1. feign接口类上不支持@RequestMapping注解
  2. 仅支持@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<>());
}

方法小结

  1. 方法上支持@CollectionFormat注解, 用来设置当前方法的集合参数添加到url上作为参数时的分隔符(默认是&)
  2. 方法上支持@RequestMapping注解, 并且请求方式(GET/POST/PUT…)只能有一个, @RequestMapping(value = “”) 就是@RequestLine中的路径部分, 也支持使用占位符, 可以从环境上下文中获取值去替换该占位符
  3. @RequestMapping#produces属性实质就是添加的Accept请求头, 用于告诉服务端当前请求需要返回的数据类型, 例如application/json
  4. @RequestMapping#consumes属性实质是添加Content-Type请求头, 用于高速服务端当前请求的参数类型, 例如application/json
  5. @RequestMapping#headers设置当前方法级别的请求头
  6. @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接口有关分页的部分

  1. 依次用注解处理器对参数注解进行处理, 只要有一个返回true(isHttpAnnotation为true), 那么它将不会被当做body字段被解析(这里说的是直接把参数变量当body参数,而非form参数当body)
  2. 满足一下条件,会给参数添加处理器,该处理器会将参数转成字符串
  • 请求头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

  1. 如果参数是map, 那么将该map参数平铺转为;k1=v1;k2=v2形式的字符串
  2. 其它类型的参数直接转为字符串类型
  3. 这里处理完成之后返回了一个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;}}
  1. RequestParamParameterProcessor会将@RequestParam标识的参数用占位符的形式添加到请求url上, 例如@RequestParam("name") String name, 此时请求url上会有?name={name}的参数, 然后将实际的参数经过编码后替换这个占位符。
  2. 如果@RequestParam注解标识的参数是个map, 那么它将会把参数都添加都url上; 此时与feign原生注解@QueryMap以及springcloud_openfeign的@SpringQueryMap作用一样
  3. 它用来给url添加单个参数。
  4. 解析该注解返回的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一样

  1. 如果@RequestHeader标识的参数是map, 那么它与feign原生的@HeaderMap注解一样, 将map中的参数都添加到请求头上
  2. 如果是单个请求头, 那么会给请求头添加一个占位符的值对象, 然后用该值经过u8编码后替换它
  3. 解析该注解返回的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;}}
  1. @RequestPart注解直接将参数添加到了form中, 那么它将被当做body参数来传递
  2. 内容会经过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;}
}
  1. 添加cookie到请求头上
  2. 它将会被u8编码
  3. @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配置相关属性

三、总结

  1. FeignClientsConfiguration中添加了子容器工厂FeignClientFactory,并添加了子容器默认的组件FeignClientsConfiguration
  2. springcloud_openfeign对于支持的参数注解的对象, 可以转为字符串的,都通过ConversionService转成字符串
  3. 对返回值为原始类型,Optional,HttpEntity,HttpEntity的支持; 使用HttpMessageConverter对返回值进行转换
  4. 仅支持feign接口上的@CollectionFormat注解, 特别地,如果接口上有@RequestMapping注解将会报错
  5. 方法上支持@CollectionFormat和@RequestMapping注解
  • @RequestMapping#produces属性实质就是添加的Accept请求头, 用于告诉服务端当前请求需要返回的数据类型, 例如application/json
  • @RequestMapping#consumes属性实质是添加Content-Type请求头, 用于高速服务端当前请求的参数类型, 例如application/json
  • @RequestMapping#headers设置当前方法级别的请求头
  • @RequestMapping#params设置添加到url上的参数, 该参数可以使用占位符, 从环境上下文中获取值
  1. 方法参数上支持
  • @MatrixVariable: 矩阵参数; 例如 ;name=小杜;age=18
  • @PathVariable: path路径参数
  • @RequestParam: url参数(一次一个)
  • @RequestHeader: 请求头
  • @SpringQueryMap: url参数(一次多个)
  • @RequestPart: 当做form参数, 以body传递
  • @CookieValue: cookie参数

别着急,下篇有完整demo

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com