源码解析: Feign RequestTemplate

MethodMetaData在Contract组件中被创建的时候已经创建了RequestTemplate,里面已经包含了一些抽取出来的原始数据。但是MethodMetaData在Contract的apply方法中被使用时,你会发现MethodMetaData里面的RequestTemplate,又被拿出来作为原材料按照不同的情况去创建已了一个新的RequestTemplate.Factory,塞入MethodHandler中待用。为啥呢?

public Map apply(Target key) {
      //通过Contract抽取出MethodMetadata,里面包含RequestTemplate。
      List metadata = contract.parseAndValidatateMetadata(key.type());
      Map result = new LinkedHashMap();
      for (MethodMetadata md : metadata) {
        BuildTemplateByResolvingArgs buildTemplate;
        //如果表单参数不为空并且请求体模板为空(说明我们的请求参数是普通的路径参数/查询参数)
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
        //创建BuildFormEncodedTemplateFromArgs。从名字理解这个工厂的意思是“从参数创建表单已编码的RequestTemplate”
          buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder);
        } else if (md.bodyIndex() != null) {
        //如果有啥也没标记的方法参数,创建工厂“从参数创建已编码的RequestTemplate”
          buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder);
        } else {
        //其他情况,创建工厂“通过解析参数创建RequestTemplate”
          buildTemplate = new BuildTemplateByResolvingArgs(md);
        }
        result.put(md.configKey(),
                   factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
      }
      return result;
    }
  }
 private static class BuildTemplateByResolvingArgs implements RequestTemplate.Factory {
    //我们解析出的md
    protected final MethodMetadata metadata;
    //@Param参数中的Expander,key是哪个参数的序号
    private final Map indexToExpander = new LinkedHashMap();
    //这个方法
    private BuildTemplateByResolvingArgs(MethodMetadata metadata) {
      this.metadata = metadata;
      //找到MD中是否有expander信息,有就倒腾过来。
      //注意这里其实一开始是没有的。因为在Contract的解析逻辑中我们是把Expander的信息放在了indexToExpanderClass里面,没有去实例化那些Expander类。
      //只有重写了Contract逻辑,调用了feign.MethodMetadata#indexToExpander(java.util.Map)方法,动态注入了Expander实例的情况才会使用。
     //这样原本的indexToExpanderClass就没用了,因为可以看到代码直接返回了。
      if (metadata.indexToExpander() != null) {
        indexToExpander.putAll(metadata.indexToExpander());
        return;
      }
      if (metadata.indexToExpanderClass().isEmpty()) {
        return;
      }
      //如果MD中填充了indexToExpanderClass相关信息,将Expander实例化,放入工厂类自己的indexToExpander待用。
      for (Entry> indexToExpanderClass : metadata
          .indexToExpanderClass().entrySet()) {
        try {
          indexToExpander
              .put(indexToExpanderClass.getKey(), indexToExpanderClass.getValue().newInstance());
        } catch (InstantiationException e) {
          throw new IllegalStateException(e);
        } catch (IllegalAccessException e) {
          throw new IllegalStateException(e);
        }
      }
    }
    
    @Override
    public RequestTemplate create(Object[] argv) {
      //将md中原本的RequestTemplate取出,创建新的RequestTemplate,以供修改,咋改呢?
      RequestTemplate mutable = new RequestTemplate(metadata.template());
     //从Contract的分析可知这里的urlIndex,代表的是方法中的参数可以是一个URI。
     //会在这里抽取出来被拼接到原本的url的前面。
     //但是我翻遍了wiki也没发现这样的用法的讲解。而且想想这样用不是很麻烦么,估计是以前的遗留功能,后来没用了吧。
      if (metadata.urlIndex() != null) {
        int urlIndex = metadata.urlIndex();
        checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
        mutable.insert(0, String.valueOf(argv[urlIndex]));
      }
     //通过indexToName,解析Param注解了的参数的值,特别是自定义了Expander的,将在这替换成真正的值,存入varBuilder。key是注解里面的名字,value是真正展开后的值。
       Map varBuilder = new LinkedHashMap();
      for (Entry> entry : metadata.indexToName().entrySet()) {
        int i = entry.getKey();
        Object value = argv[entry.getKey()];
        if (value != null) { // Null values are skipped.
          if (indexToExpander.containsKey(i)) {
            value = expandElements(indexToExpander.get(i), value);
          }
          for (String name : entry.getValue()) {
            varBuilder.put(name, value);
          }
        }
      }
    
      RequestTemplate template = resolve(argv, mutable, varBuilder);
      if (metadata.queryMapIndex() != null) {
        // add query map parameters after initial resolve so that they take
        // precedence over any predefined values
        template = addQueryMapQueryParameters((Map) argv[metadata.queryMapIndex()], template);
      }

      if (metadata.headerMapIndex() != null) {
        template = addHeaderMapHeaders((Map) argv[metadata.headerMapIndex()], template);
      }

      return template;
    }

    private Object expandElements(Expander expander, Object value) {
      if (value instanceof Iterable) {
        return expandIterable(expander, (Iterable) value);
      }
      return expander.expand(value);
    }

    private List expandIterable(Expander expander, Iterable value) {
      List values = new ArrayList();
      for (Object element : (Iterable) value) {
        if (element!=null) {
          values.add(expander.expand(element));
        }
      }
      return values;
    }

    @SuppressWarnings("unchecked")
    private RequestTemplate addHeaderMapHeaders(Map headerMap, RequestTemplate mutable) {
      for (Entry currEntry : headerMap.entrySet()) {
        Collection values = new ArrayList();

        Object currValue = currEntry.getValue();
        if (currValue instanceof Iterable) {
          Iterator iter = ((Iterable) currValue).iterator();
          while (iter.hasNext()) {
            Object nextObject = iter.next();
            values.add(nextObject == null ? null : nextObject.toString());
          }
        } else {
          values.add(currValue == null ? null : currValue.toString());
        }

        mutable.header(currEntry.getKey(), values);
      }
      return mutable;
    }

    @SuppressWarnings("unchecked")
    private RequestTemplate addQueryMapQueryParameters(Map queryMap, RequestTemplate mutable) {
      for (Entry currEntry : queryMap.entrySet()) {
        Collection values = new ArrayList();

        boolean encoded = metadata.queryMapEncoded();
        Object currValue = currEntry.getValue();
        if (currValue instanceof Iterable) {
          Iterator iter = ((Iterable) currValue).iterator();
          while (iter.hasNext()) {
            Object nextObject = iter.next();
            values.add(nextObject == null ? null : encoded ? nextObject.toString() : RequestTemplate.urlEncode(nextObject.toString()));
          }
        } else {
          values.add(currValue == null ? null : encoded ? currValue.toString() : RequestTemplate.urlEncode(currValue.toString()));
        }

        mutable.query(true, encoded ? currEntry.getKey() : RequestTemplate.urlEncode(currEntry.getKey()), values);
      }
      return mutable;
    }

    protected RequestTemplate resolve(Object[] argv, RequestTemplate mutable,
                                      Map variables) {
      // Resolving which variable names are already encoded using their indices
      Map variableToEncoded = new LinkedHashMap();
      for (Entry entry : metadata.indexToEncoded().entrySet()) {
        Collection names = metadata.indexToName().get(entry.getKey());
        for (String name : names) {
          variableToEncoded.put(name, entry.getValue());
        }
      }
      return mutable.resolve(variables, variableToEncoded);
    }
  }
