Spring RestTemplate使用总结和源代码分析

1、http的请求方式

(1)GET:通过请求URI得到资源
(2)POST:用于添加新的内容
(3)PUT:用于修改某个内容,若不存在则添加
(4)DELETE:删除某个内容
(5)OPTIONS :询问可以执行哪些方法
(6)HEAD :类似于GET, 但是不返回body信息,用于检查对象是否存在,以及得到对象的元数据
(7)CONNECT :用于代理进行传输,如使用SSL
(8)TRACE:用于远程诊断服务器

2、Spring RestTemplate 的使用方式

   RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。

   (1)restTemplate.getForEntity() 和 restTemplate.getForObject();  

           restTemplate实现了RestOperations接口的getForObject请求方法如下

 T getForObject(URI url, Class responseType) throws RestClientException;

 T getForObject(String url, Class responseType, Object... uriVariables) throws RestClientException;

 T getForObject(String url, Class responseType, Map uriVariables) throws RestClientException;

 使用上面三个接口方法,分别实现发送请求

            // 使用方法一,不带参数
            String url = "http://114.67.70.75:8090/test/test1?id=00975";
            String res = restTemplate.getForObject(url, String.class);
            System.out.println(res);

            // 使用方法二,传参替换
            url = "http://114.67.70.75:8090/test/test1?id={?}";
            res = restTemplate.getForObject(url, String.class, "00975");
            System.out.println(res);

            // 使用方法二,map传参
            url = "http://114.67.70.75:8090/test/test1?id={id}&name={name}";
            Map params = new HashMap<>();
            params.put("id", "00975");
            params.put("name","紫光");
            res = restTemplate.getForObject(url, String.class, params);
            System.out.println(res);

 

           restTemplate.getForEntity()的使用形式与restTemplate.getForObject相同, restTemplate.getForEntity()的响应结果多了

          http状态码和responseHeader;

          ResponseEntity res2 = restTemplate.getForEntity(url,String.class);

           Spring RestTemplate使用总结和源代码分析_第1张图片

     (2)   restTemplate.postForObject() 和 restTemplate.postForEntity();

             restTemplate实现了RestOperations接口的postForObject请求方法如下

 T postForObject(URI url, @Nullable Object request, Class responseType) throws RestClientException;

 T postForObject(String url, @Nullable Object request, Class responseType,
			Object... uriVariables) throws RestClientException;

 T postForObject(String url, @Nullable Object request, Class responseType,
			Map uriVariables) throws RestClientException;

 使用上面三个接口方法,分别实现发送请求

MultiValueMap request = new LinkedMultiValueMap<>();
        request.add("name", name);
        request.add("age", age);

Map param = new HashMap();
        param.put("name",name);
        param.put("age",age);

//使用方法一
String result = restTemplate.postForObject(url, request, String.class);
System.out.println(result);

//使用方法二,表单参数和url相结合
request.clear();
request.add("name", name);
result = restTemplate.postForObject(url + "?age={?}", request, String.class, age);
System.out.println(result);


// 使用方法三,以集合形式传递参数
Map params = new HashMap<>();
params.put("weight", "65kg");
result= restTemplate.postForObject(url + "?weight={weight}", request, String.class, params);
System.out.println(result);

 同样postForEntity()方法比postForObject()方法增加了http状态和httpheaders返回

 

     (3)   restTemplate.exchange()

 ResponseEntity exchange(URI url, HttpMethod method, @Nullable HttpEntity requestEntity,
			Class responseType) throws RestClientException;

 ResponseEntity exchange(String url, HttpMethod method, @Nullable HttpEntity requestEntity,
			Class responseType, Object... uriVariables) throws RestClientException;


 ResponseEntity exchange(String url, HttpMethod method, @Nullable HttpEntity requestEntity,
			Class responseType, Map uriVariables) throws RestClientException;




  exchange 的使用方法和get 、post 的方法基本相同,只不过多了一个HttpMethod参数,来指定需要发送的请求类型

   (4)如何优雅的使用get方法发送参数

 

