- springboot2.1.4
- java11
- gradle5.4.1
java中http请求封装历史
Spring提供的RestTemplate是基于HttpClient等基础上封装,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接,我们只需要传入url及返回值类型即可。相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。
RestTemplate默认依赖JDK提供http连接的能力(HttpURLConnection),如果有需要的话也可以替换为例如 Apache HttpComponents、OkHttp或者Netty等其它HTTP library。默认SpringBoot封装了HttpClient,OkHttp,只用把HttpClient或者OkHttp加入到依赖jar即可。
implementation("org.apache.httpcomponents:httpclient:4.5.8") implementation("com.squareup.okhttp3:okhttp:3.14.2") RestTemplate包含以下几个部分:
- UriTemplateHandler 地址模版转换器 默认使用@PathVariable("xx")方式
- RequestFactory
- HttpMessageConverter 对象转换器
- ClientHttpRequestFactory 默认是JDK的HttpURLConnection
- ResponseErrorHandler 异常处理
- ClientHttpRequestInterceptor 请求拦截器
一般使用RestTemplateBuilder构建RestTemplate。
@bean public RestTemplateBuilder restTemplateBuilder(MyResponseErrorHandler myResponseErrorHandler,HttpClientRequestFactory httpClientRequestFactory) { HttpMessageConverters converters = this.messageConverters.getIfUnique(); return new RestTemplateBuilder() .rootUri("http://localhost:8080") .uriTemplateHandler(new MyUriTemplateHandler()) .errorHandler(myResponseErrorHandler) .requestFactory(httpClientRequestFactory) .messageConverters(converters.getConverters()) .additionalInterceptors(new RestHttpRequestInterceptor()); } 实现业务逻辑
GET
@Test public void testSGet() { Foo foo = restTemplate.getForEntity(rootUrl + "/foos/s/1", Foo.class).getBody(); Assert.assertEquals(foo.getName(), "name1"); } POST
@Test public void testSave() { Foo foo = new Foo(1000L, "foo"); Response response = restTemplate.postForEntity(rootUrl + "/foos", foo, Response.class).getBody(); Assert.assertEquals(response.getCode(), 200); } 泛型结果请求
泛型使用:ParameterizedTypeReference
@Test public void testList() { Foo foo = new Foo(1L, "name1"); Page page = new Page(0, 10, foo); String url = uriTemplateHandler.expand(rootUrl + "/foos", page).toString(); Response<Grid<Foo>> response = restTemplateService .request(url, HttpMethod.GET, null, new ParameterizedTypeReference<Response<Grid<Foo>>>() { }); Assert.assertEquals(100, response.getData().getTotal()); } 文件上传
@Test public void testUploadByResource() { LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>(); map.add("file", new ClassPathResource("test.txt")); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<>(map, headers); ResponseEntity<Response> result = restTemplate.exchange(rootUrl + "/foos/upload", HttpMethod.POST, requestEntity, Response.class); Assert.assertEquals(result.getBody().getCode(), 200); } 文件下载
@Test public void testDownload() throws UnsupportedEncodingException { // 小文件 RequestEntity requestEntity = RequestEntity.get(URI.create(rootUrl + "/foos/download")).build(); ResponseEntity<byte[]> responseEntity = restTemplate.exchange(requestEntity, byte[].class); byte[] downloadContent = responseEntity.getBody(); String str = new String(downloadContent, "UTF-8"); // 大文件 ResponseExtractor<ResponseEntity<File>> responseExtractor = response -> { File rcvFile = File.createTempFile("rcvFile", "zip"); FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile)); return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(rcvFile); }; ResponseEntity<File> getFile = this.restTemplate .execute(rootUrl + "/foos/download", HttpMethod.GET, null, responseExtractor); } 设置自定义异常处理
public class MyResponseErrorHandler implements ResponseErrorHandler { private final Set<Integer> acceptableStatus; public MyResponseErrorHandler(HttpClientProperties httpClientProperties) { this.acceptableStatus = httpClientProperties.getAcceptableStatus(); } @Override public boolean hasError(ClientHttpResponse response) throws IOException { return !acceptableStatus.contains(response.getRawStatusCode()); } @Override public void handleError(ClientHttpResponse response) throws IOException { log.error("Response error: {} {}", response.getStatusCode(), response.getStatusText()); StringBuilder inputStringBuilder = new StringBuilder(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), "UTF-8")); String line = bufferedReader.readLine(); while (line != null) { inputStringBuilder.append(line); inputStringBuilder.append('\n'); line = bufferedReader.readLine(); } if (log.isDebugEnabled()) { log.debug("============================response begin=========================================="); log.debug("Status code : {}", response.getStatusCode()); log.debug("Status text : {}", response.getStatusText()); log.debug("Headers : {}", response.getHeaders()); log.debug("Response body: {}", inputStringBuilder.toString()); log.debug("=======================response end================================================="); } throw new MyException(inputStringBuilder.toString()); } } 设置拦截器
public class RestHttpRequestInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { logRequestDetails(request); return execution.execute(request, body); } private void logRequestDetails(HttpRequest request) { log.info("Headers: {}", request.getHeaders()); log.info("Request Method: {}", request.getMethod()); log.info("Request URI: {}", request.getURI()); } } GET请求替换为query params方法
public class MyUriTemplateHandler implements UriTemplateHandler { @Autowired private ObjectMapper mapper; @Override public URI expand(String uri, Map<String, ?> params) { UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(uri); for (Map.Entry<String, ?> entry : params.entrySet()) { String firstKey = entry.getKey(); if (entry.getValue() instanceof Map) { Map values = (Map) entry.getValue(); values.forEach((k, v) -> { if (v != null) { uriComponentsBuilder.queryParam(firstKey + "." + k, v); } }); } else { if (entry.getValue() != null) { uriComponentsBuilder.queryParam(firstKey, entry.getValue()); } } } uri = uriComponentsBuilder.build().toUriString(); return URI.create(uri); } @Override public URI expand(String uri, Object... params) { if (params.length != 0 && params[0] != null) { Map map = mapper.convertValue(params[0], Map.class); return expand(uri, map); } else { return URI.create(uri); } } } 替换FastJson
这个比较简单,注册FastJson,加入到MessageConverters列中,但是涉及到上传和下载的时候还是不要使用FastJson的比较好,会有各种坑。这里是简单的解决办法。继承FastJsonHttpMessageConverter覆盖supports方法,只解析自己想要的包下的类。或者设置fastjson解析的MediaType,只支持JSON解析。
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); List<MediaType> supportedMediaTypes = new ArrayList<>(); supportedMediaTypes.add(MediaType.APPLICATION_JSON); converter.setSupportedMediaTypes(supportedMediaTypes); public class MyFastJsonHttpMessageConverter extends FastJsonHttpMessageConverter { @Override protected boolean supports(Class<?> clazz) { if (clazz == null) { return true; } if (clazz.getPackageName().contains("com.czarea.rest")) { return true; } else { return false; } } } 
