Spring HTTP 客户端

前言

Spring 提供了一些 HTTP 客户端类,可以方便地发起 HTTP 请求。如果需要了解更多 Spring Web 的相关内容,可参考Spring Web 指南

RestTemplate

RestTemplate 是 Spring Web 模块提供的一个同步的 HTTP 客户端,在 Spring 5(SpringBoot 2)中可使用。它提供了一系列的 HTTP 请求方法,非常容易理解,也提供了高层级的 API。

RestTemplate 提供了 HTTP 方法的许多包装方法。如 getXXX, postXXX, putXXX, patchXXX, deleteXXX,其中也提供了对返回类型的转换方法,如 getForObject 直接获取响应对象,getForEntity 返回 ResponseEntity 包装的响应对象,可获取响应状态码、响应头、响应体等数据。

String url = "http://localhost:8080/test";
ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class);

// 添加参数
String url = "http://localhost:8080/testParam/{1}/{2}";
ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class, "001", "张三");

如果要构建复杂的 URI,可以使用 UriBuilderFactory,参考 Spring URI 操作工具类

初始化

执行请求时,RestTemplate 通过 ClientRequestFactory 接口初始化 HTTP 实现库,可以使用下面的实现类,可通过构造函数修改。

  • JdkClientHttpRequestFactory:Java 的 HttpClient
  • HttpComponentsClientHttpRequestFactory:Apache HTTP 组件的 HttpClient
  • JettyClientHttpRequestFactory:Jetty 的 HttpClient
  • ReactorNettyClientRequestFactory:Reactor Netty 的 HttpClient
  • SimpleClientHttpRequestFactory:默认实现

当 Apache 或 Jetty 的 HttpClient 存在时会优先使用,否则,当 java.net.http 包被载入的话使用 Java 的 HttpClient,最后使用默认的 SimpleClientHttpRequestFactory。

请求响应体处理

RestTemplate 使用 HttpMessageConverter 接口来处理请求体和响应体。它会根据请求和响应的媒体类型(MIME)来自动获取处理实现类。下面就是一些常见的实现类,也可添加自定义的实现类。

  • StringHttpMessageConverter:处理文本类型,如 MIME 类型为 text/*,返回 text/plain 类型。
  • FormHttpMessageConverter:处理表单数据,如 application/x-www-form-urlencoded 类型、multipart/form-data 类型(上传文件)。
  • ByteArrayHttpMessageConverter:处理字节数组,可以处理任意类型 */*,返回 application/octet-stream 类型。
  • MarshallingHttpMessageConverter:处理 XML 数据,对应的类型为 text/xmlapplication/xml
  • MappingJackson2HttpMessageConverter:处理 JSON 数据,基于 Jackson 的 ObjectMapper,对应的类型为 application/json
  • MappingJackson2XmlHttpMessageConverter:处理 XML 数据,基于 Jackson XML 的 XmlMapper,对应的类型为 application/xml
  • SourceHttpMessageConverter:处理 javax.xml.transform.Source 类型的数据,对应的类型为 text/xmlapplication/xml

WebClient

WebClient 是 Spring WebFlux 模块提供的一个异步非阻塞的 HTTP 客户端,在 Spring 5(SpringBoot 2)中可使用。它基于 Reactor 实现了响应式的 API。

使用静态方法 create() 或 builder() 来创建 WebClient。

WebClient client = WebClient.builder()
		.codecs(configurer -> ... )
		.build();

WebClient 一旦创建就不可变了,不过可以通过克隆一个新的来修改。

WebClient client1 = WebClient.builder()
		.filter(filterA).filter(filterB).build();

WebClient client2 = client1.mutate()
		.filter(filterC).filter(filterD).build();

// client1 has filterA, filterB

// client2 has filterA, filterB, filterC, filterD

codecs 有缓冲数据大小限制,默认是 256 KB,如果遇到以下错误,需要修改配置。

org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer
WebClient webClient = WebClient.builder()
		.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
		.build();

获取响应

使用 retrieve() 方法发起请求获取响应。

WebClient client = WebClient.create("https://example.org");

// 获取 ResponseEntity
Mono<ResponseEntity<Person>> result = client.get()
		.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
		.retrieve()
		.toEntity(Person.class);

// 仅获取响应体
Mono<Person> result = client.get()
		.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
		.retrieve()
		.bodyToMono(Person.class);

// 获取流
Flux<Quote> result = client.get()
		.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
		.retrieve()
		.bodyToFlux(Quote.class);

异常处理

如果响应返回的状态码是 4XX 或 5XX,则会抛出 WebClientResponseException 异常(或其子类)。要自定义异常处理,使用 onStatus() 方法。