3、发送方法内参数的作用

       1、方法参数是什么意义

                 url:表示要调用的服务的地址

                 method:表示调用请求的方式

                 httpEntity:表示要传递的参数

                 responseType:表示返回消息体的数据类型

                 uriVarables:表示需要格式化到url的参数

                 注意:HttpEntity用于传递具体的参数值,而uriVariables则用于格式化Http地址。

         2、httpEntity参数设置

             

        //第一种,传入MultiValueMap
        HttpHeaders headers = new HttpHeaders();
        headers.set("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
        MultiValueMap params = new LinkedMultiValueMap();
        params.add("id","00975");
        params.add("name","tomcat");
        HttpEntity httpEntity = new HttpEntity(params, headers);

        ans = restTemplate.postForObject(url, httpEntity, String.class);

        日志如下:

      Spring RestTemplate使用总结和源代码分析_第2张图片

     

        headers.set("Content-Type", "application/json;charset=utf-8");
        Map param = new HashMap();
        param.put("id","00975");
        param.put("name","tomcat");
        httpEntity = new HttpEntity(param, headers);
        ans = restTemplate.postForObject(url, httpEntity, String.class);

 日志如下:

 

 

HttpHeaders设置了Content-Type,httpEntity的传入参数需要与之对应,不然可能会出现错误。

在调用restTemplate的发送方法时,httpEntity也可以传入object, 下面的代码会将其转换为httpEntity对象

public HttpEntityRequestCallback(@Nullable Object requestBody, @Nullable Type responseType) {
			super(responseType);
			if (requestBody instanceof HttpEntity) {
				this.requestEntity = (HttpEntity) requestBody;
			}
			else if (requestBody != null) {
				this.requestEntity = new HttpEntity<>(requestBody);
			}
			else {
				this.requestEntity = HttpEntity.EMPTY;
			}
		}

        3、httpEntity传入MultiValueMap和object类型的区别

else {
				Class requestBodyClass = requestBody.getClass();
				Type requestBodyType = (this.requestEntity instanceof RequestEntity ?
						((RequestEntity)this.requestEntity).getType() : requestBodyClass);
				HttpHeaders httpHeaders = httpRequest.getHeaders();
				HttpHeaders requestHeaders = this.requestEntity.getHeaders();
				MediaType requestContentType = requestHeaders.getContentType();
				for (HttpMessageConverter messageConverter : getMessageConverters()) {
					if (messageConverter instanceof GenericHttpMessageConverter) {
						GenericHttpMessageConverter genericConverter =
								(GenericHttpMessageConverter) messageConverter;
						if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
							if (!requestHeaders.isEmpty()) {
								requestHeaders.forEach((key, values) -> httpHeaders.put(key, new LinkedList<>(values)));
							}
							logBody(requestBody, requestContentType, genericConverter);
							genericConverter.write(requestBody, requestBodyType, requestContentType, httpRequest);
							return;
						}
					}
					else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
						if (!requestHeaders.isEmpty()) {
							requestHeaders.forEach((key, values) -> httpHeaders.put(key, new LinkedList<>(values)));
						}
						logBody(requestBody, requestContentType, messageConverter);
						((HttpMessageConverter) messageConverter).write(
								requestBody, requestContentType, httpRequest);
						return;
					}
				}
				String message = "No HttpMessageConverter for " + requestBodyClass.getName();
				if (requestContentType != null) {
					message += " and content type \"" + requestContentType + "\"";
				}
				throw new RestClientException(message); 
  

 根据传入的requestObject 与restTemplate 能够write的类型匹配,判断messageConverter是否能够处理传入的requestObject,

下面代码是对传入的参数进行转换;

protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {

		MediaType contentType = outputMessage.getHeaders().getContentType();
		JsonEncoding encoding = getJsonEncoding(contentType);
		JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
		try {
			writePrefix(generator, object);

			Object value = object;
			Class serializationView = null;
			FilterProvider filters = null;
			JavaType javaType = null;

			if (object instanceof MappingJacksonValue) {
				MappingJacksonValue container = (MappingJacksonValue) object;
				value = container.getValue();
				serializationView = container.getSerializationView();
				filters = container.getFilters();
			}
			if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
				javaType = getJavaType(type, null);
			}

			ObjectWriter objectWriter = (serializationView != null ?
					this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());
			if (filters != null) {
				objectWriter = objectWriter.with(filters);
			}
			if (javaType != null && javaType.isContainerType()) {
				objectWriter = objectWriter.forType(javaType);
			}
			SerializationConfig config = objectWriter.getConfig();
			if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
					config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
				objectWriter = objectWriter.with(this.ssePrettyPrinter);
			}
			objectWriter.writeValue(generator, value);

			writeSuffix(generator, object);
			generator.flush();
		}
		catch (InvalidDefinitionException ex) {
			throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
		}
		catch (JsonProcessingException ex) {
			throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
		}
	}

 最终Map类型会被转换成json串,

