How to use the service micro RestTemplate elegant calling API (interceptors, exception handling, message conversion)

I am concerned, you can get the latest knowledge, the classic face questions as well as micro-technology sharing service

  In the micro-service, restservice call each other is very common, how do we gracefully called, in fact, the Spring framework uses RestTemplatecan be gracefully class restservice to call each other, it simplifies and httpcommunication services, unified RESTfulstandards, encapsulates httplink, the operation is simple to use, you can also customize the required definition of RestTemplate mode. among them:

  • RestTemplateDefault HttpMessageConverterinstance HTTPmessage into POJOor from a POJOconverted HTTPmessage. By default master is registered mimetype of converter, but can also setMessageConvertersregister a custom converter.
  • RestTemplateUse the default DefaultResponseErrorHandlerof 40X Bad Requestor 50X internalabnormal errorerror information capture.
  • RestTemplateYou can also use interceptor interceptor, carried out on the request link tracking, and unified head settings.

Which RestTemplatealso defines a number of RESTmethods of interactive resources, most of which corresponds to HTTPthe method, as follows:

method Resolve
delete() Perform HTTP DELETE operation on a specific resource URL
exchange() Perform a specific HTTP method in the URL, return ResponseEntity contained objects
execute() A method performed on a specific HTTP URL, returns an object body obtained from the response map
getForEntity() Sending an HTTP GET request, it returns a response body ResponseEntity contains objects mapped into
getForObject() Sending an HTTP GET request, it returns a request will be mapped to a target body
postForEntity () POST data to a URL, returns an object comprising ResponseEntity
postForObject() POST data to a URL, the response returns the object matching body is formed
headForHeaders() Sending HTTP HEAD request, it returns an HTTP header containing a specific resource URL
optionsForAllow() Sending HTTP OPTIONS request, and returning an Allow header information of a particular URL
postForLocation () POST data to a URL, returns the URL of the newly created resource
put() PUT resource to a specific URL

1. RestTemplate source

1.1 default call link

restTemplateWhen an API call, the default call chain:

###########1.使用createRequest创建请求########
resttemplate->execute()->doExecute()
HttpAccessor->createRequest()
//获取拦截器Interceptor,InterceptingClientHttpRequestFactory,SimpleClientHttpRequestFactory
InterceptingHttpAccessor->getRequestFactory() 
//获取默认的SimpleBufferingClientHttpRequest
SimpleClientHttpRequestFactory->createRequest()

#######2.获取响应response进行处理###########
AbstractClientHttpRequest->execute()->executeInternal()
AbstractBufferingClientHttpRequest->executeInternal()

###########3.异常处理#####################
resttemplate->handleResponse()

##########4.响应消息体封装为java对象#######
HttpMessageConverterExtractor->extractData()
复制代码

1.2 restTemplate->doExecute()

In the default call chain, restTemplatean API call will call doExecutea method, this method is mainly the following steps may be performed:

1) using the createRequestcreate request, the fetch response
2) determines whether the response abnormality exception handling
3) the response message encapsulated as java object body

@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 {
		//使用createRequest创建请求
		ClientHttpRequest request = createRequest(url, method);
		if (requestCallback != null) {
			requestCallback.doWithRequest(request);
		}
		//获取响应response进行处理
		response = request.execute();
		//异常处理
		handleResponse(url, method, response);
		//响应消息体封装为java对象
		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();
		}
	}
}
复制代码

1.3 InterceptingHttpAccessor->getRequestFactory()

In the default call chain, InterceptingHttpAccessor的getRequestFactory()the method, if no interceptorinterceptor, would return to the default SimpleClientHttpRequestFactory, otherwise, return InterceptingClientHttpRequestFactoryto requestFactory, can resttemplate.setInterceptorsset a custom interceptor interceptor.

//Return the request factory that this accessor uses for obtaining client request handles.
public ClientHttpRequestFactory getRequestFactory() {
        //获取拦截器interceptor(自定义的)
		List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
		if (!CollectionUtils.isEmpty(interceptors)) {
			ClientHttpRequestFactory factory = this.interceptingRequestFactory;
			if (factory == null) {
				factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
				this.interceptingRequestFactory = factory;
			}
			return factory;
		}
		else {
			return super.getRequestFactory();
		}
	}
复制代码

Then call SimpleClientHttpRequestFactory的createRequestto create a connection:

@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
	HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
	prepareConnection(connection, httpMethod.name());

	if (this.bufferRequestBody) {
		return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
	}
	else {
		return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
	}
}
复制代码

1.4 resttemplate->handleResponse()

In the default call chain resttemplate的handleResponse, handling response, including exception handling, exception handling can call and setErrorHandlerset a custom method ErrorHandler, implemented abnormality determination process and the response to the request. Customize the ErrorHandlerneed to achieve ResponseErrorHandlerthe interface, but Spring bootalso provides a default implementation DefaultResponseErrorHandler, it is also possible to achieve their class through inheritance ErrorHandler.

