1
0
mirror of https://gitee.com/mirrors/Spring-Cloud-Alibaba.git synced 2021-06-26 13:25:11 +08:00

Polish spring-cloud-incubator/spring-cloud-alibaba#348 : @DubboTransported supports RestTemplate (part 2)

This commit is contained in:
mercyblitz 2019-02-19 17:59:46 +08:00
parent 0b31eccbf0
commit 4b1c3c6454
10 changed files with 383 additions and 58 deletions

View File

@ -16,6 +16,7 @@
*/
package org.springframework.cloud.alibaba.dubbo.autoconfigure;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.Autowired;
@ -50,7 +51,7 @@ import java.util.Map;
@Configuration
@ConditionalOnClass(RestTemplate.class)
@AutoConfigureAfter(LoadBalancerAutoConfiguration.class)
public class DubboLoadBalancedRestTemplateAutoConfiguration {
public class DubboLoadBalancedRestTemplateAutoConfiguration implements BeanClassLoaderAware {
private static final Class<DubboTransported> DUBBO_TRANSPORTED_CLASS = DubboTransported.class;
@ -69,6 +70,8 @@ public class DubboLoadBalancedRestTemplateAutoConfiguration {
@Autowired(required = false)
private Map<String, RestTemplate> restTemplates = Collections.emptyMap();
private ClassLoader classLoader;
/**
* Adapt the {@link RestTemplate} beans that are annotated {@link LoadBalanced @LoadBalanced} and
* {@link LoadBalanced @LoadBalanced} when Spring Boot application started
@ -117,10 +120,14 @@ public class DubboLoadBalancedRestTemplateAutoConfiguration {
if (index > -1) {
interceptors.set(index, new DubboAdapterLoadBalancerInterceptor(repository, loadBalancerInterceptor,
restTemplate.getMessageConverters()));
restTemplate.getMessageConverters(),classLoader));
}
restTemplate.setInterceptors(interceptors);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
}

View File

@ -17,6 +17,7 @@
package org.springframework.cloud.alibaba.dubbo.client.loadbalancer;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.rpc.service.GenericException;
import com.alibaba.dubbo.rpc.service.GenericService;
import org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata;
@ -53,12 +54,16 @@ public class DubboAdapterLoadBalancerInterceptor implements ClientHttpRequestInt
private final List<HttpMessageConverter<?>> messageConverters;
private final DubboClientHttpResponseFactory clientHttpResponseFactory;
public DubboAdapterLoadBalancerInterceptor(DubboServiceMetadataRepository dubboServiceMetadataRepository,
LoadBalancerInterceptor loadBalancerInterceptor,
List<HttpMessageConverter<?>> messageConverters) {
List<HttpMessageConverter<?>> messageConverters,
ClassLoader classLoader) {
this.repository = dubboServiceMetadataRepository;
this.loadBalancerInterceptor = loadBalancerInterceptor;
this.messageConverters = messageConverters;
this.clientHttpResponseFactory = new DubboClientHttpResponseFactory(messageConverters, classLoader);
}
@Override
@ -72,30 +77,44 @@ public class DubboAdapterLoadBalancerInterceptor implements ClientHttpRequestInt
repository.initialize(serviceName);
RequestMetadata requestMetadata = buildRequestMetadata(request, uriComponents);
RequestMetadata clientMetadata = buildRequestMetadata(request, uriComponents);
ReferenceBean<GenericService> referenceBean =
repository.getReferenceBean(serviceName, requestMetadata);
RestMethodMetadata restMethodMetadata = repository.getRestMethodMetadata(serviceName, clientMetadata);
RestMethodMetadata restMethodMetadata = repository.getRestMethodMetadata(serviceName, requestMetadata);
ReferenceBean<GenericService> referenceBean = repository.getReferenceBean(serviceName, clientMetadata);
if (referenceBean == null || restMethodMetadata == null) {
return loadBalancerInterceptor.intercept(request, body, execution);
}
Object result = null;
GenericException exception = null;
try {
result = invokeService(restMethodMetadata, referenceBean, clientMetadata);
} catch (GenericException e) {
exception = e;
}
return clientHttpResponseFactory.build(result, exception, clientMetadata, restMethodMetadata);
}
private Object invokeService(RestMethodMetadata restMethodMetadata, ReferenceBean<GenericService> referenceBean,
RequestMetadata clientMetadata) throws GenericException {
MethodMetadata methodMetadata = restMethodMetadata.getMethod();
String methodName = methodMetadata.getName();
String[] parameterTypes = parameterResolver.resolveParameterTypes(methodMetadata);
Object[] parameters = parameterResolver.resolveParameters(restMethodMetadata, request, uriComponents);
Object[] parameters = parameterResolver.resolveParameters(restMethodMetadata, clientMetadata);
GenericService genericService = referenceBean.get();
Object result = genericService.$invoke(methodName, parameterTypes, parameters);
return null;
return result;
}
public static RequestMetadata buildRequestMetadata(HttpRequest request, UriComponents uriComponents) {

View File

@ -33,21 +33,19 @@ import java.io.InputStream;
*/
class DubboClientHttpResponse implements ClientHttpResponse {
private final Object result;
private final GenericException exception;
private final HttpStatus httpStatus;
private final String statusText;
private final HttpHeaders httpHeaders = new HttpHeaders();
public DubboClientHttpResponse(Object result, GenericException exception) {
this.result = result;
this.exception = exception;
private final DubboHttpOutputMessage httpOutputMessage;
public DubboClientHttpResponse(DubboHttpOutputMessage httpOutputMessage, GenericException exception) {
this.httpStatus = exception != null ? HttpStatus.INTERNAL_SERVER_ERROR : HttpStatus.OK;
this.statusText = exception != null ? exception.getExceptionMessage() : httpStatus.getReasonPhrase();
this.httpOutputMessage = httpOutputMessage;
this.httpHeaders.putAll(httpOutputMessage.getHeaders());
}
@Override
@ -67,12 +65,11 @@ class DubboClientHttpResponse implements ClientHttpResponse {
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return null;
return httpOutputMessage.getBody().getInputStream();
}
@Override

View File

@ -0,0 +1,64 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.springframework.cloud.alibaba.dubbo.client.loadbalancer;
import com.alibaba.dubbo.rpc.service.GenericException;
import org.springframework.cloud.alibaba.dubbo.http.converter.HttpMessageConverterHolder;
import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.resolver.HttpMessageConverterResolver;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.HttpMessageConverter;
import java.io.IOException;
import java.util.List;
/**
* Dubbo {@link ClientHttpResponse} Factory
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
class DubboClientHttpResponseFactory {
private final HttpMessageConverterResolver httpMessageConverterResolver;
public DubboClientHttpResponseFactory(List<HttpMessageConverter<?>> messageConverters, ClassLoader classLoader) {
this.httpMessageConverterResolver = new HttpMessageConverterResolver(messageConverters, classLoader);
}
public ClientHttpResponse build(Object result, GenericException exception,
RequestMetadata requestMetadata, RestMethodMetadata restMethodMetadata) {
DubboHttpOutputMessage httpOutputMessage = new DubboHttpOutputMessage();
HttpMessageConverterHolder httpMessageConverterHolder = httpMessageConverterResolver.resolve(requestMetadata, restMethodMetadata);
if (httpMessageConverterHolder != null) {
MediaType mediaType = httpMessageConverterHolder.getMediaType();
HttpMessageConverter converter = httpMessageConverterHolder.getConverter();
try {
converter.write(result, mediaType, httpOutputMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
return new DubboClientHttpResponse(httpOutputMessage, exception);
}
}

View File

@ -21,7 +21,6 @@ import org.springframework.http.HttpOutputMessage;
import org.springframework.util.FastByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Dubbo {@link HttpOutputMessage} implementation
@ -31,7 +30,7 @@ import java.io.OutputStream;
class DubboHttpOutputMessage implements HttpOutputMessage {
@Override
public OutputStream getBody() throws IOException {
public FastByteArrayOutputStream getBody() throws IOException {
return new FastByteArrayOutputStream();
}

View File

@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.springframework.cloud.alibaba.dubbo.http.converter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
/**
* {@link HttpMessageConverter} Holder with {@link MediaType}.
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class HttpMessageConverterHolder {
private final MediaType mediaType;
private final HttpMessageConverter<?> converter;
public HttpMessageConverterHolder(MediaType mediaType, HttpMessageConverter<?> converter) {
this.mediaType = mediaType;
this.converter = converter;
}
public MediaType getMediaType() {
return mediaType;
}
public HttpMessageConverter<?> getConverter() {
return converter;
}
}

View File

@ -21,12 +21,14 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import feign.RequestTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@ -54,9 +56,9 @@ public class RequestMetadata {
@JsonProperty("headers")
private HttpHeaders headers = new HttpHeaders();
private List<String> consumes = new LinkedList<>();
private Set<String> consumes = new LinkedHashSet<>();
private List<String> produces = new LinkedList<>();
private Set<String> produces = new LinkedHashSet<>();
public RequestMetadata() {
}
@ -100,19 +102,19 @@ public class RequestMetadata {
headers(headers);
}
public List<String> getConsumes() {
public Set<String> getConsumes() {
return consumes;
}
public void setConsumes(List<String> consumes) {
public void setConsumes(Set<String> consumes) {
this.consumes = consumes;
}
public List<String> getProduces() {
public Set<String> getProduces() {
return produces;
}
public void setProduces(List<String> produces) {
public void setProduces(Set<String> produces) {
this.produces = produces;
}
@ -137,6 +139,14 @@ public class RequestMetadata {
return toMediaTypes(produces);
}
public String getParameter(String name) {
return this.params.getFirst(name);
}
public String getHeader(String name) {
return this.headers.getFirst(name);
}
public RequestMetadata addParam(String name, String value) {
add(name, value, this.params);
return this;
@ -153,13 +163,15 @@ public class RequestMetadata {
}
private <T extends Collection<String>> RequestMetadata headers(Map<String, T> headers) {
HttpHeaders httpHeaders = new HttpHeaders();
// Add all headers
addAll(headers, httpHeaders);
// Handles "Content-Type" and "Accept" headers if present
mediaTypes(httpHeaders, HttpHeaders.CONTENT_TYPE, this.consumes);
mediaTypes(httpHeaders, HttpHeaders.ACCEPT, this.produces);
this.headers.putAll(httpHeaders);
if (!CollectionUtils.isEmpty(headers)) {
HttpHeaders httpHeaders = new HttpHeaders();
// Add all headers
addAll(headers, httpHeaders);
// Handles "Content-Type" and "Accept" headers if present
mediaTypes(httpHeaders, HttpHeaders.CONTENT_TYPE, this.consumes);
mediaTypes(httpHeaders, HttpHeaders.ACCEPT, this.produces);
this.headers.putAll(httpHeaders);
}
return this;
}
@ -199,7 +211,7 @@ public class RequestMetadata {
}
}
private static void mediaTypes(HttpHeaders httpHeaders, String headerName, List<String> destination) {
private static void mediaTypes(HttpHeaders httpHeaders, String headerName, Collection<String> destination) {
List<String> value = httpHeaders.get(headerName);
List<MediaType> mediaTypes = parseMediaTypes(value);
destination.addAll(toMediaTypeValues(mediaTypes));
@ -213,11 +225,11 @@ public class RequestMetadata {
return list;
}
private static List<MediaType> toMediaTypes(List<String> mediaTypeValues) {
private static List<MediaType> toMediaTypes(Collection<String> mediaTypeValues) {
if (mediaTypeValues.isEmpty()) {
return Collections.singletonList(MediaType.ALL);
}
return parseMediaTypes(mediaTypeValues);
return parseMediaTypes(new LinkedList<>(mediaTypeValues));
}
@Override

View File

@ -0,0 +1,186 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.springframework.cloud.alibaba.dubbo.metadata.resolver;
import org.springframework.cloud.alibaba.dubbo.http.converter.HttpMessageConverterHolder;
import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* {@link HttpMessageConverter} Resolver
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class HttpMessageConverterResolver {
private static final MediaType MEDIA_TYPE_APPLICATION = new MediaType("application");
private final List<HttpMessageConverter<?>> messageConverters;
private final List<MediaType> allSupportedMediaTypes;
private final ClassLoader classLoader;
public HttpMessageConverterResolver(List<HttpMessageConverter<?>> messageConverters, ClassLoader classLoader) {
this.messageConverters = messageConverters;
this.allSupportedMediaTypes = getAllSupportedMediaTypes(messageConverters);
this.classLoader = classLoader;
}
/**
* Resolve the most match {@link HttpMessageConverter} from {@link RequestMetadata}
*
* @param requestMetadata {@link RequestMetadata}
* @param restMethodMetadata {@link RestMethodMetadata}
* @return
*/
public HttpMessageConverterHolder resolve(RequestMetadata requestMetadata, RestMethodMetadata restMethodMetadata) {
HttpMessageConverterHolder httpMessageConverterHolder = null;
Class<?> returnValueClass = resolveReturnValueClass(restMethodMetadata);
/**
* @see AbstractMessageConverterMethodProcessor#writeWithMessageConverters(Object, MethodParameter, ServletServerHttpRequest, ServletServerHttpResponse)
*/
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(requestMetadata);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(restMethodMetadata, returnValueClass);
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (compatibleMediaTypes.isEmpty()) {
return httpMessageConverterHolder;
}
List<MediaType> mediaTypes = new ArrayList<>(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(mediaTypes);
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypes) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
} else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
httpMessageConverterHolder = new HttpMessageConverterHolder(selectedMediaType, messageConverter);
break;
}
}
}
return httpMessageConverterHolder;
}
private Class<?> resolveReturnValueClass(RestMethodMetadata restMethodMetadata) {
String returnClassName = restMethodMetadata.getMethod().getReturnType();
return ClassUtils.resolveClassName(returnClassName, classLoader);
}
/**
* Resolve the {@link MediaType media-types}
*
* @param requestMetadata {@link RequestMetadata} from client side
* @return non-null {@link List}
*/
private List<MediaType> getAcceptableMediaTypes(RequestMetadata requestMetadata) {
return requestMetadata.getProduceMediaTypes();
}
/**
* Returns
* the media types that can be produced: <ul> <li>The producible media types specified in the request mappings, or
* <li>Media types of configured converters that can write the specific return value, or <li>{@link MediaType#ALL}
* </ul>
*
* @param restMethodMetadata {@link RestMethodMetadata} from server side
* @param returnValueClass the class of return value
* @return non-null {@link List}
*/
private List<MediaType> getProducibleMediaTypes(RestMethodMetadata restMethodMetadata, Class<?> returnValueClass) {
RequestMetadata serverRequestMetadata = restMethodMetadata.getRequest();
List<MediaType> mediaTypes = serverRequestMetadata.getProduceMediaTypes();
if (!CollectionUtils.isEmpty(mediaTypes)) { // Empty
return mediaTypes;
} else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<>();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter.canWrite(returnValueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
} else {
return Collections.singletonList(MediaType.ALL);
}
}
/**
* Return the media types
* supported by all provided message converters sorted by specificity via {@link
* MediaType#sortBySpecificity(List)}.
*
* @param messageConverters
* @return
*/
private List<MediaType> getAllSupportedMediaTypes(List<HttpMessageConverter<?>> messageConverters) {
Set<MediaType> allSupportedMediaTypes = new LinkedHashSet<MediaType>();
for (HttpMessageConverter<?> messageConverter : messageConverters) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
}
List<MediaType> result = new ArrayList<MediaType>(allSupportedMediaTypes);
MediaType.sortBySpecificity(result);
return Collections.unmodifiableList(result);
}
/**
* Return the more specific of the acceptable and the producible media types
* with the q-value of the former.
*/
private MediaType getMostSpecificMediaType(MediaType acceptType, MediaType produceType) {
MediaType produceTypeToUse = produceType.copyQualityValue(acceptType);
return (MediaType.SPECIFICITY_COMPARATOR.compare(acceptType, produceTypeToUse) <= 0 ? acceptType : produceTypeToUse);
}
}

