servicecomb-java-chassis icon indicating copy to clipboard operation
servicecomb-java-chassis copied to clipboard

ServiceComb-Java-Chassis 使用CSERestTemplate访问出现类型转化失败

Open RayTigerZ opened this issue 3 years ago • 19 comments

String jsonResult = restTemplate.getForObject(url, String.class) 出现异常:com.alibaba.fastjson.JSONObject cannot be cast to java.lang.String

java-chassis版本2.6.0

RayTigerZ avatar Dec 22 '22 07:12 RayTigerZ

发现在依赖的sdk包中存在ProduceProcessor的SPI配置 FastJsonProduceProcessor StringProduceProcessor org.apache.servicecomb.common.rest.codec.produce.ProduceJsonProcessor org.apache.servicecomb.common.rest.codec.produce.ProduceTextPlainProcessor 前两个可以进行配置关闭,后两个还是会起作用,继而出现异常 java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.alibaba.fastjson.JSONObject

RayTigerZ avatar Dec 23 '22 01:12 RayTigerZ

restTemplate.getForObject(url, JSONObject.class)

RayTigerZ avatar Dec 23 '22 02:12 RayTigerZ

跟踪代码发现,CSERestTemplate中的类型参数和原生RestTemplate中的含义是有很大差异的,因为CSERestTemplate并不支持HttpMessageConverter(它能将响应数据转化为客户端真正想要的数据类型),CSERestTemplate的类型实际则依赖服务端,服务端响应数据结果定义不好、不清晰,客户端跟着摆烂。尤其在这种平台+微服务APP的情况下,上下游存在很大的差别,在开发的方方面面上

RayTigerZ avatar Dec 24 '22 07:12 RayTigerZ

CSERestTemplate 只是实现了 RestTemplate 的 get/post等接口(早期选型没做好,对外提供 RestOperations 其实更好), java chassis 与 spring mvc的区别参考: https://github.com/apache/servicecomb-java-chassis/issues/2766 。

liubao68 avatar Dec 24 '22 07:12 liubao68

平台服务端提供的接口 ,从JavaType responseType = invocation.findResponseType(responseEx.getStatus());获取到responseType大部分都是Object.class,ProduceJsonProcessor转化之后就是个Map;客户端就得自己去完成转换,大量的这类代码。如果平台主动去改服务端,几十个客户端也得跟着动,就因为servicecomb这种类型的强相关

RayTigerZ avatar Dec 24 '22 08:12 RayTigerZ

restTemplate.getForObject(url, Map.class) 或者 restTemplate.getForObject(url, Object.class) 可以?

liubao68 avatar Dec 24 '22 08:12 liubao68

restTemplate.getForObject(url, Map.class) 或者 restTemplate.getForObject(url, Object.class) 可以?

可以的,但是这种写法真的是缺少了RestTemplate中的精华:类型转换,增加了业务层的复杂

RayTigerZ avatar Dec 24 '22 08:12 RayTigerZ

restTemplate.getForObject(url, Map.class) 或者 restTemplate.getForObject(url, Object.class) 可以?

可以的,但是这种写法真的是缺少了RestTemplate中的精华:类型转换,增加了业务层的复杂 有点先入为主,但是这种东西,不得不承认,用起来很舒服

RayTigerZ avatar Dec 24 '22 08:12 RayTigerZ

发现在依赖的sdk包中存在ProduceProcessor的SPI配置 FastJsonProduceProcessor StringProduceProcessor org.apache.servicecomb.common.rest.codec.produce.ProduceJsonProcessor org.apache.servicecomb.common.rest.codec.produce.ProduceTextPlainProcessor 前两个可以进行配置关闭,后两个还是会起作用,继而出现异常 java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.alibaba.fastjson.JSONObject

估计你的咨询下你们项目这块的背景。FastJsonProduceProcessor 应该就是处理你说的场景的。 java chassis默认情况下并不支持JsonObject,也不建议这么使用。 详细说明参考: https://servicecomb.apache.org/references/java-chassis/zh_CN/build-provider/interface-constraints.html

liubao68 avatar Dec 24 '22 08:12 liubao68

restTemplate.getForObject(url, Map.class) 或者 restTemplate.getForObject(url, Object.class) 可以?

