持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情
一、springMVC快速使用
通过使用SpringBoot添加web启动器来实现SpringMVC应用
1-1、创建springboot项目
首先创建一个springboot项目,并添加web的启动器,这步操作比较简单,就不做演示了
1-1-1、创建User实体
package com.jony.entity;
import java.util.Date;
public class User {
private Integer id;
private String username;
private String address;
private Date birthday;
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + ''' +
", address='" + address + ''' +
'}';
}
public User(Integer id, String username, String address) {
this.id = id;
this.username = username;
this.address = address;
}
public User(String username, String address) {
this.username = username;
this.address = address;
}
public User(Integer id, String username, String address, Date birthday) {
this.id = id;
this.username = username;
this.address = address;
this.birthday = birthday;
}
public User() {
}
}
1-1-2、创建用于返回请求状态、信息、数据的Result类
package com.jony.entity;
public class Result<T> {
private Integer code;
private String message;
private T data;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Result(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public Result(Integer code, String message) {
this.code = code;
this.message = message;
}
public Result() {
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", message='" + message + ''' +
", data=" + data +
'}';
}
}
1-1-3、创建service模拟数据库操作
package com.jony.service;
import com.jony.entity.User;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
public class UserService {
private static Map<Integer, User> users = new HashMap<>();
static {
users.put(1, new User(1, "zhangsan", "北京", new Date()));
users.put(2, new User(2, "lisi", "上海"));
users.put(3, new User(3, "wangwu", "深圳"));
users.put(4, new User(4, "zhaoliu", "杭州"));
users.put(5, new User(5, "sunqi", "广州"));
}
/**
* 根据id查询用户
*
* @param id
* @return
*/
public User getUserById(Integer id) {
return users.get(id);
}
/**
* 查询所有用户
*
* @return
*/
public List<User> getAllUser() {
return new ArrayList(users.values());
}
/**
* 更新
*
* @param user
* @return
*/
public void update(User user) {
users.replace(user.getId(), user);
}
/**
* 新增
*
* @param user
* @return
*/
public void add(User user) {
Integer newId = users.size() + 1;
user.setId(newId);
users.put(newId, user);
}
/**
* 删除
*
* @return
*/
public void delete(Integer id) {
users.keySet().removeIf(key -> key == id);
}
}
1-1-4、创建控制器
package com.jony.controller;
import com.jony.entity.Result;
import com.jony.entity.User;
import com.jony.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
//Rest /user/1
@GetMapping("/{id}")
public Result getUser(@PathVariable Integer id) {
User user = userService.getUserById(id);
return new Result<>(200, "messge", user);
}
// 新增 /user/add
@PostMapping("/add")
public Result addUser(@RequestBody User user) {
System.out.println(user);
userService.add(user);
return new Result<>(200, "添加成功", userService.getAllUser());
}
// 修改 /user1
@PutMapping("/{id}")
public Result editUser(@RequestBody User user) {
userService.update(user);
return new Result<>(200, "修改成功", userService.getAllUser());
}
// 删除 /user1
@DeleteMapping("/{id}")
public Result deleteUser(@PathVariable Integer id) {
userService.delete(id);
return new Result<>(200, "删除成功", userService.getAllUser());
}
}
1-1-5、最终项目创建的代码结构
二、服务接口的调用
上面通过springboot创建了一个简单的服务,那么我们改如何调用这个服务呢?下面来介绍下面这几种方式
2-1、使用通过RestTemplate调用服务
RestTemplate是Spring提供的用于访问Rest服务的,RestTemplate提供了多种便捷访问远程Http服务的方法,传统情况下在java代码里访问restful服务,一般使用Apache的HttpClient。不过此种方法使用起来太过繁琐。spring提供了一种简单便捷的模板类来进行操作,这就是RestTemplate。
适用于微服务架构下 服务之间的远程调用 ps: 以后使用微服务架构, spring cloud feign
2-1-1、RestTemplate的相关方法
| 对应操作 | 对应方法 | |
|---|---|---|
| DELETE | delete | |
| GET | getForObject按照指定Class返回对象 getForEntity返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等 | |
| HEAD | headForHeaders | |
| OPTIONS | optionsForAllow | |
| POST | postForLocation/postForObject | |
| PUT | put | |
| any支持任何请求方法类型 | exchange/execute |
2-1-1-1、get操作
如下图,使用get操作,有getForObject及getForEntity,并且每个都有三个方法,参数分别为url/返回参数类型/传入的参数。
如果使用非限定参数的方法,传入的方法就可以以","分隔,比如需要调用的接口url为:baseUrl/${uid}/${username}
调用的时候就可以使用:restTemplate.getForObject(url,Result.class,123,zhangsan)
2-1-1-2、使用RestTemplate的注意事项
在使用RestTemplate在调用其他接口的时候,如果通过非URL传递参数,会被默认转换为Json的格式进行传递,因此被调用的方法,接收参数必须以Json的格式接收,否则无法接收参数
2-1-2、创建一个项目用于调用刚刚的服务
上面已经通过springboot创建了一个服务,为了更真实的演示服务之间的调用,我们再创建一个springboot服务
2-1-2-1、调用get请求查询
上图中,可以看到通过构造函数初始化了RestTemplate,然后就可以通过
restTemplate.getForObject进行调用了。
2-1-2-2-1、测试调用服务
下图中,通过调用刚刚RestTemplate的服务,然后再通过RestTemplate调用8080服务的接口,这样就可以完成两个服务之间的调用了。
2-1-2-2、调用post新增
上图调用了postForEntity的方法,目前可以看到传入了三个参数、分别为url/post对象/返回类型,postForEntity同样也支持通过URL传参,如下:
${a}/${b}为url传参,后面对应的1,2为对应参数
2-1-2-2-1、测试调用
可以看到新增的用户已经添加进去。
上面提到使用RestTemplate通过非url传参,被调用的接口必须使用Json格式进行接收参数,现在我们把接口改为非Json,看一下效果
原来方法:
// 新增 /user/add
@PostMapping("/add")
public Result addUser(@RequestBody User user) {
System.out.println(user);
userService.add(user);
return new Result<>(200, "添加成功", userService.getAllUser());
}
改后方法:
// 新增 /user/add
@PostMapping("/add")
public Result addUser(User user) {
System.out.println(user);
userService.add(user);
return new Result<>(200, "添加成功", userService.getAllUser());
}
测试结果:
可以看到我们传递的参数未被接收到。
2-1-2-3、调用put请求
我们被调用的方法,在更新用户的同时又将所有用户返回,因此我们更新之后,就可以再次获得所有用户了,被调用方法如下:
但是restTemplate中的put是没有返回值的,这就代表虽然我们可以更新数据,但是无法获得返回数据,如下图:
针对以上情况其实我们可以使用restTemplate的any的操作来进行处理,any可以处理上面table中的任意方法,如get/post/head等,
最终我们使用any的exchange调用put方法(感兴趣的同学,可以自己试下execute),如下:
2-1-2-3-1、测试调用
如上图,我们通过使用any的方式使用exchange方法,就实现了调用put请求,并获得放回值的处理。
2-1-2-4、调用delete
调用delete,使用的就是delete方法,delete同样没有返回值,如果想取返回值,同样可以使用any中的exchange/execute方法。
删除只需要通过URL传删除的ID即可,因此可以把HttpEntity设为null,然后再把HttpMethod设为Delete即可。
2-1-2-4-1、测试调用
如下图,可以看到已经没有ID为1的数据。
2-1-3、在单元测试中使用RestTemplate
需要注意的是在单元测试中需要使用TestRestTemplate,其他过程和上面远程调用一致,如下图:
2-2、通过MockMvc调用
MockMvc是由spring-test包提供,实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,使得测试速度快、不依赖网络环境。同时提供了一套验证的工具,结果的验证十分方便。
比如我们当前环境需要依赖一些外部的环境,这个时候如果启动程序还得启动其他外部程序,那就太繁琐了,这个时候就可以使用MockMvc来解决这个问题,下面来进行使用吧。
2-2-1、添加相关注解
使用MockMvc需要给测试添加两个注解分为为:@SpringBootTest和@AutoConfigureMockMvc
如下:
一般情况下使用一些功能需要添加依赖,MockMvc的依赖其实在
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
可以看下关系图
2-2-2、注入MockMvc并使用
通过AutoWrite注入MockMvc,同时添加junit测试注解,需要注意的是需要引用junit5的Test
最终代码如下:
2-2-2-1、控制台请求响应相关信息
MockHttpServletRequest:
HTTP Method = GET
Request URI = /user/1
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = com.jony.controller.UserController
Method = com.jony.controller.UserController#getUser(Integer)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"application/json"]
Content type = application/json
Body = {"code":200,"message":"messge","data":{"id":1,"username":"zhangsan","address":"北京","birthday":"2022-05-24T12:19:22.066+00:00"}}
Forwarded URL = null
Redirected URL = null
Cookies = []
2-2-2-2、设置响应的文本类型
通过accept设置响应的文本类型,如下
通过设置accept(MediaType.APPLICATION_JSON_UTF8),就可以设置Header信息了,如下:
2-2-2-3、设置请求参数
通过param设置请求参数,如下:
2-2-2-4、设置断言
通过使用andExpect设置响应断言,
可以设置响应状态的断言
设置响应数据断言
其中.data.username就可以获得username的值,然后再进行判断值是否为zhangsan
设置不匹配的断言数据,控制台就会告诉我们断言失败,如下图:
2-2-2-5、添加结果处理器
通过andDo可以添加结果处理器,我们就可以对结果进行处理,比如我们输出结果如下:
2-2-2-6、设置请求的类型
通过设置contentType就可以谁知请求的Conten-Type了。
2-2-3、使用MockMvc发送post请求
通过post可以发送相关数据
2-2-4、MockMvc的使用方法
* 1、mockMvc.perform执行一个请求。
* 2、MockMvcRequestBuilders.get("XXX")构造一个请求。
* 3、ResultActions.param添加请求传值
* 4、ResultActions.accept(MediaType.TEXT_HTML_VALUE)设置返回类型
* 5、ResultActions.contentType(MediaType.TEXT_HTML_VALUE)设置请求数据类型
* 6、ResultActions.andExpect添加执行完成后的断言。
* 7、ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情
* 比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。
* 8、ResultActions.andReturn表示执行完成后返回相应的结果。