【springboot进阶】RestTemplate集成okhttp3并自定义日志打印

目录

一、依赖 

二、配置

创建RestTemplate配置类

1.实例化OkHttp3客户端

2.实例化RestTemplate模板

创建拦截器

加入拦截器

三、效果


传统的java开发中,我们通常使用的连接工具为HttpClient使用起来比较复杂,新手容易出问题。而spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,以一种更优雅的方式实现远程调用。

一、依赖 

因为需要使用okhttp3作为http请求客户端,所以需要引入其依赖包。

    
      com.squareup.okhttp3
      okhttp
    

这里不需要手动设置版本号,maven会自动加载对应的版本jar,如下图。

【springboot进阶】RestTemplate集成okhttp3并自定义日志打印_第1张图片

spring的内置默认是使用SimpleClientHttpRequestFactory,使用的是JDK内置的java.net.URLConnection作为client客户端。经笔者使用中发现,在一些https场景中兼容性不是太好,而且配置繁琐,所以最终选择使用OkHttp3。

二、配置

创建RestTemplate配置类

1.实例化OkHttp3客户端

@Configuration
public class RestTemplateConfig {

    /**
     * 创建http请求工厂
     * 
     * 实例化OkHttp3
     * 
     * @return
     */
    @Bean
    public OkHttp3ClientHttpRequestFactory httpRequestFactory() {
        return new OkHttp3ClientHttpRequestFactory();
    }

}

当然,我们也可以设置一些请求超时的配置。

@Configuration
public class RestTemplateConfig {

    /**
     * 创建http请求工厂
     *
     * 实例化OkHttp3
     *
     * @return
     */
    @Bean
    public OkHttp3ClientHttpRequestFactory httpRequestFactory() {
        OkHttp3ClientHttpRequestFactory requestFactory = new OkHttp3ClientHttpRequestFactory();
        requestFactory.setConnectTimeout(5000);
        requestFactory.setReadTimeout(8000);
        requestFactory.setWriteTimeout(8000);
        return requestFactory;
    }

}

2.实例化RestTemplate模板

@Configuration
public class RestTemplateConfig {

    /**
     * 创建http请求工厂
     *
     * 实例化OkHttp3
     *
     * @return
     */
    @Bean
    public OkHttp3ClientHttpRequestFactory httpRequestFactory() {
        OkHttp3ClientHttpRequestFactory requestFactory = new OkHttp3ClientHttpRequestFactory();
        requestFactory.setConnectTimeout(5);
        requestFactory.setReadTimeout(8);
        requestFactory.setWriteTimeout(8);
        return requestFactory;
    }

    /**
     * 实例化RestTemplate模板
     * 
     * @return
     */
    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate(httpRequestFactory());

        // 中文乱码,主要是 StringHttpMessageConverter的默认编码为ISO导致的
        List> list = restTemplate.getMessageConverters();
        for (HttpMessageConverter converter : list) {
            if (converter instanceof StringHttpMessageConverter) {
                ((StringHttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8);
                break;
            }
        }

        return restTemplate;
    }

}

这里同时处理了中文乱码的问题,对于文本内容,统一使用UTF-8处理。

创建拦截器

