SpringBoot 使用 RestTemplate 出现乱码

文章目录

    • 1. 问题描述
    • 2. 简单处理
    • 3. 源码解读
    • 4. 解决方法
    • 5. 简单使用


1. 问题描述

项目中用到了百度的AI接口,我们使用Spring封装的RestTemplate 工具进行Http请求,出现了乱码问题,百度API文档写的是该接口响应的内容是UTF-8编码。
SpringBoot 使用 RestTemplate 出现乱码_第1张图片
如上图所示,百度AI接口返回的信息并没有指定编码(其实是UTF-8的编码格式,只是没指定)。所以Spring Boot就以默认的ISO-8859-1对返回的数据流进行了编码,转换为String,这时其实已经乱码了,接着因为我的开发环境是UTF-8的,所以打印出来全是乱码。转码过程大概是这样的:(UTF-8—>字节数据—>ISO-8859-1—>UTF-8

{
     
    "log_id":7806616030292411514,
    "items":[
        {
     
            "sentiment":2,
            "abstract":"东西很好吃",
            "prop":"东西",
            "begin_pos":0,
            "end_pos":12,
            "adj":"很好"
        },
        {
     
            "sentiment":2,
            "abstract":"服务也很不错",
            "prop":"服务",
            "begin_pos":0,
            "end_pos":18,
            "adj":"好"
        }
    ]
}

2. 简单处理

对返回信息进行编码转换。转换方法:对响应结果result进行ISO-8859-1解码和UTF-8编码即可。转码过程大概是这样的:(UTF-8—>字节数据—>ISO-8859-1—>字节数据—>UTF-8

//工具类中注入静态成员属性。
private static RestTemplate restTemplate;
@Autowired
public void setRestTemplate(RestTemplate restTemplate) {
     
     BaiduUtils.restTemplate = restTemplate;
}
//方法代码
String url = SERVER_COMMENT_TAG.replace("{TOKEN}", token);
HttpHeaders headers = new HttpHeaders();
MediaType mediaType = MediaType.parseMediaType("application/json; charset=UTF-8");
headers.setContentType(mediaType);
JSONObject jsonObject = new JSONObject();
jsonObject.put("type", type);
jsonObject.put("text", comment);
HttpEntity<String> paramEntity = new HttpEntity<>(jsonObject.toJSONString(), headers);
//这里提交的参数是String类型的
String baiduResult = restTemplate.postForObject(url, paramEntity, String.class);
//这里进行编码转换
String result = new String(baiduResult.getBytes("ISO-8859-1"), "UTF-8");
log.info(result );

打印结果

{
     
    "log_id":7806616030292411514,
    "items":[
        {
     
            "sentiment":2,
            "abstract":"东西很好吃",
            "prop":"东西",
            "begin_pos":0,
            "end_pos":12,
            "adj":"很好"
        },
        {
     
            "sentiment":2,
            "abstract":"服务也很不错",
            "prop":"服务",
            "begin_pos":0,
            "end_pos":18,
            "adj":"好"
        }
    ]
}

3. 源码解读

RestTemplate类的部分源码:

public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
     

	private static boolean romePresent;
	private static final boolean jaxb2Present;
	private static final boolean jackson2Present;
	private static final boolean jackson2XmlPresent;
	private static final boolean jackson2SmilePresent;
	private static final boolean jackson2CborPresent;
	private static final boolean gsonPresent;
	private static final boolean jsonbPresent;

	static {
     
		ClassLoader classLoader = RestTemplate.class.getClassLoader();
		romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
		jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
		jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)  && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
		jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
		jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
		jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
		gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
		jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
	}

	private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();

	//。。。。。。省略代码
	
	/**
	 * 使用默认设置创建 RestTemplate 的新实例。
	 * 默认初始化 HttpMessageConverter HttpMessageConverters。
	 */
	public RestTemplate() {
     
		this.messageConverters.add(new ByteArrayHttpMessageConverter());
		// 处理 String 类型的参数
		this.messageConverters.add(new StringHttpMessageConverter());
		this.messageConverters.add(new ResourceHttpMessageConverter(false));
		try {
     
			this.messageConverters.add(new SourceHttpMessageConverter<>());
		}
		catch (Error err) {
     
			// 忽略没有TransformerFactory实现可用的情况
		}
		this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
		if (romePresent) {
     
			this.messageConverters.add(new AtomFeedHttpMessageConverter());
			this.messageConverters.add(new RssChannelHttpMessageConverter());
		}
		if (jackson2XmlPresent) {
     
			this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
		}
		else if (jaxb2Present) {
     
			this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
		}
		if (jackson2Present) {
     
			// 处理 JSON 类型数据
			this.messageConverters.add(new MappingJackson2HttpMessageConverter());
		}
		else if (gsonPresent) {
     
			this.messageConverters.add(new GsonHttpMessageConverter());
		}
		else if (jsonbPresent) {
     
			this.messageConverters.add(new JsonbHttpMessageConverter());
		}
		if (jackson2SmilePresent) {
     
			this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter());
		}
		if (jackson2CborPresent) {
     
			this.messageConverters.add(new MappingJackson2CborHttpMessageConverter());
		}
		this.uriTemplateHandler = initUriTemplateHandler();
	}
	//。。。。。。省略代码
}

通过跟踪断点,简单分析下RestTemplate内部源码,发现以 JavaBean,Map,JSONObject 格式分别提交数据时,RestTemplate底层均采用了MappingJackson2HttpMessageConverter来处理请求。而以String格式提交数据时,底层其实采用的是StringHttpMessageConverter来处理请求。

打开StringHttpMessageConverter源码,发现该构造器默认的字符集是ISO-8859-1