View File

@ -20,9 +20,7 @@ import org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.MethodParameterMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata;
import org.springframework.http.HttpRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.web.util.UriComponents;
import java.util.Collection;
import java.util.Iterator;
@ -37,8 +35,7 @@ import java.util.Set;
*/
public class ParameterResolver {
public Object[] resolveParameters(RestMethodMetadata restMethodMetadata, HttpRequest request, UriComponents uriComponents) {
public Object[] resolveParameters(RestMethodMetadata restMethodMetadata, RequestMetadata clientMetadata) {
MethodMetadata methodMetadata = restMethodMetadata.getMethod();
@ -56,26 +53,24 @@ public class ParameterResolver {
String name = getName(indexToName, index);
parameters[index] = getValue(requestMetadata, request, uriComponents, name);
parameters[index] = getValue(requestMetadata, clientMetadata, name);
}
return parameters;
}
private String getValue(RequestMetadata requestMetadata, HttpRequest request, UriComponents uriComponents, String name) {
private String getValue(RequestMetadata serverMetadata, RequestMetadata clientMetadata, String name) {
String value = null;
Set<String> paramNames = requestMetadata.getParamNames();
Set<String> headerNames = requestMetadata.getHeaderNames();
Set<String> paramNames = serverMetadata.getParamNames();
Set<String> headerNames = serverMetadata.getHeaderNames();
if (paramNames.contains(name)) {
value = uriComponents.getQueryParams().getFirst(name);
value = clientMetadata.getParameter(name);
} else if (headerNames.contains(name)) {
value = request.getHeaders().getFirst(name);
value = clientMetadata.getHeader(name);
}
return value;
}
private String getName(Map<Integer, Collection<String>> indexToName, int index) {

View File

@ -30,14 +30,12 @@ import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@ -111,12 +109,15 @@ public class DubboSpringCloudBootstrap {
@Bean
public ApplicationRunner restTemplateRunner() {
return arguments -> {
System.out.println(restTemplate.getForEntity("http://spring-cloud-alibaba-dubbo/echo?message=小马哥", String.class));
Map<String, Object> data = new HashMap<>();
data.put("name", "小马哥");
data.put("age", 33);
data.put("height", 173);
System.out.println(restTemplate.postForEntity("http://spring-cloud-alibaba-dubbo/toString", data, String.class));
ResponseEntity<String> entity = restTemplate.getForEntity("http://spring-cloud-alibaba-dubbo/echo?message=小马哥", String.class);
System.out.println(entity.getHeaders());
System.out.println(entity.getBody());
// Still issue
// Map<String, Object> data = new HashMap<>();
// data.put("name", "小马哥");
// data.put("age", 33);
// data.put("height", 173);
// System.out.println(restTemplate.postForEntity("http://spring-cloud-alibaba-dubbo/toString", data, String.class));
};
}