在RestTemplate配置类中加入拦截器代码。

    /**
     * 自定义的拦截器
     *
     * 主要打印日志
     *
     */
    class MyRequestInterceptor implements ClientHttpRequestInterceptor {

        @Override
        public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] body, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
            //记录请求开始时间
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            //执行请求
            ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, body);
            //执行完毕后,这里要创建备份,要不然会报io提前关闭的异常错误
            ClientHttpResponse responseCopy = new BufferingClientHttpResponseWrapper(response);
            //记录请求结束时间
            stopWatch.stop();
            //获取响应内容
            StringBuilder resBody = new StringBuilder();
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(responseCopy.getBody(),
                    Charset.forName("UTF-8")))) {
                String line = bufferedReader.readLine();
                while (line != null) {
                    resBody.append(line);
                    line = bufferedReader.readLine();
                }
            }

            //获取请求内容
            String reqBody = "";
            //这里可以自定义想打印什么方法或者请求的内容,如下只打印post请求的日志,因为这时候才会带上body
            if (httpRequest.getHeaders().getContentType() != null && "POST".equals(httpRequest.getMethod().name())) {
                String requestBody = new String(body, Charset.forName("UTF-8"));
//                String contentType = httpRequest.getHeaders().getContentType().toString();
                //这里可以对contentType做一些判断,针对其格式化请求体的内容,如"application/x-www-form-urlencoded"格式会附带一些boundary(分割线)的内容
                reqBody = requestBody;

            }
            
            //打印日志的格式可以自定义
            log.info(JSON.toJSONString(RestLog.builder().costTime(stopWatch.getLastTaskTimeMillis()).headers(httpRequest.getHeaders()).method(httpRequest.getMethodValue())
                    .reqUrl(httpRequest.getURI().toString()).reqBody(reqBody).resBody(resBody.toString()).resStatus(responseCopy.getRawStatusCode()).build()));

            return responseCopy;
        }
    }


    /**
     * 响应内容备份
     */
    final class BufferingClientHttpResponseWrapper implements ClientHttpResponse {

        private final ClientHttpResponse response;

        private byte[] body;


        BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
            this.response = response;
        }


        @Override
        public HttpStatus getStatusCode() throws IOException {
            return this.response.getStatusCode();
        }

        @Override
        public int getRawStatusCode() throws IOException {
            return this.response.getRawStatusCode();
        }

        @Override
        public String getStatusText() throws IOException {
            return this.response.getStatusText();
        }

        @Override
        public HttpHeaders getHeaders() {
            return this.response.getHeaders();
        }

        @Override
        public InputStream getBody() throws IOException {
            if (this.body == null) {
                this.body = StreamUtils.copyToByteArray(this.response.getBody());
            }
            return new ByteArrayInputStream(this.body);
        }

        @Override
        public void close() {
            this.response.close();
        }
    }

    /**
     * 定义日志的字段
     */
    @Data
    @Builder
    private static class RestLog {
        private String reqUrl;
        private String method;
        private HttpHeaders headers;
        private String reqBody;
        private String resBody;
        private long costTime;
        private int resStatus;
    }

日志通过logback打印出来,要输出什么内容,格式如何打印,都可以自定义。

加入拦截器

在实例化RestTemplate模板方法中,加入上面创建的拦截器。

    /**
     * 实例化RestTemplate模板
     *
     * @return
     */
    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate(httpRequestFactory());
        // 添加拦截器
        List interceptors = new ArrayList<>();
        MyRequestInterceptor myRequestInterceptor = new MyRequestInterceptor();
        interceptors.add(myRequestInterceptor);
        restTemplate.setInterceptors(interceptors);
        // 中文乱码,主要是 StringHttpMessageConverter的默认编码为ISO导致的
        List> list = restTemplate.getMessageConverters();
        for (HttpMessageConverter converter : list) {
            if (converter instanceof StringHttpMessageConverter) {
                ((StringHttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8);
                break;
            }
        }

        return restTemplate;
    }

三、效果

测试调用网上找的免费api,查看打印日志的效果。

@SpringBootTest
class SpringbootRestTemplateApplicationTests {

    @Resource
    RestTemplate restTemplate;

    @Test
    void contextLoads() {
        String requestUrl = "https://api.uomg.com/api/icp";
        //构建json请求参数
        JSONObject params = new JSONObject();
        params.put("domain", "www.baidu.com");
        //请求头声明请求格式为json
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        //创建请求实体
        HttpEntity entity = new HttpEntity<>(params.toString(), headers);
        //执行post请求,并响应返回字符串
        String resText = restTemplate.postForObject(requestUrl, entity, String.class);
        System.out.println(resText);
    }

}

可以看到控制台的输出,将请求耗时、请求链接、请求参数、响应内容等都打印出来了。

o.l.s.f.config.RestTemplateConfig        : {"costTime":1517,"headers":{"Accept":["text/plain, */*"],"Content-Type":["application/json"],"Content-Length":["26"]},"method":"POST","reqBody":"{\"domain\":\"www.baidu.com\"}","reqUrl":"https://api.uomg.com/api/icp","resBody":"{\"code\":200901,\"msg\":\"查询域名不能为空\"}","resStatus":200}
{"code":200901,"msg":"查询域名不能为空"}

你可能感兴趣的:(springboot进阶应用,spring,boot,java,spring)