前言通常访问http接口,我们有时候会使用httpclient,但是其代码复杂,还得费心进行各种资源回收的编写,不建议直接使用。而RestTemplate是Spring提供的用于访问Rest服务的客户端,对get,post等请求以及反序列化支持都封装的比较好,使用起来简单粗暴优雅。但是笔者在使用时候碰到一些问题:1、乱码。2、不同的请求需要设置不同的超时时间。 故整理出来与大家分享如何循序渐进的封装一个比较使用顺手、完善的配置类。 一、RestTemplate初步配置

import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.client.ClientHttpRequestFactory;import org.springframework.http.client.SimpleClientHttpRequestFactory;/** * RestTemplate配置类 */@Configurationpublic class RestTemplateConfig {    @Bean    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();        factory.setReadTimeout(5000);//读超时 单位为ms        factory.setConnectTimeout(5000);//连接超时 单位为ms        return factory;    }}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;

/**
 * RestTemplate配置类
 */
@Configuration
public class RestTemplateConfig {
    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(5000);//读超时 单位为ms
        factory.setConnectTimeout(5000);//连接超时 单位为ms
        return factory;
    }
}

基于上面这点微量的配置代码,我们我们已经可以进行编写http请求代码了,粗暴简单吧!

二、使用初探下面来看下如何使用,他有post、get等spring封装好的API,也有稍微复杂点的exchange API,

但是我们的目的不是讨论这些API如何使用,而是建立一个完善已用省心的配置,这里就以post请求为例子。

