Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析

1年前 (2022) 程序员胖胖胖虎阿
303 0 0

文章目录

  • 前言
  • 核心类
    • RequestTemplate
  • 源码分析
    • 1. 进入工厂类
    • 2. 请求参数为对象【POST】
    • 3. 请求参数为对象【GET】
    • 4. @RequestParam 注解【GET】
    • 5. 转换为Request

前言

紧接上文,之前分析了方法处理器是如何进行加载及执行的,其中提到了,在调用Feign 接口方法时,会根据方法参数创建请求模板(RequestTemplate),接下来我们分析下这个是如何加载及执行的。

核心类

RequestTemplate

Feign 中存在各种各样的模板类,RequestTemplate就是其中封装了执行请求需要的相关信息,比如请求方式、路径等。

RequestTemplate中封装了执行请求所需的相关信息:

	// 查询模板
    private final Map<String, QueryTemplate> queries = new LinkedHashMap();
    // 消息头模板
    private final Map<String, HeaderTemplate> headers;
    // 请求的目标地址
    private String target;
    // URL 后面的片段
    private String fragment;
    // 是否已解析
    private boolean resolved;
    // URI 模板
    private UriTemplate uriTemplate;
    // 请求体模板
    private BodyTemplate bodyTemplate;
    // 请求方式
    private HttpMethod method;
    // 字符集
    private transient Charset charset;
    // 请求体
    private Body body;
    // 是否斜杠转义
    private boolean decodeSlash;
    // 集合格式化
    private CollectionFormat collectionFormat;
    // 方法元数据
    private MethodMetadata methodMetadata;
    // 代理的目标对象
    private Target<?> feignTarget;

RequestTemplate提供了一个核心方法,用于解析数据,创建模板。
Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析

Feign 还有其他四大模板对象UriTemplateQueryTemplateHeaderTemplateBodyTemplate,都封装在了RequestTemplate中,分别对应了请求路径、查询参数、消息头、请求体模板。

源码分析

1. 进入工厂类

方法执行器执行时,首先就会创建请求模板:
Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析
调用的是RequestTemplate的内部工厂接口的实现类BuildTemplateByResolvingArgs(通过解析方法参数构建模板工厂类),该方法会根据方法元数据创建RequestTemplate

		// argv 执行方法的参数
        public RequestTemplate create(Object[] argv) {
        	// 使用方法元数据创建RequestTemplate 
            RequestTemplate mutable = RequestTemplate.from(this.metadata.template());
            // 设置目标对象
            mutable.feignTarget(this.target);
            // 方法元数据是否包含了URL 也就是参数中是否有URL 对象
            if (this.metadata.urlIndex() != null) {
            	// 有URL 则设置请求的目标(地址)
                int urlIndex = this.metadata.urlIndex();
                Util.checkArgument(argv[urlIndex] != null, "URI parameter %s was null", new Object[]{urlIndex});
                mutable.target(String.valueOf(argv[urlIndex]));
            }
			// 封装请求参数KV 键值对 ,@RequestParams 注解时会用到
            Map<String, Object> varBuilder = new LinkedHashMap();
            Iterator var4 = this.metadata.indexToName().entrySet().iterator();
			// 死循环
            while(true) {
                Entry entry;
                int i;
                Object value;
                do {
                	// 元数据中有indexToName
                    if (!var4.hasNext()) {
                    	// 调用RequestTemplate 的解析方法
                        RequestTemplate template = this.resolve(argv, mutable, varBuilder);
                        // 如果存在@SpringQueryMap (方法上参数上有@SpringQueryMap注解,可以传递对象参数)
                        // 将对象解析为Map, 在URL后面使用键值对拼接
                        if (this.metadata.queryMapIndex() != null) {
                        	// 获取@SpringQueryMap标识的参数对象
                            Object value = argv[this.metadata.queryMapIndex()];
                            // 转对象为MAP
                            Map<String, Object> queryMap = this.toQueryMap(value);
                            // 添加请求参数
                            template = this.addQueryMapQueryParameters(queryMap, template);
                        }
						// 存在@HeaderMap 就添加消息头
                        if (this.metadata.headerMapIndex() != null) {
                            template = this.addHeaderMapHeaders((Map)argv[this.metadata.headerMapIndex()], template);
                        }

                        return template;
                    }
					// 有indexToName
                    entry = (Entry)var4.next();
                    i = (Integer)entry.getKey(); // 参数序号
                    value = argv[(Integer)entry.getKey()]; // 参数值
                } while(value == null);
				// 没有indexToName 继续执行
                if (this.indexToExpander.containsKey(i)) {
                	// 参数值
                    value = this.expandElements((Expander)this.indexToExpander.get(i), value);
                }
                Iterator var8 = ((Collection)entry.getValue()).iterator();
				// 循环,将参数名,参数类型解析为Map ,全部解析完后,还是会回到 this.resolve(argv, mutable, varBuilder)
                while(var8.hasNext()) {
                    String name = (String)var8.next();
                    varBuilder.put(name, value);
                }
            }
        }

