RestTemplate Configuration and Use of SpringBoot 2.X Kotlin Series

Keywords: Java Spring REST Apache

Since the rise of RESTFul API, Spring has provided developers with a client to access Rest. RestTemplate can not only call http interface conveniently, but also call micro-services under the same registry. It also has load balancing and fusing mechanisms. Of course, I've heard about OKhttp, HTTPClient and other network frameworks, but we're focusing on RestTemplate (one implementation is based on HTTPClient), because SpringBook comes with it, and the basic usage scenarios are covered.

What is RestTemplate?

RestTemplate is Spring's client for accessing Rest services.

RestTemplate provides a variety of convenient access methods to remote Http services, which can greatly improve the writing efficiency of the client.

Calling the default constructor of RestTemplate, the RestTemplate object creates HTTP requests at the bottom by using the implementation under the java.net package.

You can specify different HTTP requests by using ClientHttpRequestFactory.

ClientHttpRequestFactory interface provides two main implementations

1. One is SimpleClient HttpRequestFactory, which creates the underlying Http request connection in a way provided by J2SE (as provided by the java.net package).

2. One way is to use HttpComponents Client HttpRequestFactory. The bottom layer uses HttpClient to access remote Http services, and HttpClient can configure connection pool and certificate information.

Simple Configuration of RestTemplate

@Configuration
class RestTemplateConfig {

    /**
     * Build a RestTemplate Bean with the default configuration
     */
    @Bean
    fun restTemplate():RestTemplate {
        return RestTemplateBuilder().build()
    }
}

Send POST requests and submit parameters through Form forms

@Component
class RestTemplateClient {

    @Autowired lateinit var restTemplate: RestTemplate

    val logger:Logger = LoggerFactory.getLogger(this.javaClass)

    /**
     * Send POST requests and submit parameters through Form forms
     */
    fun <T> postForm(params: Map<String, String>, url: String, t:T) : ResultDTO<T>? {
        logger.info("request URL===========:{}", url)
        logger.info("Request parameters===========:{}", params)
        val httpHeaders = HttpHeaders()
        httpHeaders.contentType = MediaType.APPLICATION_FORM_URLENCODED
        val requestParams = LinkedMultiValueMap<String, String>()
        params.forEach(requestParams::add)
        val httpEntity = HttpEntity<MultiValueMap<String, String>>(requestParams, httpHeaders)
        return restTemplate.postForObject(url, httpEntity, ResultDTO<T>().javaClass)
    }
}

Writing Controller and Testing

@RestController
@RequestMapping("/api")
class LotteryController {

    @Autowired lateinit var restTemplateClient: RestTemplateClient

    /**
     * Lottery Type Query
     */
    @PostMapping("/lottery/types")
    fun lotteryTypes(@RequestBody params:Map<String, String>) : ResultDTO<LotteryDTO>? {
        return restTemplateClient.postForm(params, UrlEnums.DEMO1.url, LotteryDTO())
    }
}

Send POST requests, pass JSON parameters

/**
 * Send POST requests and submit parameters through Form forms
 */
fun <T> postBody(params: Map<String, String>, url: String, t:T) : ResultDTO<T>? {
    logger.info("request URL===========:{}", url)
    logger.info("Request parameters===========:{}", params)
    val httpHeaders = HttpHeaders()
    httpHeaders.contentType = MediaType.APPLICATION_JSON_UTF8
    val httpEntity = HttpEntity(params, httpHeaders)
    return restTemplate.postForObject(url, httpEntity, ResultDTO<T>().javaClass)
}

Send GET requests

/**
 * Send POST requests and submit parameters through Form forms
 */
fun <T> get(params: Map<String, String>, url: String, t:T) : ResultDTO<T>? {
    logger.info("request URL===========:{}", url)
    logger.info("Request parameters===========:{}", params)
    val builder = StringBuilder()
    params.forEach{k, v -> builder.append(k).append("=").append(v).append("&") }
    val param : String = builder.toString().substring(0, builder.length - 1)
    return restTemplate.getForObject(url.plus(param), ResultDTO<T>().javaClass)
}

GET Download Resources

/**
 * Download resources
 */