可以的,但是这种写法真的是缺少了RestTemplate中的精华:类型转换,增加了业务层的复杂 有点先入为主,但是这种东西,不得不承认,用起来很舒服

我理解 JsonObject 和 Map几乎是等价的,所以提了这个。 你期望 restTemplate.getForObject(url, MyCustomBean.class) 这种?为啥都会需要 JsonObject 了? 这个用起来不存在舒服的说法呀。

liubao68 avatar Dec 24 '22 08:12 liubao68

发现在依赖的sdk包中存在ProduceProcessor的SPI配置 FastJsonProduceProcessor StringProduceProcessor org.apache.servicecomb.common.rest.codec.produce.ProduceJsonProcessor org.apache.servicecomb.common.rest.codec.produce.ProduceTextPlainProcessor 前两个可以进行配置关闭,后两个还是会起作用,继而出现异常 java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.alibaba.fastjson.JSONObject

估计你的咨询下你们项目这块的背景。FastJsonProduceProcessor 应该就是处理你说的场景的。 java chassis默认情况下并不支持JsonObject,也不建议这么使用。 详细说明参考: https://servicecomb.apache.org/references/java-chassis/zh_CN/build-provider/interface-constraints.html

跟我们项目没啥关系,我们直接被平台挖的坑绊倒了。我跟完代码后,也慢慢理解为啥他为啥怎么做了:从JavaType responseType = invocation.findResponseType(responseEx.getStatus());获取到responseType大部分都是Object.class,ProduceJsonProcessor转化之后就是个Map,自已搞个FastJsonProduceProcessor 转换成JSONObject(提供了get().asXXX()),好像是比Map取值强转好多l

RayTigerZ avatar Dec 24 '22 08:12 RayTigerZ

restTemplate.getForObject(url, Map.class) 或者 restTemplate.getForObject(url, Object.class) 可以?

可以的,但是这种写法真的是缺少了RestTemplate中的精华:类型转换,增加了业务层的复杂 有点先入为主,但是这种东西,不得不承认,用起来很舒服

我理解 JsonObject 和 Map几乎是等价的,所以提了这个。 你期望 restTemplate.getForObject(url, MyCustomBean.class) 这种?为啥都会需要 JsonObject 了? 这个用起来不存在舒服的说法呀。

从JavaType responseType = invocation.findResponseType(responseEx.getStatus());获取到responseType大部分都是Object.class,能用 MyCustomBean.class接收吗

RayTigerZ avatar Dec 24 '22 08:12 RayTigerZ

restTemplate.getForObject(url, Map.class) 或者 restTemplate.getForObject(url, Object.class) 可以?

可以的,但是这种写法真的是缺少了RestTemplate中的精华:类型转换,增加了业务层的复杂 有点先入为主,但是这种东西,不得不承认,用起来很舒服

我理解 JsonObject 和 Map几乎是等价的,所以提了这个。 你期望 restTemplate.getForObject(url, MyCustomBean.class) 这种?为啥都会需要 JsonObject 了? 这个用起来不存在舒服的说法呀。

从JavaType responseType = invocation.findResponseType(responseEx.getStatus());获取到responseType大部分都是Object.class,能用 MyCustomBean.class接收吗

可以。这里取到的类型根据 restTemplate.getForObject(url, MyCustomBean.class) 决定的。

liubao68 avatar Dec 24 '22 08:12 liubao68

restTemplate.getForObject(url, Map.class) 或者 restTemplate.getForObject(url, Object.class) 可以?

可以的,但是这种写法真的是缺少了RestTemplate中的精华:类型转换,增加了业务层的复杂 有点先入为主,但是这种东西,不得不承认,用起来很舒服

我理解 JsonObject 和 Map几乎是等价的,所以提了这个。 你期望 restTemplate.getForObject(url, MyCustomBean.class) 这种?为啥都会需要 JsonObject 了? 这个用起来不存在舒服的说法呀。

从JavaType responseType = invocation.findResponseType(responseEx.getStatus());获取到responseType大部分都是Object.class,能用 MyCustomBean.class接收吗

可以。这里取到的类型根据 restTemplate.getForObject(url, MyCustomBean.class) 决定的。

只保留 org.apache.servicecomb.common.rest.codec.produce.ProduceJsonProcessor org.apache.servicecomb.common.rest.codec.produce.ProduceTextPlainProcessor 出现异常 java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.alibaba.fastjson.JSONObject

