1、引言
以往单体应用在单机中进行进程内通信,稳定性相当好。但修改为分布式系统,变为进程间通信,就需要跨设备的网络访问,由于微服务化后,每个微服务系统都对外暴露REST风格的访问接口,因此服务间的通信一般都是通过发起http请求RESTful接口完成的。
在日常工作中,主要有以下方式:
- HttpURLConnection(java内置的,过于原始,低效)
- HttpClient
- OKHttp
- RestTemplate
- Feign
HttpURLConnection、HttpClient、OKHttp都需要繁琐的编码,使用较少。RestTemplate和Feign相对简单,是常用的方式。
2、RestTemplate
RestTemplate
: Spring 提供的用于访问 Rest 服务的客户端,其提供了多种便捷访问远程 HTTP 服务的方法,大大提高客户端的编写效率。
使用方法(spring提供,不需要格外依赖)//proxyBeanMethods = false:轻量级模式,用来优化配置性能 @Configuration(proxyBeanMethods = false) public class RestTemplateConfig { //使用时注入该bean对象即可使用其方法访问远程服务 @Bean public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){ return restTemplateBuilder.build(); } }
2.1、核心方法
getForObject()、postForObject方法设置返回值类型,会返回该类型的返回值
put()、delete()方法摒弃了方法的返回值
getForEntity、postForEntity方法会返回ResponseEntity对象,该对象在响应结果基础上包装了响应状态码和响应头
exchange():自定义(底层用的麻烦,用的少),例:put请求并提供返回值使用
@SpringBootTest class RestTemplateTestApplicationTests { @Autowired private RestTemplate restTemplate; public static final String URI="http://localhost:8080"; //get和delete请求,请求体不能携带数据,只能使用url传值 //put和post请求,可以在url传值,也可以在请求体传值 //url传值:使用 k=v 或 可变长参数或map(替换{}占位符) //请求体传值:使用HttpEntity对象等 @Test public void testGet(){ //发送get请求(指定url和方法返回值类型,可指定url参数) //restTemplate.getForObject(URI+"/rest/get?username=小明",String.class); //restTemplate.getForObject(URI + "/rest/get?username={username}", String.class, "小李"); Map<String,String> map=new HashMap<>(); map.put("username","小白"); restTemplate.getForObject(URI + "/rest/get?username={username}", String.class,map); } @Test public void testDelete(){ //delete方法丢弃了返回值 restTemplate.delete(URI + "/rest/delete?username={username}", "小花"); } @Test public void testPost(){ //发送Post请求(指定url,请求体内容和方法返回值类型,可指定url参数) //创建httpheaders,并设置请求中内容类型 HttpHeaders headers=new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); //准备请求参数 MultiValueMap<k,v> ==》 Map<k,List<v>> MultiValueMap<String,String> map=new LinkedMultiValueMap<>(); map.add("username","小红"); map.add("password","123456"); //构建请求实体对象 HttpEntity<MultiValueMap<String,String>> entity=new HttpEntity<>(map,headers); User user = restTemplate.postForObject(URI + "/rest/post?age={age}", entity, User.class, 20); System.out.println(user); } @Test public void testPostJson(){ //发送Post请求(指定url,请求体内容和方法返回值类型,可指定url参数) //创建httpheaders,并设置请求中内容类型 HttpHeaders headers=new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); //构建请求实体对象 HttpEntity<Object> entity = new HttpEntity<>(new User("小蓝","123123"),headers); User user = restTemplate.postForObject(URI + "/rest/postjson?age={age}", entity, User.class, 21); System.out.println(user); //简单写法: //restTemplate.postForObject(URI + "/rest/postjson?age={age}",new User("小蓝","123123") , User.class, 21); } @Test public void testPut(){ //put方法丢弃了返回值 restTemplate.put(URI + "/rest/put", new User("小蓝","123123"), User.class); } @Test public void testPostForEntity(){ ResponseEntity<User> entity = restTemplate.postForEntity(URI + "/rest/postjson?age={age}", new User("小蓝", "123123"), User.class, 21); System.out.println("响应状态码"+entity.getStatusCodeValue()); System.out.println("响应结果:"+entity.getStatusCode().getReasonPhrase()); System.out.println("响应体:"+entity.getBody()); } @Test public void testExchange(){ //自定义(底层用的麻烦,用的少),例:put请求并提供返回值 //创建httpheaders,并设置请求中内容类型 HttpHeaders headers=new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); //构建请求实体对象 HttpEntity<Object> entity = new HttpEntity<>(new User("小黑","123123"),headers); ResponseEntity<User> response = restTemplate.exchange(URI + "/rest/put", HttpMethod.PUT, entity, User.class); System.out.println(response.getBody()); } }
2.2、切换底层通信组件
RestTemplate只是对其他的HTTP客户端的封装,其本身并没有实现HTTP相关的基础功能。其底层通信组件的实现是可以配置切换的,RestTemplate 默认支持三种HTTP客户端库:
- SimpleClientHttpRequestFactory。对应的HTTP库是java JDK自带的HttpURLConnection,这是默认使用的通信组件。
- HttpComponentsAsyncClientHttpRequestFactory。对应的HTTP库是Apache HttpComponents(包含HttpClient)。
- OkHttp3ClientHttpRequestFactory。对应的HTTP库是OkHttp3。
从开发人员的反馈,和网上的各种HTTP客户端性能以及易用程度评测来看,OkHttp3略优使用OKHttp3
- 导入okhttp3依赖
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.8.1</version> </dependency>
- 通过设置setRequestFactory方法,切换底层实现
@Configuration(proxyBeanMethods = false) public class RestTemplateConfig { @Bean public RestTemplate restTemplate(RestTemplateBuilder builder){ //切换底层通信组件为okhttp3 注意restTemplateBuilder为不可变对象,每次修改会返回新的builder,需要重新赋值 builder = builder.requestFactory(()->{ OkHttp3ClientHttpRequestFactory httpRequestFactory = new OkHttp3ClientHttpRequestFactory(); httpRequestFactory.setConnectTimeout(3000);//设置建立连接的超时时间 httpRequestFactory.setReadTimeout(5000);//设置读取数据的超时时间 httpRequestFactory.setWriteTimeout(5000);//设置写出数据的超时时间 return httpRequestFactory; }); return builder.build(); } }
2.3、使用拦截器
有一些场景需要对请求进行统一处理,如:向请求中添加统一的请求头对请求进行拦截、根据需要改写请求URL。
拦截器的开发步骤
:
- 实现ClientHttpRequestInterceptor接口
public class MyClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { @Override /** * @param request 请求对象,通过它可以获取请求的URI、请求方式和请求头 * @param body 随请求发送的请求体 * @param execution 执行请求的工具 */ public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { System.out.println("拦截器开发"); //发送请求 ClientHttpResponse response = execution.execute(request, body); return response; } }
- 配置自定义拦截器生效
@Configuration(proxyBeanMethods = false) public class RestTemplateConfig { @Bean public RestTemplate restTemplate(RestTemplateBuilder builder){ builder=builder.additionalInterceptors(new MyClientHttpRequestInterceptor()); return builder.build(); } }