单体项目拆分和RestTemplate
#1. 单体项目拆分
在之前的项目中,虽然我们利用了 maven 的『多模块』知识点,将一个项目(project)拆分成了多个模块(module),各个模块单独打包,但是,整个项目的最终的『成果』、产出仍然是一个 spring boot 的 jar 包。
各个模块的关系和整体关系如下:
整个项目
│
│── 前端项目(vue-cli 项目)
│
└── 后端项目
│
├── web 项目(整个后端项目的入口和最终代码成果)
│
│── xxx 模块
│ │── service
│ └── dao
│
│── yyy 模块
│ │── service
│ └── dao
│
└── zzz 模块
│── service
└── dao
整个项目的『代码成果』有 2 个:
- 前端项目执行 build 命令后生成的 dist 目录下的 html、css、js 等文件;
- web 项目执行 build 命令后生成的 target 中生成的 web.jar 包。逻辑上,它是一个 war 包。
而在微服务的架构思想中,我们一个项目会被『真正地』拆分成几个项目,每个项目都有独立的 MVC 三层。那么,原来的『每个模块』,现在就变成了真正意义上的『每个项目』。
各个模块(各个项目)的关系和整体关系如下:
整个项目
│
│── 前端项目 - vue 项目
│
│── 后端项目 - xxx 项目(入口1和成果1)
│ ├── web
│ │── service
│ └── dao
│
│── 后端项目 - yyy 项目(入口2和成果2)
│ ├── web
│ │── service
│ └── dao
│
└── 后端项目 - zzz 项目(入口3和成果3)
├── web
│── service
└── dao
整个项目的『代码成果』有 N 个:
-
前端项目执行 build 命令后生成的 dist 目录下的 html、css、js 等文件;
-
xxx 项目执行 build 命令后生成的 target 中生成的 xxx.jar 包。逻辑上,它是一个 war 包。
-
yyy 项目执行 build 命令后生成的 target 中生成的 yyy.jar 包。逻辑上,它是一个 war 包。
-
zzz 项目执行 build 命令后生成的 target 中生成的 zzz.jar 包。逻辑上,它是一个 war 包。
那么,在涉及到跨模块调用时,我们就无法像以前一样从代码层面直接调用。那么怎么办?在 xxx 项目的代码中编写代码,向 B 项目发起 HTTP 请求。
#2. RestTemplate 发起 HTTP 请求
说明
你的项目只要直接或间接引入了 spring-web 包,你就可以使用 RestTemplate 。
RestTemplate 类似于 Slf4J,它本身并没有做『更多』的什么事情,它的主要功能和目的是对背后真正干活的“人”做二次包装,以提供统一的、简洁的使用方式。
在默认的(未引入其它包的)情况下,在 RestTemplate 背后真正干活的是 JDK 中的网络相关类:HttpURLConnection 。如此之外,RestTemplate 还支持使用 HttpClient 和 OkHTTP 库,前提是,你要额外引入这两个包。
Spring RestTemplate 是 Spring 提供的用于访问 Rest 服务的客端。 RestTemplate 提供了多种便捷访问远程 HTTP 服务的方法,能够大大提高客户端的编写效率,所以很多客户端比如 Android 或者第三方服务商都是使用 RestTemplate 请求 restful 服务。
在以前的 Spring Boot 版本中,Spring IoC 容器中已经有一个创建好了的 RestTemplate 供我们使用,不过到了新版本的 Spring Boot 中,需要我们自己创建 RestTemplate 的单例对象:
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
#2.1 API 方法介绍
常见方法有:
请求类型 | API | 说明 |
---|---|---|
GET 请求 | getForEntity 方法 | 返回的 ResponseEntity 包含了响应体所映射成的对象 |
GET 请求 | getForObject 方法 | 返回的请求体将映射为一个对象 |
POST 请求 | postForEntity 方法 | 返回包含一个对象的 ResponseEntity ,这个对象是从响应体中映射得到的 |
POST 请求 | postForObject 方法 | 返回根据响应体匹配形成的对象 |
PUT 请求 | put 方法 | PUT 资源到特定的 URL |
DELETE 请求 | delete 方法 | 对资源执行 HTTP DELETE 操作 |
任何请求 | exchange 方法 | 返回包含对象的 ResponseEntity ,这个对象是从响应体中映射得到的 |
任何请求 | execute 方法 | 返回一个从响应体映射得到的对象 |
GET / POST / DELETE / PUT 都有专门的方法发出对应方式的请求。这些方法的底层方式都是 execute 方法,不过该方法的使用有些繁琐:
exchange(String url, HttpMeghod method, HttpEntity requestEntity,
Class responseType, Object... uriVariables);
exchange(String url, HttpMethod method, HttpEntity requestEntity,
Class responseType, Map uriVariables);
exchange(String url, HttpMethod method, HttpEntity requestEntity,
ParameterizedTypeReference responseType, Object... uriVariables);
exchange(String url, HttpMethod method, HttpEntity requestEntity,
ParameterizedTypeReference responseType, Map uriVariables);
exchange 方法参数说明:
参数 | 说明 |
---|---|
参数 url | 向哪个 url 发起请求。 |
参数 method | 发起哪种请求。 |
参数 requestEntity | 用以封装请求头(header)和请求体(body)的对象。 |
参数 responseType | 指定返回响应中的 body 的数据类型。 |
返回值 ResponseEntity | 其中封装的响应数据。包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。在输出结果中我们能够看到这些与 HTTP 协议有关的数据。 |
#2.2 发起 GET 请求
回顾一下 GET 请求的特点:
-
GET 请求不用『管』header 中的 content-type 的值。
-
GET 请求的参数是『追加』到 URL 中,而不是附带在请求的 body 部分的。
因此,如果没有其他的设置请求头的要求,GET 请求在调用 exchange()
方法时,不需要 HttpEntity 参数,因为它就是用来封装请求 body 和请求 header 的。
#参数
-
服务端代码
略。服务端返回一个简单的 String 。
Copied! -
客户端调用
@Resource private RestTemplate restTemplate; @Test public void test1() { String url = "http://localhost:8080/get1"; // RestTemplate template = new RestTemplate(); ResponseEntity<String> responseEntity = template.exchange(url, HttpMethod.GET, null, String.class); log.info("{}", responseEntity.getStatusCode()); log.info("{}", responseEntity.getHeaders()); log.info("{}", responseEntity.getBody()); }
Copied!
#返回
这里的关键在于,要在调用 exchange()
方法时明确说明返回的是一个 User 类型的对象。(这背后是因为,RestTemplate 需要知道要将收到的 JSON 格式的字符串按什么规则转换)。
-
服务端代码
略。服务端返回一个 User 对象。
Copied! -
客户端调用
String url = "http://localhost:8080/get3?username={1}&age={2}"; ResponseEntity<User> responseEntity1 = template.exchange(url, HttpMethod.GET, null, User.class, "tom", 10); log.info("{}", responseEntity1.getBody());
Copied!
#2.3 发起 POST 请求
回顾一下 POST 请求的特点:
-
POST 请求的 header 中的 content-type 的值是
application/x-www-form-urlencoded
。 -
POST 请求的参数是附加到请求的 body 部分的。
因此,在调用 exchange()
方法时,需要为其提供一个 HttpEntity
类型参数。
#参数
客户端调用代码:
String url = "http://localhost:8080/post1";
// 准备请求头部信息
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// 无参数情况下,不需要设置请求 body 部分
HttpEntity<?> entity = new HttpEntity<>(null, headers);
ResponseEntity<String> responseEntity = template.exchange(url, HttpMethod.POST, entity, String.class);
log.warn("{}", responseEntity.getStatusCode());
log.warn("{}", responseEntity.getHeaders());
log.warn("{}", responseEntity.getBody());
#返回
理论上,和 GET 请求方式是一样的,只需要按照上面所述做出响应修改即可。
#2.4 请求头和响应头
有时候你可能有些额外的信息需要附带在请求头中发送到后台,此时,你可以使用如下形式的代码:
String url = "http://47.xxx.xxx.96/register/checkEmail";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.add("...", "...");
headers.add("...", "...");
headers.add("...", "...");
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("...", "...");
params.add("...", "...");
params.add("...", "...");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
// Xxx obj = template.postForObject(url, params, Xxx.class);
Xxx obj = template.postForObject(url, request, Xxx.class);
如果我们想获取更多的 HTTP 响应信息(例如响应头),可以使用 postForEntity 方法。如下:
ResponseEntity<String> entity = template.postForEntity(url, params, String.class);
// 查看响应的状态码
System.out.println(entity.getStatusCodeValue());
// 查看响应的响应体
System.out.println(entity.getBody());
// 查看响应头
HttpHeaders headMap = entity.getHeaders();
for (Map.Entry<String, List<String>> m : headMap.entrySet()) {
System.out.println(m.getKey() + ": " + m.getValue());
}
#3. 切换底层实现(了解)
RestTemplate 底层实现最常用的有以下三种:
-
SimpleClientHttpRequestFactory 封装 JDK 的 URLConnection。默认实现
-
HttpComponentsClientHttpRequestFactory 封装第三方类库 HttpClient
-
OkHttp3ClientHttpRequestFactory 封装封装第三方类库 OKHttp
HttpClient 和 OKHttp 执行效率都要比 JDK 的 URLConnection 高不少。OKHttp 的性能最好,而 HttpClient(因为出现早)使用率更高、更流行。
引用 apache 基金会的 httpclient :
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
所以,在你将 RestTemplate 配置成单例时,你可以指定它使用何种底层库:
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate(); // 默认实现
// RestTemplate restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory()); // 等同默认实现
// RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); // 使用 HttpClient
// RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory()); // 使用 OkHttp
return restTemplate;
}
在实际的应用中,只需要选择上面的代码中的其中一种 RestTemplate Bean 即可。当然,无论 RestTemplate 背后是谁在真正地处理 HTTP 请求和响应,RestTemplate 对外提供的接口都是一致的,并且更简洁。