fun downGet(url: String, headerParams: Map<String, String>) : ByteArray? {
    logger.info("Resources URL==========>:{}", url)
    val httpHeaders = HttpHeaders()
    if (!CollectionUtils.isEmpty(headerParams)) {
        headerParams.forEach(httpHeaders::add)
    }
    val httpEntity = HttpEntity(LinkedMultiValueMap<String, String>().putAll(httpHeaders))
    return restTemplate.exchange(url, HttpMethod.GET, httpEntity, ByteArray::class.java).body
}

Invoking Microservice Interface

When we develop micro-service applications, sometimes an application may deploy many, but not on different machines. At this time, we can use Nginx to do a load balancing, but what we recommend here is Spring Cloud to provide load balancing. Just add a comment on the Bean to @LoadBalanced, as follows:

@Bean
@LoadBalanced
fun restTemplate():RestTemplate {
    return RestTemplateBuilder().build()
}

The difference between calling a normal HTTP request and calling a microservice is that it is http://application.name/xxx. The path we access is the microservice application name+business routing, because the service will be registered in the registry, and the registry will maintain the application name. When we access the application, we can know that the application is on that machine.

The basic usage is finished, but there is still a problem, because we use the simplest configuration, where we do not set the default timeout, the official default is 60S, but our business is usually response speed is required, a minute of waiting time for users is too long, at this time we need a timeout mechanism, specific settings. Set the following:

package io.intodream.kotlin05.config

import org.apache.http.impl.client.HttpClientBuilder
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.client.ClientHttpRequestFactory
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
import org.springframework.http.client.SimpleClientHttpRequestFactory
import org.springframework.http.converter.HttpMessageConverter
import org.springframework.http.converter.StringHttpMessageConverter
import org.springframework.stereotype.Component
import org.springframework.web.client.RestTemplate
import java.nio.charset.StandardCharsets

/**
 * @description
 * RestTemplate To configure
 * @author Jwenk
 * @copyright intoDream.io Dream building technology
 * @email xmsjgzs@163.com
 * @date 2019-04-05,22:42
 */
@Configuration
class RestTemplateConfig {

    @Value("\${remote.maxTotalConnect}")
    val maxTotalConnect: Int = 0
    @Value("\${remote.maxConnectPerRoute}")
    val maxConnectPerRoute: Int = 200
    @Value("\${remote.connectTimeout}")
    val connectTimeout: Int = 2000
    @Value("\${remote.readTimeout}")
    val readTimeout: Int = 3000

    /**
     * Create HTTP Client Factory
     */
    private fun createFactory() : ClientHttpRequestFactory {
        if (this.maxTotalConnect <= 0) {
            val factory = SimpleClientHttpRequestFactory()
            factory.setReadTimeout(this.readTimeout)
            factory.setConnectTimeout(this.connectTimeout)
            return factory
        }
        val httpClient = HttpClientBuilder.create()
                .setMaxConnTotal(this.maxTotalConnect)
                .setMaxConnPerRoute(this.maxConnectPerRoute).build()
        val factory = HttpComponentsClientHttpRequestFactory(httpClient)
        factory.setConnectTimeout(this.connectTimeout)
        factory.setReadTimeout(this.readTimeout)
        return factory
    }

    /**
     * Build a RestTemplate Bean with the default configuration
     */
    @Bean
    fun restTemplate():RestTemplate {
        val restTemplate = RestTemplate(this.createFactory())
        val converterList : MutableList<HttpMessageConverter<*>> = restTemplate.messageConverters
        var converterTarget : HttpMessageConverter<*>? = null
        /**
         * Setting String HttpMessageConverter Character Set to UTF-8 resolves the problem of Chinese scrambling
         */
        for (item: HttpMessageConverter<*> in converterList) {
            if (StringHttpMessageConverter::class.java == item.javaClass) {
                converterTarget = item
                break
            }
        }
        if (null != converterTarget) {
            converterList.remove(converterTarget)
        }
        converterList.add(1, StringHttpMessageConverter(StandardCharsets.UTF_8))
        return restTemplate
    }
}

Remember to introduce the jar of HttpClient in pom.xml, otherwise the startup will be wrong

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.6</version>
</dependency>

Some of the use and configuration optimization of RestTemplate is over. My favorite friends can pay attention to my blog. https://www.tisnz.com If there is something wrong, please correct it.

Posted by teamfox20 on Sun, 19 May 2019 18:16:16 -0700