2. 请求参数为对象【POST】

首先看下参数类型为对象时,请求模板时如何创建的。

Feign 接口:

    @PostMapping("/order/post")
    public Order post(Order order);

服务提供者:

    @PostMapping("/post")
    public Order insertOrder(@RequestBody Order order) throws InterruptedException {
        return order;
    }

可以看到请求方式为Post 、参数为对象时,在解析元数据的时候,会直接将其解析为请求体,
Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析
在创建模板时,会直接调用BuildEncodedTemplateFromArgsresolve方法:
Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析
resolve中会使用编码器,将实体类转为请求体,最后调用RequestTemplate的解析方法处理参数。

        protected RequestTemplate resolve(Object[] argv, RequestTemplate mutable, Map<String, Object> variables) {
        	// 根据参数下标,获取到当前参数对象
            Object body = argv[this.metadata.bodyIndex()];
            // 检查参数,不能为null
            Util.checkArgument(body != null, "Body parameter %s was null", new Object[]{this.metadata.bodyIndex()});

            try {
            	// 调用SpringEncoder,将参数对象进行编码,并设置到RequestTemplate 中
                this.encoder.encode(body, this.metadata.bodyType(), mutable);
            } catch (EncodeException var6) {
                throw var6;
            } catch (RuntimeException var7) {
                throw new EncodeException(var7.getMessage(), var7);
            }
			// 调用RequestTemplate 的resolve
            return super.resolve(argv, mutable, variables);
        }
    }

最后,这种请求方式的参数就被转化为了byte 数组封装在RequestTemplate中了
Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析

3. 请求参数为对象【GET】

可以看到Get 请求时,也会将参数编码到请求体中。
Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析
这个时候底层HTTP 框架就会报错method GET must not have a request body.,GET 请求不支持请求体。
Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析
这个时候就需要用到@SpringQueryMap注解,spring cloud在2.1.x版本中提供了@SpringQueryMap注解,可以传递对象参数,框架自动解析。

我们修改下服务调用者及提供者:

    @GetMapping(value = "/order/post")
    public Object post(@SpringQueryMap Order order);
	
	@GetMapping("/post")
    public Order insertOrder(Order order) throws InterruptedException {
        return order;
    }

首先可以看到,解析方法元数据的时候,会解析@SpringQueryMap标识的参数,设置queryMapIndex下标为0。

Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析
在解析模板的时候,因为queryMapIndex下标为0,所以会进入到addQueryMapQueryParameters方法,添加请求参数,
Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析

addQueryMapQueryParameters方法中,会循环键值对,并将其转化为QueryTemplate,可以看到直接使用值的ToString方法进行值的获取。

        private RequestTemplate addQueryMapQueryParameters(Map<String, Object> queryMap, RequestTemplate mutable) {
            Entry currEntry;
            ArrayList values;// 存放值集合
            boolean encoded;
            // 循环Map,将键值对添加到queryTemplate中
            for(Iterator var3 = queryMap.entrySet().iterator(); var3.hasNext(); mutable.query(encoded ? (String)currEntry.getKey() : UriUtils.encode((String)currEntry.getKey()), values)) {
                currEntry = (Entry)var3.next(); // 键值对
                values = new ArrayList();
                encoded = this.metadata.queryMapEncoded(); // QueryMap是否已编码
                Object currValue = currEntry.getValue(); // 键对应的值
                if (currValue instanceof Iterable) {
                	// 如果值为集合,调用ToString
                    Iterator iter = ((Iterable)currValue).iterator();
                    while(iter.hasNext()) {
                        Object nextObject = iter.next();
                        values.add(nextObject == null ? null : (encoded ? nextObject.toString() : UriUtils.encode(nextObject.toString())));
                    }
                } else {
                	// 值为null  直接返回null, 不为null,则直接ToString
                    values.add(currValue == null ? null : (encoded ? currValue.toString() : UriUtils.encode(currValue.toString())));
                }
            }
            return mutable;
        }