import lombok.Data;import java.io.Serializable;@Datapublic class PersonRequest implements Serializable {    // 请求参数}import lombok.Data;import java.io.Serializable;@Datapublic class Person implements Serializable {    /**名称*/    private String name;    /**年龄*/    private int age;}public static void main(String[] args){        PersonRequest request= new PersonRequest ()        Person person= restTemplate.postForObject("http://172.27.249.130:3000/entity_service",                JSONObject.toJSONString(request), Person.class);} 
import lombok.Data;
import java.io.Serializable;

@Data
public class PersonRequest implements Serializable {
    // 请求参数
}

import lombok.Data;
import java.io.Serializable;

@Data
public class Person implements Serializable {
    /**名称*/
    private String name;
    /**年龄*/
    private int age;
}

public static void main(String[] args){
        PersonRequest request= new PersonRequest ()
        Person person= restTemplate.postForObject("http://172.27.249.130:3000/entity_service",
                JSONObject.toJSONString(request), Person.class);
}


三、问题乱码配置如果只是上述简单配置,那么我们在服务端接受到的参数会出现中文乱码

{\"name\":\"??\",\"age\":18},这是因为我这里是以String格式提交数据的,底层其实采用的是StringHttpMessageConverter来处理请求,默认是ISO-8859-1字符集

@Beanpublic RestTemplate restTemplate(ClientHttpRequestFactory factory) {    RestTemplate restTemplate = new RestTemplate(factory);    // 解决字符串编码默认iso乱码问题,将字符集强制设置为UTF-8    List>> list = restTemplate.getMessageConverters();    for (HttpMessageConverter> httpMessageConverter : list) {        if (httpMessageConverter instanceof StringHttpMessageConverter) {            ((StringHttpMessageConverter) httpMessageConverter).setDefaultCharset(StandardCharsets.UTF_8);            break;        }    }    return restTemplate;}
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
    RestTemplate restTemplate = new RestTemplate(factory);
    // 解决字符串编码默认iso乱码问题,将字符集强制设置为UTF-8
    List>> list = restTemplate.getMessageConverters();
    for (HttpMessageConverter> httpMessageConverter : list) {
        if (httpMessageConverter instanceof StringHttpMessageConverter) {
            ((StringHttpMessageConverter) httpMessageConverter).setDefaultCharset(StandardCharsets.UTF_8);
            break;
        }
    }
    return restTemplate;
}

这个时候我们的乱码就解决了

{\"name\":\"一斛墨水\",\"age\":18} 四、超时问题

我们的初步配置中,超时时间写死了,那么对于我们的url接口,可能A接口处理的慢,10秒钟才能返回,B接口1分钟才能返回,而C接口10毫秒就返回了,默认配置超时时间肯定不行,A和B接口只会触发超时。那有没有什么办法能够针对不同的接口设置不同的超时时间呢?

// 通过源码跟踪我们发现,// org.apache.http.impl.execchain.MainClientExec#execute// 在发起请求时会优先使用HttpClientContext中的超时时间设置@Overridepublic CloseableHttpResponse execute(        final HttpRoute route,        final HttpRequestWrapper request,        final HttpClientContext context,        final HttpExecutionAware execAware) throws IOException, HttpException {    ....            final int timeout = config.getSocketTimeout();            if (timeout >= 0) {                managedConn.setSocketTimeout(timeout);            }            ....}// 而config.getSocketTimeout()取的是RequestConfig属性socketTimeout的值// 那么我们就需要再使用的时候,每次重新设置这个值就可以了
// 通过源码跟踪我们发现,
// org.apache.http.impl.execchain.MainClientExec#execute
// 在发起请求时会优先使用HttpClientContext中的超时时间设置
@Override
public CloseableHttpResponse execute(
        final HttpRoute route,
        final HttpRequestWrapper request,
        final HttpClientContext context,
        final HttpExecutionAware execAware) throws IOException, HttpException {
    ....
            final int timeout = config.getSocketTimeout();
            if (timeout >= 0) {
                managedConn.setSocketTimeout(timeout);
            }
            ....
}
// 而config.getSocketTimeout()取的是RequestConfig属性socketTimeout的值
// 那么我们就需要再使用的时候,每次重新设置这个值就可以了


五、超时配置


import org.apache.http.client.HttpClient;import org.apache.http.client.config.RequestConfig;import org.apache.http.client.protocol.HttpClientContext;import org.apache.http.protocol.HttpContext;import org.springframework.http.HttpMethod;import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;import java.net.URI;// 参考了 class RestmelateFactory extends HttpComponentsClientHttpRequestFactory {    // 每次请求都可以设置超时时间 单位毫秒 the timeout value in milliseconds        public static ThreadLocal socketTimeoutThreadLocal = new ThreadLocal<>();    public RestmelateFactory(HttpClient httpClient) {        super(httpClient);    }    @Override    protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {        HttpContext context = HttpClientContext.create();        RequestConfig config = createRequestConfig(getHttpClient());        // 每次都从新设置超时时间                // 从ThreadLocal中获取超时时间,并设置到context中                Integer socketTimeout = socketTimeoutThreadLocal.get();        if (null != socketTimeout) {            RequestConfig.Builder builder = RequestConfig.copy(config);            builder.setSocketTimeout(socketTimeout);            config = builder.build();        }        context.setAttribute(HttpClientContext.REQUEST_CONFIG, config);        return context;    }}@Beanpublic ClientHttpRequestFactory restTemplateFactory() {    HttpClientBuilder httpClientBuilder = HttpClients.custom();    // HttpClient相关配置          RestTemplateFactory factory = new RestTemplateFactory(httpClient);    factory.setConnectTimeout(5000);    // 即为 SocketTimeout        factory.setReadTimeout(30000);    factory.setConnectionRequestTimeout(5000);    return factory;}// 这次在使用,就可以根据不同的请求设置不同的超时时间了// 可以再url服务端打上断点,一直等到restTemplate超时,// 会发现日志前后调用耗时就是你得超时时间public static void main(String[] args) {    try {       // 设置当前请求超时时间                RestTemplateFactory.socketTimeoutThreadLocal.set(10);        PersonRequest request = new PersonRequest() log.info("handler, start request:" + request);        try {            Person person = restTemplate.postForObject("http://172.27.249.130:3000/entity_service", JSONObject.toJSONString(request), Person.class);        } catch (RestClientException e) {            log.error("handler接口异常, request:" + request, e);        }    } finally {        // 清理ThreadLocal中超时时间                RestTemplateFactory.socketTimeoutThreadLocal.remove();    }}


六、切面设置超时时间每次调用都要编写重复代码设置超时时间到ThreadLocal,我强迫症,不能忍。大家要是能忍的话就可以不用再往下看了。下面我们就使用注解加切面消除这项重复工作。


import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * RestTemplate配置,比如目前只有的超时 */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface RestTemplateAnnotation {    /**请求超时 the timeout value in milliseconds*/    int timeout();}import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;@Aspect@Component@Slf4jpublic class RestTemplateAspect {    @Pointcut(value = "@annotation(com.annotation.RestTemplateAnnotation)")    public void accessOperation() {    }    @Around(value = "accessOperation() && @annotation(restTemplateAnnotation)")    public Object doAround(ProceedingJoinPoint joinPoint, RestTemplateAnnotation restTemplateAnnotation) throws Throwable {        try {            // 设置当前请求超时时间            HttpClientRequestFactory.socketTimeoutThreadLocal.set(restTemplateAnnotation.timeout());            return joinPoint.proceed();        } finally {            // 清理ThreadLocal中超时时间            HttpClientRequestFactory.socketTimeoutThreadLocal.remove();        }    }}/** * 注解使用 * * @param request * @return  */@RestTemplateAnnotation(timeout = 60000) public void handler () {  PersonRequest request = new PersonRequest() log.info("handler, start request:" + request);  try {      Person person = restTemplate.postForObject("http://172.27.249.130:3000/entity_service", JSONObject.toJSONString(request), Person.class);  } catch (RestClientException e) {      log.error("handler接口异常, request:" + request, e);  }}

至此我们就完成了一个使用简单粗暴优雅的http调用封装


java RestTemplate发送xml请求报文 resttemplate发送get请求_resttemplate get请求参数

        五、预告


        下期 :让电脑桌面烦人的羞羞广告弹窗无处遁形