DefaultResponseErrorHandlerThe default of 40X Bad Requestor 50X internalabnormal errorerror information capture. If you want to catch exceptions thrown by the information service itself, it requires self-realization through RestTemplatethe ErrorHandler.

ResponseErrorHandler errorHandler = getErrorHandler();
               //判断响应是否有异常
	boolean hasError = errorHandler.hasError(response);
	if (logger.isDebugEnabled()) {
		try {
			int code = response.getRawStatusCode();
			HttpStatus status = HttpStatus.resolve(code);
			logger.debug("Response " + (status != null ? status : code));
		}catch (IOException ex) {
			// ignore
		}
	}
	//有异常进行异常处理
	if (hasError) {
		errorHandler.handleError(url, method, response);
	}
}
复制代码

1.5 HttpMessageConverterExtractor->extractData()

In the default call chain, HttpMessageConverterExtractoris extractDatacarried out as a response message for encapsulating javaan object, you need to use messagethe converter can be increased by adding a custom way messageConverter: first acquiring a conventional messageConverter, custom then messageConverteradded.

According to restTemplatethe setMessageConverterssource code available, using additional embodiment prevents the original messageConverteris lost, the source:

public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        //检验
		validateConverters(messageConverters);
		// Take getMessageConverters() List as-is when passed in here
		if (this.messageConverters != messageConverters) {
		    //先清除原有的messageConverter
			this.messageConverters.clear();
			//后加载重新定义的messageConverter
			this.messageConverters.addAll(messageConverters);
		}
	}
复制代码

HttpMessageConverterExtractor的extractDataSource:

MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
	if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
		return null;
	}
	//获取到response的ContentType类型
	MediaType contentType = getContentType(responseWrapper);

	try {
	    //依次循环messageConverter进行判断是否符合转换条件,进行转换java对象
		for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
		//会根据设置的返回类型responseType和contentType参数进行匹配,选择合适的MessageConverter
			if (messageConverter instanceof GenericHttpMessageConverter) {
				GenericHttpMessageConverter<?> genericMessageConverter =
						(GenericHttpMessageConverter<?>) messageConverter;
				if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
					if (logger.isDebugEnabled()) {
						ResolvableType resolvableType = ResolvableType.forType(this.responseType);
						logger.debug("Reading to [" + resolvableType + "]");
					}
					return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
				}
			}
			if (this.responseClass != null) {
				if (messageConverter.canRead(this.responseClass, contentType)) {
					if (logger.isDebugEnabled()) {
						String className = this.responseClass.getName();
						logger.debug("Reading to [" + className + "] as \"" + contentType + "\"");
					}
					return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
				}
			}
		}
	}
	.....
}
复制代码

The relationship between contentType and messageConverter 1.6

In HttpMessageConverterExtractorthe extractDataprocess seen, based on contentTypethe responseClassselection messageConverteris readable, the message conversion. Relationship is as follows:

The class name Supported JavaType Supported MediaType
ByteArrayHttpMessageConverter byte[] application/octet-stream, */*
StringHttpMessageConverter String text/plain, */*
ResourceHttpMessageConverter Resource */*
SourceHttpMessageConverter Source application/xml, text/xml, application/*+xml
AllEncompassingFormHttpMessageConverter Map<K, List<?>> application/x-www-form-urlencoded, multipart/form-data
MappingJackson2HttpMessageConverter Object application/json, application/*+json
Jaxb2RootElementHttpMessageConverter Object application/xml, text/xml, application/*+xml
JavaSerializationConverter Serializable x-java-serialization;charset=UTF-8
FastJsonHttpMessageConverter Object */*

2. springboot integrated RestTemplate

  According to the source code analysis study, it can easily, easily to RestTemplate be gracefully used in the project, such as adding custom exception handling, MessageConverterand interceptors interceptor. As used herein, examples demo, please see the following pages.

2.1 Import dependency: (RestTemplate integrated in the Web Start)

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.2.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.10</version>
  <scope>provided</scope>
</dependency>
复制代码

. 2.2 RestTemplat located:

  • Use ClientHttpRequestFactoryproperty RestTemplat configuration parameters, for example ConnectTimeout, ReadTimeout;
  • Add custom interceptorinterceptors and exception handling;
  • Additional messageconverter;
  • Configure a custom exception handler.

 @Configuration
public class RestTemplateConfig {

    @Value("${resttemplate.connection.timeout}")
    private int restTemplateConnectionTimeout;
    @Value("${resttemplate.read.timeout}")
    private int restTemplateReadTimeout;