最后,值不为null 的键值对,会被添加到QueryTemplate中,在执行请求时会将这些查询参数解析出来,这些参数会拼接在URL后面。
Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析

4. @RequestParam 注解【GET】

在Spring MVC中,@RequestParam将请求参数绑定到你控制器的方法参数上,那么使用了这个注解的模板是怎么生成的呢?

首先可以看到在元数据中,这些参数会被解析为MethodMetadataindexToExpanderindexToName属性。Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析
所以在解析模板的时候会进入到以下代码中Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析

最终将这些参数解析为键值对:
Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析
然后调用RequestTemplateresolve方法,在该方法中,会将请求参数键值对解析为&符号链接的字符串,并添加到最终发送请求的URI中。

	// variables 为参数键值对
    public RequestTemplate resolve(Map<String, ?> variables) {
    	// 请求路径
        StringBuilder uri = new StringBuilder();
        RequestTemplate resolved = from(this);
        // 没有URL 则会创建一个空路径
        if (this.uriTemplate == null) {
            this.uriTemplate = UriTemplate.create("", !this.decodeSlash, this.charset);
        }
		// 请求路径: /order/insert
        String expanded = this.uriTemplate.expand(variables);
        if (expanded != null) {
            uri.append(expanded); // 添加到变量uri 中
        }

        String headerValues;
        String queryString;
        if (!this.queries.isEmpty()) {
        	// 查询参数尾部空
            resolved.queries(Collections.emptyMap());
            StringBuilder query = new StringBuilder();
            Iterator queryTemplates = this.queries.values().iterator();
			// 拼接参数请求字符串 
            while(queryTemplates.hasNext()) {
                QueryTemplate queryTemplate = (QueryTemplate)queryTemplates.next();
                headerValues = queryTemplate.expand(variables);
                if (Util.isNotBlank(headerValues)) {
                    query.append(headerValues);
                    if (queryTemplates.hasNext()) {
                        query.append("&");
                    }
                }
            }
			// 创建请求后面拼接的参数字符串=》accountId=11&commodityCode=sss&count=22&money=33
            queryString = query.toString();
            if (!queryString.isEmpty()) {
                Matcher queryMatcher = QUERY_STRING_PATTERN.matcher(uri);
                if (queryMatcher.find()) {
                    uri.append("&");
                } else {
                    uri.append("?");
                }

                uri.append(queryString);
            }
        }
		// 最终的URI=》 /order/insert?accountId=11&commodityCode=sss&count=22&money=33
        resolved.uri(uri.toString());
        if (!this.headers.isEmpty()) {
            resolved.headers(Collections.emptyMap());
            Iterator var9 = this.headers.values().iterator();

            while(var9.hasNext()) {
                HeaderTemplate headerTemplate = (HeaderTemplate)var9.next();
                queryString = headerTemplate.expand(variables);
                if (!queryString.isEmpty()) {
                    headerValues = queryString.substring(queryString.indexOf(" ") + 1);
                    if (!headerValues.isEmpty()) {
                        resolved.header(headerTemplate.getName(), Literal.create(headerValues));
                    }
                }
            }
        }
		// 处理Body
        if (this.bodyTemplate != null) {
            resolved.body(this.bodyTemplate.expand(variables));
        }

        resolved.resolved = true;
        return resolved;
    }

最终这个请求模板内容如下:

Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析

5. 转换为Request

请求模板创建成功以后,会将其转换为Request对象,这个步骤是在方法处理器中进行的。
Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析
转换的时候,会调用拦截器:
Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析

Request对象的创建,还是调用的RequestTemplaterequest()方法:

    public Request request() {
        if (!this.resolved) {
            throw new IllegalStateException("template has not been resolved.");
        } else {
            return Request.create(this.method, this.url(), this.headers(), this.body, this);
        }
    }

创建过程中,会将请求用的到路径、请求方式、模板等,封装在Request对象中。
Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析
最终,这个请求对象经过负载均衡器,获取到请求真实IP,传递给底层HTTP 框架,再由其转为自身的Request 对象,执行请求,整个流程就结束了。
Spring Cloud Open Feign系列【18】RequestTemplate(请求模板)源码分析

相关文章

暂无评论

暂无评论...