Skip to content

RestTemplate

RestTemplateUtil

1.添加httpclient依赖作为restTemplate的实现

java
<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<HttpMessageConverter<?>> 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请求的封装

java
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.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

/**
 * 发送http请求工具类
 *
 * @author gavin
 * @date 2020-07-04
 */

@Component
public 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);
    }
}

优化版本(添加自定义请求头参数):

java
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
 */

@Component
public 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.标准返回类

java
package com.bestser.factory.cloud;

import lombok.Data;

/**
 * @author gavin
 * @date 2020-07-04
 */
@Data
public class CloudResponse<T> {

    private Integer state;
    private String message;
    private T data;
}

CloudUrlBuilder

1.url构建工具类

java
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
 */
@Component
public 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资源路径的定义

java
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;
    }
}

使用示例

调用方

java
@RestController
@ResponseResult
@RequestMapping("/api/setting/part")
public class PartSettingController {

    @Autowired
    private DesignPartService designPartService;

    @GetMapping("/list")
    public List<ProductPropertyVO> listParts() {
        return RestTemplateUtil.get(CloudUrlBuilder.buildInventoryUrl(CloudResource.PART_LIST));
    }

    @PostMapping
    public 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())));
    }
}

被调用方

java
 @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);
    }