    @Bean
    //@LoadBalanced
    public RestTemplate restTemplate( ClientHttpRequestFactory simleClientHttpRequestFactory) {
        RestTemplate restTemplate = new RestTemplate();
        //配置自定义的message转换器
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        messageConverters.add(new CustomMappingJackson2HttpMessageConverter());
        restTemplate.setMessageConverters(messageConverters);
        //配置自定义的interceptor拦截器
        List<ClientHttpRequestInterceptor> interceptors=new ArrayList<ClientHttpRequestInterceptor>();
        interceptors.add(new HeadClientHttpRequestInterceptor());
        interceptors.add(new TrackLogClientHttpRequestInterceptor());
        restTemplate.setInterceptors(interceptors);
        //配置自定义的异常处理
        restTemplate.setErrorHandler(new CustomResponseErrorHandler());
        restTemplate.setRequestFactory(simleClientHttpRequestFactory);

        return restTemplate;
    }

    @Bean
    public ClientHttpRequestFactory simleClientHttpRequestFactory(){
        SimpleClientHttpRequestFactory reqFactory= new SimpleClientHttpRequestFactory();
        reqFactory.setConnectTimeout(restTemplateConnectionTimeout);
        reqFactory.setReadTimeout(restTemplateReadTimeout);
        return reqFactory;
    }
}
复制代码

2.3. Component (a custom exception handler, interceptor interceptor, message converter)

Custom interceptorinterceptors to achieve ClientHttpRequestInterceptorInterface

  • Custom TrackLogClientHttpRequestInterceptor, records resttemplateof requestand responseinformation can be tracked analysis;
  • Custom HeadClientHttpRequestInterceptor, the parameter setting request header. API to send various requests, many requests need to use similar or identical Http Header. If you regard the request before each Headerfill HttpEntity/RequestEntity, such code would seem rather redundant, you can set up a unified interceptor.

TrackLogClientHttpRequestInterceptor:

/**
 * @Auther: ccww
 * @Date: 2019/10/25 22:48,记录resttemplate访问信息
 * @Description:   记录resttemplate访问信息
 */
@Slf4j
public class TrackLogClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        trackRequest(request,body);
        ClientHttpResponse httpResponse = execution.execute(request, body);
        trackResponse(httpResponse);
        return httpResponse;
    }

    private void trackResponse(ClientHttpResponse httpResponse)throws IOException {
        log.info("============================response begin==========================================");
        log.info("Status code  : {}", httpResponse.getStatusCode());
        log.info("Status text  : {}", httpResponse.getStatusText());
        log.info("Headers      : {}", httpResponse.getHeaders());
        log.info("=======================response end=================================================");
    }

    private void trackRequest(HttpRequest request, byte[] body)throws UnsupportedEncodingException {
        log.info("======= request begin ========");
        log.info("uri : {}", request.getURI());
        log.info("method : {}", request.getMethod());
        log.info("headers : {}", request.getHeaders());
        log.info("request body : {}", new String(body, "UTF-8"));
        log.info("======= request end ========");
    }
}
复制代码

HeadClientHttpRequestInterceptor:

@Slf4j
public class HeadClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
    public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
       log.info("#####head handle########");
        HttpHeaders headers = httpRequest.getHeaders();
        headers.add("Accept", "application/json");
        headers.add("Accept-Encoding", "gzip");
        headers.add("Content-Encoding", "UTF-8");
        headers.add("Content-Type", "application/json; charset=UTF-8");
        ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, bytes);
        HttpHeaders headersResponse = response.getHeaders();
        headersResponse.add("Accept", "application/json");
        return  response;
    }
}
复制代码

Custom exception handler, inheritable DefaultResponseErrorHandleror implemented ResponseErrorHandlerinterfaces:

  • Implement a custom ErrorHandleridea is a corresponding exception handling strategy according to the response message body, the other abnormalities parent DefaultResponseErrorHandlerfor processing.
  • Custom CustomResponseErrorHandlerconduct 30x exception handling

CustomResponseErrorHandler:

/**
 * @Auther: Ccww
 * @Date: 2019/10/28 17:00
 * @Description:  30X的异常处理
 */
@Slf4j
public class CustomResponseErrorHandler extends DefaultResponseErrorHandler {
    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        HttpStatus statusCode = response.getStatusCode();
        if(statusCode.is3xxRedirection()){
            return true;
        }
        return super.hasError(response);
    }

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
        HttpStatus statusCode = response.getStatusCode();
        if(statusCode.is3xxRedirection()){
            log.info("########30X错误,需要重定向!##########");
            return;
        }
        super.handleError(response);
    }

}
复制代码

Custom message reformer

/**
 * @Auther: Ccww
 * @Date: 2019/10/29 21:15
 * @Description: 将Content-Type:"text/html"转换为Map类型格式
 */
public class CustomMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
    public CustomMappingJackson2HttpMessageConverter() {
        List<MediaType> mediaTypes = new ArrayList<MediaType>();
        mediaTypes.add(MediaType.TEXT_PLAIN);
        mediaTypes.add(MediaType.TEXT_HTML);  //加入text/html类型的支持
        setSupportedMediaTypes(mediaTypes);// tag6
    }

}
复制代码

Finally, the public may be concerned about [No.] Ccww notes, study together. Plus group, will share dry goods, as well as learning video collection every day!

Guess you like

Origin juejin.im/post/5db99c285188257e435592ac