1、Content-Type:application/x-www-form-urlencoded;

protected String serializeForm(MultiValueMap formData, Charset charset) {
		StringBuilder builder = new StringBuilder();
		formData.forEach((name, values) ->
				values.forEach(value -> {
					try {
						if (builder.length() != 0) {
							builder.append('&');
						}
						builder.append(URLEncoder.encode(name, charset.name()));
						if (value != null) {
							builder.append('=');
							builder.append(URLEncoder.encode(String.valueOf(value), charset.name()));
						}
					}
					catch (UnsupportedEncodingException ex) {
						throw new IllegalStateException(ex);
					}
				}));

		return builder.toString();
	}

         这是对MultiValueMap类型的body的创建,可以看出将键值对以“=”链接,将多个键值对使用“&”符链接

httpEntity参数不同,发送内容的形式不同,接收参数的方法也不一样:

         4、参数传递类型与接收方式

       根据headers中content-type的设置类型,http能够传输的类型也是很多的,项目中调用API常用的:application/json发送json报文是对象类型的数据,application/x-www-form-urlencoded 数据以前端表单形式提交

       同样发送数据时,类型不相同,springboot在接收时的方式也是不相同的,如果使用application/json形式发送数据,springboot在接收数据时,就要以整体的对象形式来接收参数,request.getParameter("key")就不能得到参数了,同样也不能使用注解@RequestParam("key")了,需要以整体的形式来接收,可以使用注解@RequestBody; 同样如果反过来的话,使用application/x-www-form-urlencoded形式发送数据,就需要在parameter中才能得到参数;如下是使用springboot接收两种形式的参数的方法:

    @RequestMapping(value="test/acceptParam",method = RequestMethod.POST)
    public int acceptParam(@RequestParam("key") String key, HttpServletRequest request) {
        System.out.println(request.getParameter("key"));
        System.out.println(key);
        return 333;
    }

    @RequestMapping(value="test/acceptJson",method = RequestMethod.POST)
    public int acceptJson(@RequestBody String jsonString, HttpServletRequest request) {
        request.getParameter("key");
        System.out.println(jsonString);
        return 333;
    }

 

4、RestTemplaet的参数配置

      首先我们来看一下RestTemplaet的构造器

public RestTemplate() {
		this.messageConverters.add(new ByteArrayHttpMessageConverter());
		this.messageConverters.add(new StringHttpMessageConverter());
		this.messageConverters.add(new ResourceHttpMessageConverter(false));
		try {
			this.messageConverters.add(new SourceHttpMessageConverter<>());
		}
		catch (Error err) {
			// Ignore when no TransformerFactory implementation is available
		}
		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) {
			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能够发送的数据类型,这个在3.3中对传入参数进行格式化的时候会用到

public RestTemplate(ClientHttpRequestFactory requestFactory) {
		this();
		setRequestFactory(requestFactory);
	}

 这是我们常用的构造器,需要传入ClientHttpRequestFactory,创建restTemplate之前,需要先创建ClientHttpRequestFactory

HttpClient httpClient = HttpClientBuilder.create().setMaxConnTotal(200)//连接池的最大连接数
        .setMaxConnPerRoute(20).build();//单个主机最大连接数
    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
    factory.setConnectTimeout(3000);//连接超时时间3秒
    factory.setReadTimeout(30000);//读取超时时间30秒

RestTemplate restTemplate = new RestTemplate(factory);

解决中文乱码问题

StringHttpMessageConverter m = new StringHttpMessageConverter(Charset.forName("UTF-8"));
restTemplate.getMessageConverters().set(1,m);

ClientHttpRequestFactory也可以传入SimpleClientHttpRequestFactory:

//HttpComponentsClientHttpRequestFactory

public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
		HttpClient client = getHttpClient();

		HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
		postProcessHttpRequest(httpRequest);
		HttpContext context = createHttpContext(httpMethod, uri);
		if (context == null) {
			context = HttpClientContext.create();
		}


//SimpleClientHttpRequestFactory
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
		HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
		prepareConnection(connection, httpMethod.name());

		if (this.bufferRequestBody) {
			return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
		}
		else {
			return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
		}
	}

ClientHttpRequestFactory 有连接池管理链接,SimpleClientHttpRequestFactory每次调用restTemplate时都要打开一个链接,然后关闭,性能稍差一些。

 

 

 

你可能感兴趣的:(java技术分享,Spring)