/*
 * Copyright 2013 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package feign;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import static feign.Util.CONTENT_LENGTH;
import static feign.Util.UTF_8;
import static feign.Util.checkArgument;
import static feign.Util.checkNotNull;
import static feign.Util.emptyToNull;
import static feign.Util.toArray;
import static feign.Util.valuesOrEmpty;

/**
 * Builds a request to an http target. Not thread safe. 


relationship to JAXRS * 2.0

A combination of {@code javax.ws.rs.client.WebTarget} and {@code * javax.ws.rs.client.Invocation.Builder}, ensuring you can modify any part of the request. However, * this object is mutable, so needs to be guarded with the copy constructor. */ public final class RequestTemplate implements Serializable { private static final long serialVersionUID = 1L; private final Map> queries = new LinkedHashMap>(); private final Map> headers = new LinkedHashMap>(); private String method; /* final to encourage mutable use vs replacing the object. */ private StringBuilder url = new StringBuilder(); private transient Charset charset; private byte[] body; private String bodyTemplate; private boolean decodeSlash = true; public RequestTemplate() { } /* Copy constructor. Use this when making templates. */ public RequestTemplate(RequestTemplate toCopy) { checkNotNull(toCopy, "toCopy"); this.method = toCopy.method; this.url.append(toCopy.url); this.queries.putAll(toCopy.queries); this.headers.putAll(toCopy.headers); this.charset = toCopy.charset; this.body = toCopy.body; this.bodyTemplate = toCopy.bodyTemplate; this.decodeSlash = toCopy.decodeSlash; } private static String urlDecode(String arg) { try { return URLDecoder.decode(arg, UTF_8.name()); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } static String urlEncode(Object arg) { try { return URLEncoder.encode(String.valueOf(arg), UTF_8.name()); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } private static boolean isHttpUrl(CharSequence value) { return value.length() >= 4 && value.subSequence(0, 3).equals("http".substring(0, 3)); } private static CharSequence removeTrailingSlash(CharSequence charSequence) { if (charSequence != null && charSequence.length() > 0 && charSequence.charAt(charSequence.length() - 1) == '/') { return charSequence.subSequence(0, charSequence.length() - 1); } else { return charSequence; } } /** * Expands a {@code template}, such as {@code username}, using the {@code variables} supplied. Any * unresolved parameters will remain.
Note that if you'd like curly braces literally in the * {@code template}, urlencode them first. * * @param template URI template that can be in level 1 RFC6570 * form. * @param variables to the URI template * @return expanded template, leaving any unresolved parameters literal */ public static String expand(String template, Map variables) { // skip expansion if there's no valid variables set. ex. {a} is the // first valid if (checkNotNull(template, "template").length() < 3) { return template; } checkNotNull(variables, "variables for %s", template); boolean inVar = false; StringBuilder var = new StringBuilder(); StringBuilder builder = new StringBuilder(); for (char c : template.toCharArray()) { switch (c) { case '{': if (inVar) { // '{{' is an escape: write the brace and don't interpret as a variable builder.append("{"); inVar = false; break; } inVar = true; break; case '}': if (!inVar) { // then write the brace literally builder.append('}'); break; } inVar = false; String key = var.toString(); Object value = variables.get(var.toString()); if (value != null) { builder.append(value); } else { builder.append('{').append(key).append('}'); } var = new StringBuilder(); break; default: if (inVar) { var.append(c); } else { builder.append(c); } } } return builder.toString(); } //将查询字符串解析成ma private static Map> parseAndDecodeQueries(String queryLine) { Map> map = new LinkedHashMap>(); if (emptyToNull(queryLine) == null) { return map; } //如果没有&这个符号说明争哥问号后面的查询参数就一个,直接放进去。 //如果有,则遍历此字符串一个个的塞。值得注意的是,putKV这个方法中还将我们的查询字符串先解码了的。也就是说就算我们当时写的时候有中文而且是编过码的。 //比如“name=张三”,我们写的是“name%3D%E5%BC%A0%E4%B8%89”。也能正确解码出来。放入结果集中。 if (queryLine.indexOf('&') == -1) { putKV(queryLine, map); } else { char[] chars = queryLine.toCharArray(); int start = 0; int i = 0; for (; i < chars.length; i++) { if (chars[i] == '&') { putKV(queryLine.substring(start, i), map); start = i + 1; } } putKV(queryLine.substring(start, i), map); } return map; } private static void putKV(String stringToParse, Map> map) { String key; String value; // note that '=' can be a valid part of the value int firstEq = stringToParse.indexOf('='); if (firstEq == -1) { key = urlDecode(stringToParse); value = null; } else { key = urlDecode(stringToParse.substring(0, firstEq)); value = urlDecode(stringToParse.substring(firstEq + 1)); } Collection values = map.containsKey(key) ? map.get(key) : new ArrayList(); values.add(value); map.put(key, values); } /** {@link #resolve(Map, Map)}, which assumes no parameter is encoded */ public RequestTemplate resolve(Map unencoded) { return resolve(unencoded, Collections.emptyMap()); } /** * Resolves any template parameters in the requests path, query, or headers against the supplied * unencoded arguments.


relationship to JAXRS 2.0

This call is * similar to {@code javax.ws.rs.client.WebTarget.resolveTemplates(templateValues, true)} , except * that the template values apply to any part of the request, not just the URL */ RequestTemplate resolve(Map unencoded, Map alreadyEncoded) { replaceQueryValues(unencoded, alreadyEncoded); Map encoded = new LinkedHashMap(); for (Entry entry : unencoded.entrySet()) { final String key = entry.getKey(); final Object objectValue = entry.getValue(); String encodedValue = encodeValueIfNotEncoded(key, objectValue, alreadyEncoded); encoded.put(key, encodedValue); } String resolvedUrl = expand(url.toString(), encoded).replace("+", "%20"); if (decodeSlash) { resolvedUrl = resolvedUrl.replace("%2F", "/"); } url = new StringBuilder(resolvedUrl); Map> resolvedHeaders = new LinkedHashMap>(); for (String field : headers.keySet()) { Collection resolvedValues = new ArrayList(); for (String value : valuesOrEmpty(headers, field)) { String resolved = expand(value, unencoded); resolvedValues.add(resolved); } resolvedHeaders.put(field, resolvedValues); } headers.clear(); headers.putAll(resolvedHeaders); if (bodyTemplate != null) { body(urlDecode(expand(bodyTemplate, encoded))); } return this; } private String encodeValueIfNotEncoded(String key, Object objectValue, Map alreadyEncoded) { String value = String.valueOf(objectValue); final Boolean isEncoded = alreadyEncoded.get(key); if (isEncoded == null || !isEncoded) { value = urlEncode(value); } return value; } /* roughly analogous to {@code javax.ws.rs.client.Target.request()}. */ public Request request() { Map> safeCopy = new LinkedHashMap>(); safeCopy.putAll(headers); return Request.create( method, url + queryLine(), Collections.unmodifiableMap(safeCopy), body, charset ); } /* @see Request#method() */ public RequestTemplate method(String method) { this.method = checkNotNull(method, "method"); checkArgument(method.matches("^[A-Z]+$"), "Invalid HTTP Method: %s", method); return this; } /* @see Request#method() */ public String method() { return method; } public RequestTemplate decodeSlash(boolean decodeSlash) { this.decodeSlash = decodeSlash; return this; } public boolean decodeSlash() { return decodeSlash; } /* @see #url() */ //这个方法是在Contract设置url的时候被调用的,他不仅简单设置了url,而且调用了RequestTemplate的pullAnyQueriesOutOfUrl。分析了url上的查询参数,并从url上抽走,设置到queries成员变量里面去 public RequestTemplate append(CharSequence value) { url.append(value); url = pullAnyQueriesOutOfUrl(url); return this; } /* @see #url() */ public RequestTemplate insert(int pos, CharSequence value) { if(isHttpUrl(value)) { value = removeTrailingSlash(value); if(url.length() > 0 && url.charAt(0) != '/') { url.insert(0, '/'); } } url.insert(pos, pullAnyQueriesOutOfUrl(new StringBuilder(value))); return this; } public String url() { return url.toString(); } /** * Replaces queries with the specified {@code name} with the {@code values} supplied. *
Values can be passed in decoded or in url-encoded form depending on the value of the * {@code encoded} parameter. *
When the {@code value} is {@code null}, all queries with the {@code configKey} are * removed.


relationship to JAXRS 2.0

Like {@code WebTarget.query}, * except the values can be templatized.
ex.
*
   * template.query("Signature", "{signature}");
   * 
*
Note: behavior of RequestTemplate is not consistent if a query parameter with * unsafe characters is passed as both encoded and unencoded, although no validation is performed. *
ex.
*
   * template.query(true, "param[]", "value");
   * template.query(false, "param[]", "value");
   * 
* * @param encoded whether name and values are already url-encoded * @param name the name of the query * @param values can be a single null to imply removing all values. Else no values are expected * to be null. * @see #queries() */ public RequestTemplate query(boolean encoded, String name, String... values) { return doQuery(encoded, name, values); } /* @see #query(boolean, String, String...) */ public RequestTemplate query(boolean encoded, String name, Iterable values) { return doQuery(encoded, name, values); } /** * Shortcut for {@code query(false, String, String...)} * @see #query(boolean, String, String...) */ public RequestTemplate query(String name, String... values) { return doQuery(false, name, values); } /** * Shortcut for {@code query(false, String, Iterable)} * @see #query(boolean, String, String...) */ public RequestTemplate query(String name, Iterable values) { return doQuery(false, name, values); } //doQuery方法实际逻辑在这,其实就是为RequestTemplate的queries属性放入查询参数的键值对,而且是编过码的。 private RequestTemplate doQuery(boolean encoded, String name, String... values) { checkNotNull(name, "name"); String paramName = encoded ? name : encodeIfNotVariable(name); queries.remove(paramName); if (values != null && values.length > 0 && values[0] != null) { ArrayList paramValues = new ArrayList(); for (String value : values) { paramValues.add(encoded ? value : encodeIfNotVariable(value)); } this.queries.put(paramName, paramValues); } return this; } private RequestTemplate doQuery(boolean encoded, String name, Iterable values) { if (values != null) { return doQuery(encoded, name, toArray(values, String.class)); } return doQuery(encoded, name, (String[]) null); } private static String encodeIfNotVariable(String in) { if (in == null || in.indexOf('{') == 0) { return in; } return urlEncode(in); } /** * Replaces all existing queries with the newly supplied url decoded queries.
*

relationship to JAXRS 2.0

Like {@code WebTarget.queries}, except the * values can be templatized.
ex.
*
   * template.queries(ImmutableMultimap.of("Signature", "{signature}"));
   * 
* * @param queries if null, remove all queries. else value to replace all queries with. * @see #queries() */ public RequestTemplate queries(Map> queries) { if (queries == null || queries.isEmpty()) { this.queries.clear(); } else { for (Entry> entry : queries.entrySet()) { query(entry.getKey(), toArray(entry.getValue(), String.class)); } } return this; } /** * Returns an immutable copy of the url decoded queries. * * @see Request#url() */ public Map> queries() { Map> decoded = new LinkedHashMap>(); for (String field : queries.keySet()) { Collection decodedValues = new ArrayList(); for (String value : valuesOrEmpty(queries, field)) { if (value != null) { decodedValues.add(urlDecode(value)); } else { decodedValues.add(null); } } decoded.put(urlDecode(field), decodedValues); } return Collections.unmodifiableMap(decoded); } /** * Replaces headers with the specified {@code configKey} with the {@code values} supplied.
* When the {@code value} is {@code null}, all headers with the {@code configKey} are removed. *


relationship to JAXRS 2.0

Like {@code WebTarget.queries} and * {@code javax.ws.rs.client.Invocation.Builder.header}, except the values can be templatized. *
ex.
*
   * template.query("X-Application-Version", "{version}");
   * 
* * @param name the name of the header * @param values can be a single null to imply removing all values. Else no values are expected to * be null. * @see #headers() */ public RequestTemplate header(String name, String... values) { checkNotNull(name, "header name"); if (values == null || (values.length == 1 && values[0] == null)) { headers.remove(name); } else { List headers = new ArrayList(); headers.addAll(Arrays.asList(values)); this.headers.put(name, headers); } return this; } /* @see #header(String, String...) */ public RequestTemplate header(String name, Iterable values) { if (values != null) { return header(name, toArray(values, String.class)); } return header(name, (String[]) null); } /** * Replaces all existing headers with the newly supplied headers.


relationship to * JAXRS 2.0

Like {@code Invocation.Builder.headers(MultivaluedMap)}, except the * values can be templatized.
ex.
*
   * template.headers(mapOf("X-Application-Version", asList("{version}")));
   * 
* * @param headers if null, remove all headers. else value to replace all headers with. * @see #headers() */ public RequestTemplate headers(Map> headers) { if (headers == null || headers.isEmpty()) { this.headers.clear(); } else { this.headers.putAll(headers); } return this; } /** * Returns an immutable copy of the current headers. * * @see Request#headers() */ public Map> headers() { return Collections.unmodifiableMap(headers); } /** * replaces the {@link feign.Util#CONTENT_LENGTH} header.
Usually populated by an {@link * feign.codec.Encoder}. * * @see Request#body() */ public RequestTemplate body(byte[] bodyData, Charset charset) { this.bodyTemplate = null; this.charset = charset; this.body = bodyData; int bodyLength = bodyData != null ? bodyData.length : 0; header(CONTENT_LENGTH, String.valueOf(bodyLength)); return this; } /** * replaces the {@link feign.Util#CONTENT_LENGTH} header.
Usually populated by an {@link * feign.codec.Encoder}. * * @see Request#body() */ public RequestTemplate body(String bodyText) { byte[] bodyData = bodyText != null ? bodyText.getBytes(UTF_8) : null; return body(bodyData, UTF_8); } /** * The character set with which the body is encoded, or null if unknown or not applicable. When * this is present, you can use {@code new String(req.body(), req.charset())} to access the body * as a String. */ public Charset charset() { return charset; } /** * @see Request#body() */ public byte[] body() { return body; } /** * populated by {@link Body} * * @see Request#body() */ public RequestTemplate bodyTemplate(String bodyTemplate) { this.bodyTemplate = bodyTemplate; this.charset = null; this.body = null; return this; } /** * @see Request#body() * @see #expand(String, Map) */ public String bodyTemplate() { return bodyTemplate; } /** * if there are any query params in the URL, this will extract them out. */ private StringBuilder pullAnyQueriesOutOfUrl(StringBuilder url) { // parse out queries int queryIndex = url.indexOf("?"); if (queryIndex != -1) { //抽取问号后面的查询参数字符串 String queryLine = url.substring(queryIndex + 1); // Map> firstQueries = parseAndDecodeQueries(queryLine); if (!queries.isEmpty()) { firstQueries.putAll(queries); queries.clear(); } //Since we decode all queries, we want to use the //query()-method to re-add them to ensure that all //logic (such as url-encoding) are executed, giving //a valid queryLine() for (String key : firstQueries.keySet()) { Collection values = firstQueries.get(key); if (allValuesAreNull(values)) { //Queries where all values are null will //be ignored by the query(key, value)-method //So we manually avoid this case here, to ensure that //we still fulfill the contract (ex. parameters without values) queries.put(urlEncode(key), values); } else { query(key, values); } } return new StringBuilder(url.substring(0, queryIndex)); } return url; } private boolean allValuesAreNull(Collection values) { if (values == null || values.isEmpty()) { return true; } for (String val : values) { if (val != null) { return false; } } return true; } @Override public String toString() { return request().toString(); } /** {@link #replaceQueryValues(Map, Map)}, which assumes no parameter is encoded */ public void replaceQueryValues(Map unencoded) { replaceQueryValues(unencoded, Collections.emptyMap()); } /** * Replaces query values which are templated with corresponding values from the {@code unencoded} * map. Any unresolved queries are removed. */ void replaceQueryValues(Map unencoded, Map alreadyEncoded) { // Iterator>> iterator = queries.entrySet().iterator(); while (iterator.hasNext()) { Entry> entry = iterator.next(); if (entry.getValue() == null) { continue; } Collection values = new ArrayList(); for (String value : entry.getValue()) { if (value.indexOf('{') == 0 && value.indexOf('}') == value.length() - 1) { Object variableValue = unencoded.get(value.substring(1, value.length() - 1)); // only add non-null expressions if (variableValue == null) { continue; } if (variableValue instanceof Iterable) { for (Object val : Iterable.class.cast(variableValue)) { String encodedValue = encodeValueIfNotEncoded(entry.getKey(), val, alreadyEncoded); values.add(encodedValue); } } else { String encodedValue = encodeValueIfNotEncoded(entry.getKey(), variableValue, alreadyEncoded); values.add(encodedValue); } } else { values.add(value); } } if (values.isEmpty()) { iterator.remove(); } else { entry.setValue(values); } } } public String queryLine() { if (queries.isEmpty()) { return ""; } StringBuilder queryBuilder = new StringBuilder(); for (String field : queries.keySet()) { for (String value : valuesOrEmpty(queries, field)) { queryBuilder.append('&'); queryBuilder.append(field); if (value != null) { queryBuilder.append('='); if (!value.isEmpty()) { queryBuilder.append(value); } } } } queryBuilder.deleteCharAt(0); return queryBuilder.insert(0, '?').toString(); } interface Factory { /** * create a request template using args passed to a method invocation. */ RequestTemplate create(Object[] argv); } }

你可能感兴趣的:(源码解析: Feign RequestTemplate)