移动端和pc网站,网站开发自学资料,即墨网站设计,在西部数码做的企业网站不能与阿里巴巴网站相连接文章目录 一、前言二、子容器默认组件FeignClientsConfigurationDecoder的注入Contract约定 对注解的支持对类上注解的支持对方法上注解的支持对参数上注解的支持MatrixVariablePathVariableRequestParamRequestHeaderSpringQueryMapRequestPartCookieValue FormattingConversi… 文章目录 一、前言二、子容器默认组件FeignClientsConfigurationDecoder的注入Contract约定 对注解的支持对类上注解的支持对方法上注解的支持对参数上注解的支持MatrixVariablePathVariableRequestParamRequestHeaderSpringQueryMapRequestPartCookieValue FormattingConversionServiceRetryerFeignLoggerFactory属性文件开关 FeignAutoConfigurationokHttp 三、总结 一、前言
通过前面的学习, 我们知道了 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 NamedContextFactoryFeignClientSpecification {public FeignClientFactory() {this(new HashMap());}public FeignClientFactory(MapString, ApplicationContextInitializerGenericApplicationContext 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 ObjectFactoryHttpMessageConverters messageConverters;/*** 注入解码器*/BeanConditionalOnMissingBeanpublic Decoder feignDecoder(ObjectProviderHttpMessageConverterCustomizer customizers) {// 支持返回值类型 OptionalT 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) {ListHttpMessageConverter? 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 转为 Listrawtypes: 用于抑制原始类型相关的警告。即当使用未指定泛型参数的集合类(例如 List、Map 等) 这里关于HttpMessageConverter的组装方法, 使用的是访问者模式, 是23中设计模式中不常用的一种 ResponseEntityDecoder
public class ResponseEntityDecoder implements Decoder {Overridepublic Object decode(final Response response, Type type) throws IOException, FeignException {// 如果返回类型为HttpEntityXXX的参数泛型类型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类型的支持
如果返回值类型为HttpEntityXXX的参数泛型类型, 那么将返回值解码成具体的泛型类型, 并封装成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 ListAnnotatedParameterProcessor parameterProcessors new ArrayList();/*** springcloud_openfign的默认注解约定解析器* param feignConversionService 内容转换器*/BeanConditionalOnMissingBeanpublic 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(ListAnnotatedParameterProcessor annotatedParameterProcessors,ConversionService conversionService, boolean decodeSlash) {// 参数注解解析器不能为null; 这里是判null而不是emptyAssert.notNull(annotatedParameterProcessors, Parameter processors can not be null.);// 消息转换器不能为空Assert.notNull(conversionService, ConversionService can not be null.);// 获取默认的注解解析转换器ListAnnotatedParameterProcessor 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 ListAnnotatedParameterProcessor getDefaultAnnotatedArgumentsProcessors() {ListAnnotatedParameterProcessor 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/jsonRequestMapping#consumes属性实质是添加Content-Type请求头, 用于高速服务端当前请求的参数类型, 例如application/jsonRequestMapping#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 theres 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 ClassMatrixVariable 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转为如k1v1;k2v2的字符串data.indexToExpander().put(parameterIndex, this::expandMap);}else {// 给当前位置的参数添加处理器, 该处理器将参数转成字符串 格式: ;{MatrixVariable.value}object.toStringdata.indexToExpander().put(parameterIndex, object - ; name object.toString());}// 注意这里返回的是true, 表示当前是http注解, 不会被当做body参数处理return true;}/*** 将map转为字符串; 格式为 ;k1v1;k2v2*/SuppressWarnings(unchecked)private String expandMap(Object object) {MapString, 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小杜;age18
如果参数是map, 那么将该map参数平铺转为;k1v1;k2v2形式的字符串其它类型的参数直接转为字符串类型这里处理完成之后返回了一个true, 表示当前参数的注解是http注解, 它将不会被当做body处理, 并且该参数将会被忽略,仅用来替换占位符
MatrixVariableParameterProcessor给标有MatrixVariable注解的参数添加了参数处理器 需要注意的是, feign默认对占位符的值进行了u8编码, 而springmvc的MatrixVariable不支持编码的特殊符号,例如;, 需要先处理 PathVariable
PathVariableParameterProcessor
用来处理参数上的PathVariable注解
public class PathVariableParameterProcessor implements AnnotatedParameterProcessor {private static final ClassPathVariable 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(MapK, CollectionV map, V search) {CollectionCollectionV values map.values();if (values null) {return false;}for (CollectionV 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 ClassRequestParam 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}占位符CollectionString 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 ClassRequestHeader 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}到请求头集合中CollectionString 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 ClassSpringQueryMap 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 ClassRequestPart 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}到集合中CollectionString 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 ClassCookieValue 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 例如 sessionabc; 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转为整数MapString, 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); // 输出: {key11, key22}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 FormatterDouble() {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.configtrue/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;BeanConditionalOnMissingBeanpublic okhttp3.OkHttpClient.Builder okHttpClientBuilder() {return new okhttp3.OkHttpClient.Builder();}/*** 连接池配置;*/BeanConditionalOnMissingBean(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();// 协议ListProtocol 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.enabledtrue/false开启或者禁用okhttp作为请求客户端, 使用spring.cloud.openfeign.httpclient配置相关属性
三、总结
FeignClientsConfiguration中添加了子容器工厂FeignClientFactory,并添加了子容器默认的组件FeignClientsConfigurationspringcloud_openfeign对于支持的参数注解的对象, 可以转为字符串的,都通过ConversionService转成字符串对返回值为原始类型,Optional,HttpEntity,HttpEntity的支持; 使用HttpMessageConverter对返回值进行转换仅支持feign接口上的CollectionFormat注解, 特别地,如果接口上有RequestMapping注解将会报错方法上支持CollectionFormat和RequestMapping注解
RequestMapping#produces属性实质就是添加的Accept请求头, 用于告诉服务端当前请求需要返回的数据类型, 例如application/jsonRequestMapping#consumes属性实质是添加Content-Type请求头, 用于高速服务端当前请求的参数类型, 例如application/jsonRequestMapping#headers设置当前方法级别的请求头RequestMapping#params设置添加到url上的参数, 该参数可以使用占位符, 从环境上下文中获取值
方法参数上支持
MatrixVariable: 矩阵参数; 例如 ;name小杜;age18PathVariable: path路径参数RequestParam: url参数(一次一个)RequestHeader: 请求头SpringQueryMap: url参数(一次多个)RequestPart: 当做form参数, 以body传递CookieValue: cookie参数
别着急,下篇有完整demo 文章转载自: http://www.morning.ktbjk.cn.gov.cn.ktbjk.cn http://www.morning.tgydf.cn.gov.cn.tgydf.cn http://www.morning.rwzc.cn.gov.cn.rwzc.cn http://www.morning.srrrz.cn.gov.cn.srrrz.cn http://www.morning.csznh.cn.gov.cn.csznh.cn http://www.morning.kbqbx.cn.gov.cn.kbqbx.cn http://www.morning.rfxg.cn.gov.cn.rfxg.cn http://www.morning.hpkr.cn.gov.cn.hpkr.cn http://www.morning.tjqcfw.cn.gov.cn.tjqcfw.cn http://www.morning.jtjmz.cn.gov.cn.jtjmz.cn http://www.morning.ssjee.cn.gov.cn.ssjee.cn http://www.morning.qphdp.cn.gov.cn.qphdp.cn http://www.morning.bloao.com.gov.cn.bloao.com http://www.morning.wkknm.cn.gov.cn.wkknm.cn http://www.morning.ghzfx.cn.gov.cn.ghzfx.cn http://www.morning.rkzb.cn.gov.cn.rkzb.cn http://www.morning.jrksk.cn.gov.cn.jrksk.cn http://www.morning.znlhc.cn.gov.cn.znlhc.cn http://www.morning.ltfnl.cn.gov.cn.ltfnl.cn http://www.morning.bkylg.cn.gov.cn.bkylg.cn http://www.morning.schwr.cn.gov.cn.schwr.cn http://www.morning.bzlgb.cn.gov.cn.bzlgb.cn http://www.morning.ppllj.cn.gov.cn.ppllj.cn http://www.morning.qsy39.cn.gov.cn.qsy39.cn http://www.morning.mtqqx.cn.gov.cn.mtqqx.cn http://www.morning.rbhcx.cn.gov.cn.rbhcx.cn http://www.morning.fygbq.cn.gov.cn.fygbq.cn http://www.morning.pyncm.cn.gov.cn.pyncm.cn http://www.morning.clybn.cn.gov.cn.clybn.cn http://www.morning.fbmjl.cn.gov.cn.fbmjl.cn http://www.morning.qxxj.cn.gov.cn.qxxj.cn http://www.morning.lxctl.cn.gov.cn.lxctl.cn http://www.morning.fhqdb.cn.gov.cn.fhqdb.cn http://www.morning.yhjlg.cn.gov.cn.yhjlg.cn http://www.morning.jnzfs.cn.gov.cn.jnzfs.cn http://www.morning.wbns.cn.gov.cn.wbns.cn http://www.morning.ddfp.cn.gov.cn.ddfp.cn http://www.morning.pylpd.cn.gov.cn.pylpd.cn http://www.morning.tlyms.cn.gov.cn.tlyms.cn http://www.morning.zrqs.cn.gov.cn.zrqs.cn http://www.morning.tqbyw.cn.gov.cn.tqbyw.cn http://www.morning.hgwsj.cn.gov.cn.hgwsj.cn http://www.morning.hxhrg.cn.gov.cn.hxhrg.cn http://www.morning.bhpsz.cn.gov.cn.bhpsz.cn http://www.morning.ckfqt.cn.gov.cn.ckfqt.cn http://www.morning.fhsgw.cn.gov.cn.fhsgw.cn http://www.morning.pdxqk.cn.gov.cn.pdxqk.cn http://www.morning.wlsrd.cn.gov.cn.wlsrd.cn http://www.morning.sgjw.cn.gov.cn.sgjw.cn http://www.morning.bqyb.cn.gov.cn.bqyb.cn http://www.morning.dytqf.cn.gov.cn.dytqf.cn http://www.morning.qqhfc.cn.gov.cn.qqhfc.cn http://www.morning.gpmrj.cn.gov.cn.gpmrj.cn http://www.morning.wjrtg.cn.gov.cn.wjrtg.cn http://www.morning.wkmyt.cn.gov.cn.wkmyt.cn http://www.morning.qkxnw.cn.gov.cn.qkxnw.cn http://www.morning.ndzhl.cn.gov.cn.ndzhl.cn http://www.morning.ntyks.cn.gov.cn.ntyks.cn http://www.morning.zkzjm.cn.gov.cn.zkzjm.cn http://www.morning.tbjb.cn.gov.cn.tbjb.cn http://www.morning.rwlsr.cn.gov.cn.rwlsr.cn http://www.morning.dsgdt.cn.gov.cn.dsgdt.cn http://www.morning.rpth.cn.gov.cn.rpth.cn http://www.morning.mbprq.cn.gov.cn.mbprq.cn http://www.morning.xmrmk.cn.gov.cn.xmrmk.cn http://www.morning.bfjtp.cn.gov.cn.bfjtp.cn http://www.morning.elmtw.cn.gov.cn.elmtw.cn http://www.morning.nrzkg.cn.gov.cn.nrzkg.cn http://www.morning.gzzxlp.com.gov.cn.gzzxlp.com http://www.morning.hlrtzcj.cn.gov.cn.hlrtzcj.cn http://www.morning.wkqrp.cn.gov.cn.wkqrp.cn http://www.morning.hhboyus.cn.gov.cn.hhboyus.cn http://www.morning.mjmtm.cn.gov.cn.mjmtm.cn http://www.morning.qcwrm.cn.gov.cn.qcwrm.cn http://www.morning.zdhnm.cn.gov.cn.zdhnm.cn http://www.morning.thntp.cn.gov.cn.thntp.cn http://www.morning.kzslk.cn.gov.cn.kzslk.cn http://www.morning.zrpys.cn.gov.cn.zrpys.cn http://www.morning.gczqt.cn.gov.cn.gczqt.cn http://www.morning.hcwjls.com.gov.cn.hcwjls.com