Feign调用中文参数被encode编译
原因
在实现一个feign调用时使用了Post请求,并且拼接url参数,name传值为中文时被encode转译,且最终接取数据之前未被decode转译回,问题探索:
feign:
1
2
3
4
5
|
@FeignClient (name = "service-test" ) public interface TestServiceApi { @PostMapping ( "/test/abc" ) public String getTestNo( @RequestParam ( "code" ) String code, @RequestParam ( "name" ) String name); } |
controller:
1
2
3
4
5
6
7
8
9
|
@RequestMapping ( "/test" ) public interface TestController { @Autowired private TestService testService; @PostMapping ( "/abc" ) public String getTestNo( @RequestParam ( "code" ) String code, @RequestParam ( "name" ) String name) { return testService.query(code, name); } } |
在controller中接到的中文参数被URIEncode转译了
测试 被转译成:%E6%B5%8B%E8%AF%95
找到feign的入口类ReflectiveFeign中拼装RequestTemplate的方法:
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
|
@Override public RequestTemplate create(Object[] argv) { RequestTemplate mutable = new RequestTemplate(metadata.template()); 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])); } Map<String, Object> varBuilder = new LinkedHashMap<String, Object>(); for (Entry<Integer, Collection<String>> 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); } } } // 组装template的方法 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(argv, template); } if (metadata.headerMapIndex() != null ) { template = addHeaderMapHeaders(argv, template); } return template; } |
在RequestTemplate类中如果是拼接在url后的param那么会被使用encodeValueIfNotEncoded都encode转译,但是不会走decode的方法
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
|
/** * Resolves any template parameters in the requests path, query, or headers against the supplied * unencoded arguments. <br> <br><br><b>relationship to JAXRS 2.0</b><br> <br> 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<String, ?> unencoded, Map<String, Boolean> alreadyEncoded) { replaceQueryValues(unencoded, alreadyEncoded); Map<String, String> encoded = new LinkedHashMap<String, String>(); for (Entry<String, ?> 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<String, Collection<String>> resolvedHeaders = new LinkedHashMap<String, Collection<String>>(); for (String field : headers.keySet()) { Collection<String> resolvedValues = new ArrayList<String>(); 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 ; } |
如果传入的值在requestBody中,则不会被encode转译
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
|
@Override public void encode(Object requestBody, Type bodyType, RequestTemplate request) throws EncodeException { // template.body(conversionService.convert(object, String.class)); if (requestBody != null ) { Class<?> requestType = requestBody.getClass(); Collection<String> contentTypes = request.headers().get( "Content-Type" ); MediaType requestContentType = null ; if (contentTypes != null && !contentTypes.isEmpty()) { String type = contentTypes.iterator().next(); requestContentType = MediaType.valueOf(type); } for (HttpMessageConverter<?> messageConverter : this .messageConverters .getObject().getConverters()) { if (messageConverter.canWrite(requestType, requestContentType)) { if (log.isDebugEnabled()) { if (requestContentType != null ) { log.debug( "Writing [" + requestBody + "] as \"" + requestContentType + "\" using [" + messageConverter + "]" ); } else { log.debug( "Writing [" + requestBody + "] using [" + messageConverter + "]" ); } } FeignOutputMessage outputMessage = new FeignOutputMessage(request); try { @SuppressWarnings ( "unchecked" ) HttpMessageConverter<Object> copy = (HttpMessageConverter<Object>) messageConverter; copy.write(requestBody, requestContentType, outputMessage); } catch (IOException ex) { throw new EncodeException( "Error converting request body" , ex); } // clear headers request.headers( null ); // converters can modify headers, so update the request // with the modified headers request.headers(getHeaders(outputMessage.getHeaders())); // do not use charset for binary data if (messageConverter instanceof ByteArrayHttpMessageConverter) { request.body(outputMessage.getOutputStream().toByteArray(), null ); } else { request.body(outputMessage.getOutputStream().toByteArray(), Charset.forName( "UTF-8" )); } return ; } } String message = "Could not write request: no suitable HttpMessageConverter " + "found for request type [" + requestType.getName() + "]" ; if (requestContentType != null ) { message += " and content type [" + requestContentType + "]" ; } throw new EncodeException(message); } } |
综合上述的调试,如果在Post中拼接参数那么会被encode转译,且不会被decode转译,如果使用body传参,那么不会出现转译问题,如果必须使用拼接传参,那么可以使用方法
1. @RequestLine的注解自定义参数的格式,具体参考该注解的使用方式。
2.在Feign的RequestInterceptor将传递的值decode的扩展方法。
记录今天遇到的feign多参数问题
1.Post方式
错误写法示例如下:
1
|
public int save( @RequestBody final User u, @RequestBody final School s); |
错误原因:
fegin中可以有多个@RequestParam,但只能有不超过一个@RequestBody,@RequestBody用来修饰对象,但是既有@RequestBody也有@RequestParam,
那么参数就要放在请求的Url中,@RequestBody修饰的就要放在提交对象中。
注意!!! 用来处理@RequestBody Content-Type 为 application/json,application/xml编码的内容
正确写法示例如下:
1
|
public int save( @RequestBody final Person p, @RequestParam ( "userId" ) String userId, @RequestParam ( "userTel" ) String userTel); |
2.Get方式
错误写法示例如下:
1
2
|
@RequestMapping (value= "/test" , method=RequestMethod.GET) Model test( final String name, final int age); |
错误原因:
异常原因:当使用Feign时,如果发送的是get请求,那么需要在请求参数前加上@RequestParam注解修饰,Controller里面可以不加该注解修饰,@RequestParam可以修饰多个,@RequestParam是用来修饰参数,不能用来修饰整个对象。
注意:@RequestParam Content-Type 为 application/x-www-form-urlencoded 而这种是默认的
正确写法示例如下:
1
2
3
|
@GetMapping ( "/getSchoolDetail" ) public ResultMap getSchoolDetail( @RequestParam ( "kSchoolId" ) LongkSchoolId, @RequestParam ( "kSchoolYearId" ) Long kSchoolYearId); |
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/baidu_34932610/article/details/107092448