前言通常访问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调用封装
五、预告
下期 :让电脑桌面烦人的羞羞广告弹窗无处遁形