Mono<Person> result = client.get()
		.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
		.retrieve()
		.onStatus(HttpStatusCode::is4xxClientError, response -> ...)
		.onStatus(HttpStatusCode::is5xxServerError, response -> ...)
		.bodyToMono(Person.class);

Exchange

exchangeToMono() 和 exchangeToFlux() 方法提供了更灵活的请求方式,可自定义异常的处理。

Mono<Person> entityMono = client.get()
		.uri("/persons/1")
		.accept(MediaType.APPLICATION_JSON)
		.exchangeToMono(response -> {
			if (response.statusCode().equals(HttpStatus.OK)) {
				return response.bodyToMono(Person.class);
			}
			else {
				// 产生错误
				return response.createError();
			}
		});

请求体

请求体可使用异步类型包裹,如 Mono 或 Flux。

Mono<Person> personMono = ... ;

Mono<Void> result = client.post()
		.uri("/persons/{id}", id)
		.contentType(MediaType.APPLICATION_JSON)
		.body(personMono, Person.class)
		.retrieve()
		.bodyToMono(Void.class);

Flux<Person> personFlux = ... ;

Mono<Void> result = client.post()
		.uri("/persons/{id}", id)
		.contentType(MediaType.APPLICATION_STREAM_JSON)
		.body(personFlux, Person.class)
		.retrieve()
		.bodyToMono(Void.class);

// 使用 bodyValue 快捷方法包裹
Person person = ... ;

Mono<Void> result = client.post()
		.uri("/persons/{id}", id)
		.contentType(MediaType.APPLICATION_JSON)
		.bodyValue(person)
		.retrieve()
		.bodyToMono(Void.class);

要发送表单数据,可使用 MultiValueMap<String, String> 作为请求体对象,会自动将 Content-Type 设置为 application/x-www-form-urlencoded

MultiValueMap<String, String> formData = ... ;

Mono<Void> result = client.post()
		.uri("/path", id)
		.bodyValue(formData)
		.retrieve()
		.bodyToMono(Void.class);

要发送 Multipart 数据,可使用 MultiValueMap<String, ?> 作为请求体对象,MultipartBodyBuilder 可用于构造请求体。

MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart1", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));
builder.part("myPart", part); // Part from a server request

MultiValueMap<String, HttpEntity<?>> parts = builder.build();

Mono<Void> result = client.post()
		.uri("/path", id)
		.body(parts)
		.retrieve()
		.bodyToMono(Void.class);

同步使用

WebClient 也可以用在同步场景中,使用 block() 方法阻塞。

Person person = client.get().uri("/person/{id}", i).retrieve()
	.bodyToMono(Person.class)
	.block();

List<Person> persons = client.get().uri("/persons").retrieve()
	.bodyToFlux(Person.class)
	.collectList()
	.block();

如果有多个请求同时发送,可在最终结果阻塞,而不是每个请求都阻塞。

Mono<Person> personMono = client.get().uri("/person/{id}", personId)
		.retrieve().bodyToMono(Person.class);

Mono<List<Hobby>> hobbiesMono = client.get().uri("/person/{id}/hobbies", personId)
		.retrieve().bodyToFlux(Hobby.class).collectList();

Map<String, Object> data = Mono.zip(personMono, hobbiesMono, (person, hobbies) -> {
			Map<String, String> map = new LinkedHashMap<>();
			map.put("person", person);
			map.put("hobbies", hobbies);
			return map;
		})
		.block();

RestClient

RestClient 是 Spring Web 模块提供的一个同步的 HTTP 客户端,在 Spring 6(SpringBoot 3)中可使用。它是 RestTemplate 的升级版本,提供相似的 API。在 Spring 6 中建议使用它替换 RestTemplate。

使用静态方法 create() 或 builder() 来创建 RestClient。

RestClient defaultClient = RestClient.create();

RestClient customClient = RestClient.builder()
  .requestFactory(new HttpComponentsClientHttpRequestFactory())
  .messageConverters(converters -> converters.add(new MyCustomMessageConverter()))
  .baseUrl("https://example.com")
  .defaultUriVariables(Map.of("variable", "foo"))
  .defaultHeader("My-Header", "Foo")
  .requestInterceptor(myCustomInterceptor)
  .requestInitializer(myCustomInitializer)
  .build();

RestClient 提供了 HTTP 方法对应的 get(), post(), put(), patch(), delete() 等方法来发起请求。

int id = 42;
restClient.get()
  .uri("https://example.com/orders/{id}", id)
  ....

RestClient 初始化和 RestTemplate 类似,也会根据情况选择合适的 HttpClient。

获取响应

使用 retrieve() 方法获取响应,使用 body(Class) 或 body(ParameterizedTypeReference) 方法来转换响应体,它也使用 HttpMessageConverter 来处理请求体和响应体。