RayTigerZ avatar Dec 24 '22 08:12 RayTigerZ

只保留 org.apache.servicecomb.common.rest.codec.produce.ProduceJsonProcessor org.apache.servicecomb.common.rest.codec.produce.ProduceTextPlainProcessor的情况下还试过: restTemplate.getForObject(url, String.class) 出现异常 java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to String

RayTigerZ avatar Dec 24 '22 08:12 RayTigerZ

restTemplate.getForObject(url, Map.class) 或者 restTemplate.getForObject(url, Object.class) 可以?

可以的,但是这种写法真的是缺少了RestTemplate中的精华:类型转换,增加了业务层的复杂 有点先入为主,但是这种东西,不得不承认,用起来很舒服

我理解 JsonObject 和 Map几乎是等价的,所以提了这个。 你期望 restTemplate.getForObject(url, MyCustomBean.class) 这种?为啥都会需要 JsonObject 了? 这个用起来不存在舒服的说法呀。

从JavaType responseType = invocation.findResponseType(responseEx.getStatus());获取到responseType大部分都是Object.class,能用 MyCustomBean.class接收吗

可以。这里取到的类型根据 restTemplate.getForObject(url, MyCustomBean.class) 决定的。

我跟代码的时观察到的是:MyCustomBean.class起到的作用只是避免类型的强行转换,实际上类型是由 JavaType responseType = invocation.findResponseType(responseEx.getStatus());决定的

RayTigerZ avatar Dec 24 '22 08:12 RayTigerZ

DefaultHttpClientFilter这段代码完成类型的确认和转化

JavaType responseType = invocation.findResponseType(responseEx.getStatus());
result = produceProcessor.decodeResponse(responseEx.getBodyBuffer(), responseType);
Response response = Response.create(responseEx.getStatusType(), result);

CseClientHttpRequest没有进行类型的转化,包装一下就完了

private CseClientHttpResponse invoke(Map<String, Object> swaggerArguments) {
    Invocation invocation = prepareInvocation(swaggerArguments);
    Response response = doInvoke(invocation);

    if (response.isSucceed()) {
      return new CseClientHttpResponse(response);
    }

    throw ExceptionFactory.convertConsumerException(response.getResult());
  }

CseHttpMessageConverterExtractor拆取时也没有类型转换

public class CseHttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
  @Override
  @SuppressWarnings("unchecked")
  public T extractData(ClientHttpResponse response) throws IOException {
    return (T) ((CseClientHttpResponse) response).getResult();
  }
}

CSERestTemplate继承RestTemplate的方法,也没有进行类型转换

@Nullable
	protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
			@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {

		Assert.notNull(url, "URI is required");
		Assert.notNull(method, "HttpMethod is required");
		ClientHttpResponse response = null;
		try {
			ClientHttpRequest request = createRequest(url, method);
			if (requestCallback != null) {
				requestCallback.doWithRequest(request);
			}
			response = request.execute();
			handleResponse(url, method, response);
			return (responseExtractor != null ? responseExtractor.extractData(response) : null);
		}
		catch (IOException ex) {
			String resource = url.toString();
			String query = url.getRawQuery();
			resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
			throw new ResourceAccessException("I/O error on " + method.name() +
					" request for \"" + resource + "\": " + ex.getMessage(), ex);
		}
		finally {
			if (response != null) {
				response.close();
			}
		}
	}

RayTigerZ avatar Dec 24 '22 08:12 RayTigerZ

restTemplate.postForObject(url, params, JSONObject.class) 没有问题

restTemplate.getForObject(url, JSONObject.class)出现 java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.alibaba.fastjson.JSONObject

RayTigerZ avatar Dec 24 '22 11:12 RayTigerZ

你能从使用者的角度提供下用法吗? 包括服务端的接口定义、客户端的请求代码示例。 我感觉前面提供的信息,有些是不正确的用法。 对着具体的使用方式, 可能更好的讨论。

比如: String result = restTemplate.getForObject(url, String.class) 这种客户端用法, 适用于服务端的接口定义中返回值类型也是String。 如果不是, 那么则无法工作。

liubao68 avatar Dec 26 '22 03:12 liubao68