public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
     

	/**
	 * 转换器使用的默认字符集。
	 */
	public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
	@Nullable
	private volatile List<Charset> availableCharsets;
	private boolean writeAcceptCharset = true;
	/**
	 * 使用"ISO-8859-1"作为默认字符集的默认构造函数。
	 * @see #StringHttpMessageConverter(Charset)
	 */
	public StringHttpMessageConverter() {
     
		this(DEFAULT_CHARSET);
	}
	/**
	 * 如果所请求的内容类型没有指定,则接受要使用的默认字符集的构造函数。
	 */
	public StringHttpMessageConverter(Charset defaultCharset) {
     
		super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
	}
	//。。。。。。省略代码
}

4. 解决方法

直接在List>中对StringHttpMessageConverter设置编码,并进行覆盖。

public class RestUtil {
     
	private final static RestTemplate restTemplate;
    static {
     
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        //做一些其他设置
        requestFactory.setConnectTimeout(3000);
        requestFactory.setReadTimeout(3000);
        restTemplate = new RestTemplate(requestFactory);
        //解决乱码问题,在列表中的对应位置直接覆盖
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
    }
    //工具方法
}
public class RestUtil {
     
	private final static RestTemplate restTemplate;
    static {
     
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        //做一些其他设置
        requestFactory.setConnectTimeout(3000);
        requestFactory.setReadTimeout(3000);
        restTemplate = new RestTemplate(requestFactory);
        //解决乱码问题,遍历寻找,进行覆盖
        List<HttpMessageConverter<?>> list = restTemplate.getMessageConverters();
	    for (HttpMessageConverter<?> httpMessageConverter : list) {
     
	        if(httpMessageConverter instanceof StringHttpMessageConverter) {
     
	            ((StringHttpMessageConverter) httpMessageConverter).setDefaultCharset(StandardCharsets.UTF_8);
	            break;
	        }
	    }
    }
    //工具方法
}

RestTemplate提供的构建器RestTemplateBuilder也提供了设置MessageConverter的方法,所以可以在构建时设置特殊的编码就可以解决问题了。

public class RestUtil {
     
	private final static RestTemplate restTemplate;
	static {
     
	    //解决乱码问题,遍历寻找,进行覆盖
	    StringHttpMessageConverter messageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
	    //添加额外的HttpMessageConverter,在构建器上配置的任何转换器都将替换RestTemplate的默认转换器。
	    restTemplate = new RestTemplateBuilder().additionalMessageConverters(messageConverter).build();
	}
	//工具方法
}

5. 简单使用

RestTemplate底层是通过HttpURLConnection实现的。
(1)getForObject

RestTemplate restTemplate = new RestTemplate(https://blog.csdn.net/mryang125/article/details/80955558);
String url = "http://localhost:8080/user/{id}";
UserVo userVo = restTemplate.getForObject(url, UserVo.class, id);

第一个参数表示URL,第二个参数表示返回类型,第三个参数表示URI中对应的参数,是一个可变长参数,可按顺序写多个参数。

(2)getForEntity

RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/users/{userName}/{note}/{start}/{limit}";
//使用map封装多个参数
Map<String, Object> params = new HashMap<>();
params.put("userName", userName);
params.put("note", note);
params.put("start", start);
params.put("limit", limit);
ResponseEntity<List> responseEntity = restTemplate.getForEntity(url, List.class, params);
List<UserVo> userVos = responseEntity.getBody();

(3)postForObject

RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/user";
//设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
//创建请求实体对象
HttpEntity<UserVo> request = new HttpEntity<>(newUserVo, headers);
User user = restTemplate.postForObject(url, request, User.class);

(4)postForEntity

RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/user";
//设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
//创建请求实体对象
HttpEntity<UserVo> request = new HttpEntity<>(newUserVo, headers);
//请求服务器
ResponseEntity<User> responseEntity = restTemplate.postForEntity(url, request, User.class);
//获取响应体
User user = responseEntity.getBody();
//获取响应头
HttpHeaders respHeaders = responseEntity.getHeaders();
//获取响应属性
List<String> success = respHeaders.get("success");
//获取响应状态码
int statusCode = responseEntity.getStatusCodeValue();

(5)delete

RestTemplate restTemplate = new RestTemplate();
restTemplate.delete("http://localhost:8080/use/{id}", id);

(6)exchange
RestTemplate还提供了一个exchange方法,该方法比上面的方法灵活,可以通过制定参数实现各种Http请求。下面列出Spring提供的八种方法。

<T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException;

<T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;

<T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType) throws RestClientException;

<T> ResponseEntity<T> exchange(String url,HttpMethod method, HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException;

<T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Map<String, ?> uriVariables) throws RestClientException;

<T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType) throws RestClientException;

<T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Class<T> responseType) throws RestClientException;

<T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, ParameterizedTypeReference<T> responseType) throws RestClientException;

下面写一个使用例子:

RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/user";
//设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
//创建请求实体对象
HttpEntity<UserVo> request = new HttpEntity<>(newUserVo, headers);
//请求服务器
ResponseEntity<User> responseEntity = restTemplate.exchange(url,  HttpMethod.POST, request, User.class);
//获取响应体
User user = responseEntity.getBody();
//获取响应头
HttpHeaders respHeaders = responseEntity.getHeaders();
//获取响应属性
List<String> success = respHeaders.get("success");
//获取响应状态码
int statusCode = responseEntity.getStatusCodeValue();

//获取资源
String url1 = "http://localhost:8080/user/{id}";
ResponseEntity<User> responseEntity1 = restTemplate.exchange(url1,  HttpMethod.GET, null, User.class, id);
//获取响应体
User user1 = responseEntity1.getBody();

你可能感兴趣的:(Spring,java,spring,http)