String result = restClient.get()
  .uri("https://example.com")
  .retrieve()
  .body(String.class);

System.out.println(result);

toEntity() 方法可以获取 ResponseEntity,即可获取响应的状态码、响应头等数据。

ResponseEntity<String> result = restClient.get()
  .uri("https://example.com")
  .retrieve()
  .toEntity(String.class);

System.out.println("Response status: " + result.getStatusCode());
System.out.println("Response headers: " + result.getHeaders());
System.out.println("Contents: " + result.getBody());

异常处理

当响应状态码为 4xx 或 5xx 时,RestClient 会抛出 RestClientException 异常的子类,可使用 onStatus 方法来注册处理函数。

String result = restClient.get()
  .uri("https://example.com/this-url-does-not-exist")
  .retrieve()
  .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
      throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders())
  })
  .body(String.class);

Exchange

exchange() 提供了比 retrieve() 方法更多的处理灵活性,不会自动抛出异常,可自行进行处理。

Pet result = restClient.get()
  .uri("https://petclinic.example.com/pets/{id}", id)
  .accept(APPLICATION_JSON)
  .exchange((request, response) -> {
    if (response.getStatusCode().is4xxClientError()) {
      throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders());
    }
    else {
      Pet pet = convertResponse(response);
      return pet;
    }
  });

HTTP Interface

Spring 也提供了通过接口的方式来发起 HTTP 请求,类似于 Feign。它基于 RestClient 或 WebClient,可发起同步或异步响应式请求,在 Spring 6(SpringBoot 3)中可使用。

在接口上使用注解定义。

interface RepositoryService {

	@GetExchange("/repos/{owner}/{repo}")
	Repository getRepository(@PathVariable String owner, @PathVariable String repo);

	// more HTTP exchange methods...

}

然后可以构建一个代理类来发起请求。下面的例子使用 RestClient。

RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();

RepositoryService service = factory.createClient(RepositoryService.class);

下面的例子使用 WebClient。

WebClient webClient = WebClient.builder().baseUrl("https://api.github.com/").build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();

RepositoryService service = factory.createClient(RepositoryService.class);

下面的例子使用 RestTemplate。

RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory("https://api.github.com/"));
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();

RepositoryService service = factory.createClient(RepositoryService.class);

也可以在类上定义共用参数,这样对所有的方法适用。

@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
interface RepositoryService {

	@GetExchange
	Repository getRepository(@PathVariable String owner, @PathVariable String repo);

	@PatchExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
	void updateRepository(@PathVariable String owner, @PathVariable String repo,
			@RequestParam String name, @RequestParam String description, @RequestParam String homepage);

}

异常处理

我们可以自定义异常处理方法。

对于 RestClient:

RestClient restClient = RestClient.builder()
		.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...)
		.build();

RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();

可参考 RestClient.Builder 的 defaultStatusHandler 的实现。

对于 WebClient:

WebClient webClient = WebClient.builder()
		.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
		.build();

WebClientAdapter adapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(adapter).build();

可参考 WebClient.Builder 的 defaultStatusHandler 的实现。

对于 RestTemplate:

RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(myErrorHandler);

RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();

可参考 RestTemplate 的 setErrorHandler 方法。

以上就是 Spring 提供的常用的 HTTP 客户端。如果觉得有用请点赞、收藏吧!

  • 41
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 引入Spring Cloud Config客户端依赖 在POM文件中添加如下依赖: ``` <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> ``` 2. 配置bootstrap.yml 在应用程序的src/main/resources目录下,新建bootstrap.yml文件,配置如下: ``` spring: application: name: your-application-name cloud: config: uri: http://config-server-host:port profile: dev label: master ``` 其中: - your-application-name:是当前应用程序的名称,必填项。 - uri:配置服务器的地址,必填项。 - profile:用于指定当前应用程序的配置文件,选填项。 - label:用于指定Git仓库中的分支或标签,默认为master,选填项。 3. 从配置服务器获取配置信息 在需要获取配置信息的类中,注入ConfigurableApplicationContext对象,并使用该对象获取配置信息,示例代码如下: ``` @Autowired private ConfigurableApplicationContext applicationContext; public void getConfig() { String foo = applicationContext.getEnvironment().getProperty("foo"); String bar = applicationContext.getEnvironment().getProperty("bar"); String baz = applicationContext.getEnvironment().getProperty("baz"); System.out.println(foo + " " + bar + " " + baz); } ``` 其中,foo、bar和baz是配置文件中的属性名。 4. 运行应用程序 配置完成后,启动应用程序即可从配置服务器获取配置信息。如果配置服务器中的配置信息发生变化,客户端会自动刷新配置信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值