RestTemplateUtil
1.添加httpclient依赖作为restTemplate的实现
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId></dependency>
2.配置
- 使用clientHttpRequestFactory, 可以设置超时时间, 连接池等等
- 使用自定义的fastJson转换器
- 使用自定义拦截器可以在每次发送请求时添加header等信息 ```java package com.bestser.factory.cloud;
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import com.bestser.factory.config.security.oauth2.AuthorizationConstants; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.client.RestTemplate;
/**
- RestTemplate配置类 *
- @author gavin
- @date 2020-07-01
- 文档: https://docs.spring.io/spring/docs/4.3.9.RELEASE/spring-framework-reference/html/remoting.html#rest-client-access
api: https://docs.spring.io/spring-framework/docs/4.3.9.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html */ @Configuration public class RestTemplateConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(RestTemplateConfig.class); private StringRedisTemplate redisTemplate;
public RestTemplateConfig(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Bean public RestTemplate restTemplate(ClientHttpRequestInterceptor interceptor) {
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());restTemplate.setInterceptors(Collections.singletonList(interceptor));replaceJackson2FastJson(restTemplate);return restTemplate;
}
@Bean public RestTemplate authorizedRestTemplate(ClientHttpRequestInterceptor interceptor) {
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());restTemplate.setInterceptors(Collections.singletonList(interceptor));replaceJackson2FastJson(restTemplate);return restTemplate;
}
/**
替换默认的jackson转换器为fastJson转换器 */ private void replaceJackson2FastJson(RestTemplate restTemplate) { List
> converters = restTemplate.getMessageConverters(); //原有的String是ISO-8859-1编码 替换成 UTF-8编码 converters.removeIf(c -> c instanceof StringHttpMessageConverter); converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8)); converters.add(0, fastJsonHttpMessageConverter()); } /**
配置fastJson转换器 */ @Bean public HttpMessageConverter fastJsonHttpMessageConverter() { FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue, SerializerFeature.QuoteFieldNames,
SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.WriteNullListAsEmpty,SerializerFeature.DisableCircularReferenceDetect);
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); fastJsonHttpMessageConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8)); fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig); return fastJsonHttpMessageConverter; }
/**
配置clientHttpRequestFactory */ @Bean public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() { try {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();//设置连接池PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();//最大连接数connectionManager.setMaxTotal(20);//同路由并发数connectionManager.setDefaultMaxPerRoute(10);httpClientBuilder.setConnectionManager(connectionManager);HttpClient httpClient = httpClientBuilder.build();// httpClient连接配置HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);//连接超时requestFactory.setConnectTimeout(60 * 1000);//数据读取超时时间requestFactory.setReadTimeout(60 * 1000);//连接不够用的等待时间requestFactory.setConnectionRequestTimeout(60 * 1000);return requestFactory;
} catch (Exception e) {
LOGGER.error(String.format("初始化clientHttpRequestFactory失败, 错误信息: %s", e));
} return null; }
/**
- 自定义拦截器携带token和authorization
*/
@Bean
public ClientHttpRequestInterceptor authorizedRequestInterceptor() {
return (httpRequest, bytes, clientHttpRequestExecution) -> {
}; } }HttpHeaders headers = httpRequest.getHeaders();String token = (String) SecurityContextHolder.getContext().getAuthentication().getCredentials();if (!StringUtils.isBlank(token)) {headers.add(AuthorizationConstants.TOKEN, token);}String authorization = redisTemplate.opsForValue().get(AuthorizationConstants.OAUTH2_TOKEN_KEY_PREFIX + token);if (!StringUtils.isBlank(authorization)) {headers.add(AuthorizationConstants.AUTHORIZATION, authorization);}return clientHttpRequestExecution.execute(httpRequest, bytes);
3.工具类, http请求的封装```javapackage com.bestser.factory.cloud;import com.alibaba.fastjson.JSONObject;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.http.HttpMethod;import org.springframework.stereotype.Component;import org.springframework.web.client.RestTemplate;/*** 发送http请求工具类** @author gavin* @date 2020-07-04*/@Componentpublic class RestTemplateUtil {private final static Log LOGGER = LogFactory.getLog(RestTemplateUtil.class);private final static int SUCCESS_CODE = 200;private final static Object EMPTY_REQUEST_BODY = new Object();private static RestTemplate rest;public RestTemplateUtil(RestTemplate restTemplate) {rest = restTemplate;}@SuppressWarnings("unchecked")private static <T> CloudResponse<T> doCall(HttpMethod method, String url, Object requestBody) {CloudResponse<T> response = new CloudResponse<>();switch (method) {case GET:response = rest.getForObject(url, CloudResponse.class);break;case POST:response = rest.postForObject(url, requestBody, CloudResponse.class);break;case PUT:rest.put(url, requestBody);response.setState(SUCCESS_CODE);break;case DELETE:rest.delete(url);response.setState(SUCCESS_CODE);break;default:throw new RuntimeException(String.format("未知请求类型: %s", method));}return response;}/*** 发送http请求** @param method 请求方法* @param url 请求url* @param requestBody 请求体* @param <T> 返回值的泛型* @return T*/private static <T> T call(HttpMethod method, String url, Object requestBody) {CloudResponse<T> response;try {response = doCall(method, url, requestBody);LOGGER.info(String.format("url: %s, requestBody: %s, response: %s", url, JSONObject.toJSON(requestBody),JSONObject.toJSON(response)));} catch (Exception e) {LOGGER.error(String.format("url: %s, requestBody: %s, errorMessage: %s", url, JSONObject.toJSON(requestBody),e.getMessage()));throw e;}if (response.getState() != SUCCESS_CODE) {LOGGER.error(String.format("url: %s, requestBody: %s, errorMessage: %s", url, JSONObject.toJSON(requestBody),response.getMessage()));throw new RuntimeException(String.format("url: %s, requestBody: %s, errorMessage: %s", url, JSONObject.toJSON(requestBody),response.getMessage()));}return response.getData();}public static <T> T get(String url) {return call(HttpMethod.GET, url, EMPTY_REQUEST_BODY);}public static <T> T post(String url) {return call(HttpMethod.POST, url, EMPTY_REQUEST_BODY);}public static <T> T post(String url, Object requestBody) {return call(HttpMethod.POST, url, requestBody);}public static void put(String url) {call(HttpMethod.PUT, url, EMPTY_REQUEST_BODY);}public static void put(String url, Object requestBody) {call(HttpMethod.PUT, url, requestBody);}public static void delete(String url) {call(HttpMethod.DELETE, url, EMPTY_REQUEST_BODY);}public static void delete(String url, Object requestBody) {call(HttpMethod.DELETE, url, requestBody);}}
优化版本(添加自定义请求头参数):
package com.bestser.factory.cloud;import com.alibaba.fastjson.JSONObject;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.http.HttpEntity;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpMethod;import org.springframework.http.MediaType;import org.springframework.stereotype.Component;import org.springframework.web.client.RestTemplate;/*** 发送http请求工具类** @author gavin* @date 2020-07-04*/@Componentpublic class RestTemplateUtil {private final static Log LOGGER = LogFactory.getLog(RestTemplateUtil.class);private final static int SUCCESS_CODE = 200;private final static Object EMPTY_REQUEST_BODY = new Object();private final static HttpHeaders EMPTY_REQUEST_HEADER = new HttpHeaders();private static RestTemplate rest;public RestTemplateUtil(RestTemplate restTemplate) {rest = restTemplate;}/*** 发送http请求** @param method 请求方法* @param url 请求url* @param requestBody 请求体* @param <T> 返回值的泛型* @return T*/@SuppressWarnings("unchecked")private static <T> T call(String url, HttpMethod method, HttpHeaders requestHeaders, Object requestBody) {CloudResponse<T> response;requestHeaders.setContentType(MediaType.APPLICATION_JSON);HttpEntity<Object> requestEntity = new HttpEntity<>(requestBody, requestHeaders);try {response = rest.exchange(url, method, requestEntity, CloudResponse.class).getBody();LOGGER.info(String.format("url: %s, requestBody: %s, response: %s", url, JSONObject.toJSON(requestBody),JSONObject.toJSON(response)));} catch (Exception e) {LOGGER.error(String.format("url: %s, requestBody: %s, errorMessage: %s", url, JSONObject.toJSON(requestBody),e.getMessage()));throw e;}if (response == null || response.getState() != SUCCESS_CODE) {LOGGER.error(String.format("url: %s, requestBody: %s, errorMessage: %s", url, JSONObject.toJSON(requestBody),response == null ? "null" : response.getMessage()));throw new RuntimeException(String.format("url: %s, requestBody: %s, errorMessage: %s", url, JSONObject.toJSON(requestBody),response == null ? "null" : response.getMessage()));}return response.getData();}public static <T> T get(String url) {return call(url, HttpMethod.GET, EMPTY_REQUEST_HEADER, EMPTY_REQUEST_BODY);}public static <T> T get(String url, HttpHeaders httpHeaders) {return call(url, HttpMethod.GET, httpHeaders, EMPTY_REQUEST_BODY);}public static <T> T post(String url) {return call(url, HttpMethod.POST, EMPTY_REQUEST_HEADER, EMPTY_REQUEST_BODY);}public static <T> T post(String url, Object requestBody) {return call(url, HttpMethod.POST, EMPTY_REQUEST_HEADER, requestBody);}public static void put(String url) {call(url, HttpMethod.PUT, EMPTY_REQUEST_HEADER, EMPTY_REQUEST_BODY);}public static void put(String url, Object requestBody) {call(url, HttpMethod.PUT, EMPTY_REQUEST_HEADER, requestBody);}public static void delete(String url) {call(url, HttpMethod.DELETE, EMPTY_REQUEST_HEADER, EMPTY_REQUEST_BODY);}}
4.标准返回类
package com.bestser.factory.cloud;import lombok.Data;/*** @author gavin* @date 2020-07-04*/@Datapublic class CloudResponse<T> {private Integer state;private String message;private T data;}
CloudUrlBuilder
1.url构建工具类
package com.bestser.factory.cloud;import java.util.Collections;import java.util.Map;import org.springframework.stereotype.Component;import org.springframework.util.CollectionUtils;import org.springframework.web.util.UriComponents;import org.springframework.web.util.UriComponentsBuilder;/*** cloud端url构建器** @author gavin* @date 2020-07-03*/@Componentpublic class CloudUrlBuilder {private static final String HTTP = "http";private static final String AUTH_HOST = "localhost:8612";private static final String INVENTORY_HOST = "localhost:8610";/*** 构建url** @param host 主机ip:端口* @param path 路径* @param params 请求参数* @param args 路径参数* @return url*/private static String buildUrl(String host, CloudResource path, Map<String, Object> params, String... args) {UriComponentsBuilder builder = UriComponentsBuilder.newInstance().scheme(HTTP).host(host).path(path.getPath());if (!CollectionUtils.isEmpty(params)) {params.forEach(builder::queryParam);}UriComponents build = builder.build();if (args != null && args.length > 0) {return build.expand(args).toUriString();}return build.toUriString();}public static String buildAuthUrl(CloudResource path, String... args) {return buildAuthUrl(path, Collections.emptyMap(), args);}public static String buildAuthUrl(CloudResource path, Map<String, Object> params) {return buildAuthUrl(path, params, null);}public static String buildAuthUrl(CloudResource path, Map<String, Object> params, String... args) {return buildUrl(AUTH_HOST, path, params, args);}public static String buildInventoryUrl(CloudResource path, String... args) {return buildInventoryUrl(path, Collections.emptyMap(), args);}public static String buildInventoryUrl(CloudResource path, Map<String, Object> params) {return buildInventoryUrl(path, params, null);}public static String buildInventoryUrl(CloudResource path, Map<String, Object> params, String... args) {return buildUrl(INVENTORY_HOST, path, params, args);}}
2.url资源路径的定义
package com.bestser.factory.cloud;/*** cloud端资源路径** @author gavin* @date 2020-07-01*/public enum CloudResource {/*** oauth2令牌*/OAUTH2_TOKEN("oauth2令牌", "/oauth/token"),/*** 客户端权限*/CLIENT_PRIVILEGE("客户端权限", "/auth/{serviceNames}"),/*** 配件*/PART("配件", "/product/part"),PART_VALUE("配件值", "/product/part/{propertyValueId}"),PART_LIST("配件列表", "/product/part/list");private final String caption;private final String path;CloudResource(String caption, String path) {this.caption = caption;this.path = path;}public String caption() {return this.caption;}public String getPath() {return path;}}
使用示例
调用方
@RestController@ResponseResult@RequestMapping("/api/setting/part")public class PartSettingController {@Autowiredprivate DesignPartService designPartService;@GetMapping("/list")public List<ProductPropertyVO> listParts() {return RestTemplateUtil.get(CloudUrlBuilder.buildInventoryUrl(CloudResource.PART_LIST));}@PostMappingpublic void createParts(@RequestBody ProductPropertyVO vo) {if (StringUtils.isBlank(vo.getPropertyValue())) {throw new RestMessageException(ResponseStateEnum.PARAM_ERROR, "配件名称不能为空");}RestTemplateUtil.post(CloudUrlBuilder.buildInventoryUrl(CloudResource.PART), vo);}@PutMapping@Transactional(rollbackFor = Exception.class)public void updatePart(@RequestBody ProductPropertyVO vo) {if (StringUtils.isBlank(vo.getPropertyValue()) || StringUtils.isBlank(vo.getNewPropertyValue())) {throw new RestMessageException(ResponseStateEnum.PARAM_ERROR, "配件名称不能为空");}// 修改 design_part 表的配件名称designPartService.updatePartName(vo.getPropertyValue(), vo.getNewPropertyValue());RestTemplateUtil.put(CloudUrlBuilder.buildInventoryUrl(CloudResource.PART), vo);}@DeleteMapping@Transactional(rollbackFor = Exception.class)public void deletePart(@RequestBody ProductPropertyVO vo) {if (designPartService.isPartUsed(vo.getPropertyValue())) {throw new RestMessageException(ResponseStateEnum.PARAM_ERROR, "配件已被使用, 不能删除");}RestTemplateUtil.delete(CloudUrlBuilder.buildInventoryUrl(CloudResource.PART_VALUE, String.valueOf(vo.getPropertyValueId())));RestTemplateUtil.delete(CloudUrlBuilder.buildInventoryUrl(CloudResource.PART_VALUE, String.valueOf(vo.getPropertyValueId())));}}
被调用方
@ApiOperation("获取配件列表")@GetMapping("/part/list")public List<PropertyValueVO> listParts() {return productService.listPropertiesByName("配件名称");}@ApiOperation("新增配件, 参数中propertyValue必填")@PostMapping("/part")public void createParts(@RequestBody ProductPropertyParam param) {if (StringUtils.isBlank(param.getPropertyValue())) {throw new ServiceException("配件名称不能为空");}productService.createPart(param.getPropertyValue());}@ApiOperation("修改配件名称, 参数中propertyValueId和propertyValue必填")@PutMapping("/part")public void updatePart(@RequestBody ProductPropertyParam param) {if (StringUtils.isBlank(param.getPropertyValue())) {throw new ServiceException("配件名称不能为空");}productService.updateProductPropertyValue(param.getPropertyValueId(), param.getNewPropertyValue());}@ApiOperation("删除配件, 参数中propertyValueId必填")@DeleteMapping("/part/{propertyValueId}")public void deletePart(@PathVariable("propertyValueId") Integer propertyValueId) {productService.deleteProductPropertyValue(propertyValueId);}
