Feign高级功能 
动态修改请求地址 像这样,创建接口时放置一个类型为java.net.URI的参数,这样真正发送请求时就会以此uri为准。
1 2 3 4 interface  dyPath  {@RequestLine("GET /get/item") getItem (URI uri) ;
动态请求配置Options 放置一个类型为Options的参数在接口中,这样发送请求时就会使用此类作为配置,否则会使用默认的配置
1 2 3 4 interface  dyPath  {@RequestLine("GET /get/item") getItem (Request.Options options) ;
1 2 3 4 5 6 7 8 9 10 11 findOptions (Object[] argv)  {if  (argv == null  || argv.length == 0 ) {return  this .options;return  Stream.of(argv)this .options);
@QueryMap注解 
这个注解如果注在Map上,此Map的键只能是String类型的如Map<String,Object>,其他类型会报错。
  1 2 3 4 5 if  (keyClass != null ) {"%s key must be a String: %s" , name, keyClass.getSimpleName());
如果在@RequestLine的链接中放置了参数, @QueryMap中有同名参数是一个空集合,则会把前面的参数删掉,所以想要删除某参数,也可以直接设置一个空集合参数,同理添加heads也有这样的删除策略。
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15     interface  Fq  {@RequestLine(value = "GET /cms/materials/create?username={username}") getItemCoupon (@Param(value = "username")  String username                         , @QueryMap  Map<String, Object> params) ;new  HashMap <>();"username" , Collections.emptyList());String  itemCoupon  =  target.getItemCoupon("小明" , params);
@QueryMap解析出来的参数只会拼接在url后面,不会当成请求体,无论请求类型是什么。
 有时候post请求不希望参数拼在链接后面,就不能用这个了。
 
 
@Param注解注意事项 注解标记的参数,如果没有在任何地方被使用,则会被放入MethodMetadata的formParams中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 super .registerParameterAnnotation(Param.class, (paramAnnotation, data, paramIndex) -> {String  name  =  paramAnnotation.value();null , "Param annotation was empty on param %s." ,extends  Param .Expander> expander = paramAnnotation.expander();if  (expander != Param.ToStringExpander.class) {if  (!data.template().hasRequestVariable(name)) {
如下面写法的两个参数name和age 就是未使用因为RequestLine和Headers没有用它:
1 2 3 4 5 interface  TestJson  {@RequestLine(value = "POST /test") @Headers("Content-Type:application/json") getItemCoupon (@Param("name")  String name,@Param("age")  Integer age) ;
在发送请求时,会将他们组成一个map然后传入encoder中进行编码,编码后的结果作为请求的body使用,而默认的encoder不能处理map类型的,会报错.
feign.codec.EncodeException: class java.util.LinkedHashMap is not a type supported by this encoder
添加一个JacksonEncoder后可以将他们序列化成JSON字符串作为请求body,即使是GET请求也会变成请求body,所以如果这不是你想要的结果,就不要添加了参数却不使用它。
如果不想将他们转成JSON,可以自己写encoder来处理 。
没有加任何注解的参数 默认情况下允许存在一个没有注解的参数(注意URI不包括在内),将它的参数索引设置成MeteDate的bodyIndex,发送请求前,会把这个参数的值取出来,然后调用encoder进行编码,编码的结果当成请求body使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 protected  MethodMetadata parseAndValidateMetadata (Class<?> targetType, Method method)  {MethodMetadata  data  =  new  MethodMetadata ();if  (targetType.getInterfaces().length == 1 ) {0 ]);for  (Annotation methodAnnotation : method.getAnnotations()) {if  (data.isIgnored()) {return  data;int  count  =  parameterAnnotations.length;for  (int  i  =  0 ; i < count; i++) {boolean  isHttpAnnotation  =  false ;if  (parameterAnnotations[i] != null ) {if  (isHttpAnnotation) {if  (parameterTypes[i] == URI.class) {else  if  (!isHttpAnnotation && parameterTypes[i] != Request.Options.class"Body parameters cannot be used with form parameters." );null , "Method has too many Body parameters: %s" , method);if  (data.headerMapIndex() != null ) {"HeaderMap" , parameterTypes[data.headerMapIndex()],if  (data.queryMapIndex() != null ) {if  (Map.class.isAssignableFrom(parameterTypes[data.queryMapIndex()])) {"QueryMap" , genericParameterTypes[data.queryMapIndex()]);return  data;
默认的encoder只能处理String和byte[]类型的参数,其他参数需要自定义encoder来处理。
如下面两种情况,存在没注解的参数,会把他们转成请求体使用:
1 2 3 4 interface  TestJson  {@RequestLine(value = "POST /test") getItemCoupon (String name) ;
1 2 3 4 interface  TestJson  {@RequestLine(value = "POST /test") getItemCoupon (Map<String,Object> name) ;
需要注意的一点是,这种方式构建请求body和上面@Param注解注意事项 里面未使用的@Param标记构建请求body的方式一起存在的话,有两种可能。
如果@Param的参数在无注解参数的前面,则按照上面代码49行会抛出Body parameters cannot be used with form parameters,提示body参数不能和表单参数一起用。
如果无注解参数放在@Param参数前面,无注解方式会被忽略,发送请求体由@Param的参数决定。	
 
三种设置请求体的优先级 一共三种设置请求体的方式,分别是:
1.使用@Body注解 
2.使用没有任何注解的参数 (这样的参数只能存在一个) 
3.使用没有被使用的@Param标记的参数 (这个可以有多个参数) 
 
他们之间的关系很复杂不能简单的用优先级来排序了。
1 2 3 4 5 6 7 8 super .registerMethodAnnotation(Body.class, (ann, data) -> {String  body  =  ann.value();if  (body.indexOf('{' ) == -1 ) {else  {
可以看到,如果有body注解,如果body注解的值是固定的,则将值设置为body,如果是可变的设置为bodyTemplate
 
根据上面Param注解注意事项 源码看出,如果@Param标记的参数没被使用,则放入data.formParams()中
 
在根据上面讲的没有加任何注解的参数 , 则会将他设置为data.bodyIndex(i)
 
且bodyIndex只能设置一个,设置bodyIndex时会断言formParams为空,所以后两个同时使用的话,要保证后者放在参数的前面,先解析才不会报错。
调用时是这样的:feign.ReflectiveFeign.ParseHandlersByName#apply
1 2 3 4 5 6 7 8 9 10 11 if  (!md.formParams().isEmpty() && md.template().bodyTemplate() == null ) {new  BuildFormEncodedTemplateFromArgs (md, encoder, queryMapEncoder, target);else  if  (md.bodyIndex() != null ) {new  BuildEncodedTemplateFromArgs (md, encoder, queryMapEncoder, target);else  {new  BuildTemplateByResolvingArgs (md, queryMapEncoder, target);
所以他们的优先级是 
formParams > bodyIndex
bodyIndex优先级 > bodyTemplate
bodyTemplate > formParams
 
简直是个三角形的关系。
但我觉得将无注解参数放在@Param参数前就不报错,应该数据bug。