查询用户以及用户详情
最简单的,也最常用的方式是 GET 请求,用于查询全部数据或者其中一条数据的详情。下面先对 Restful 的 GET 请求进行介绍。
编写测试类程序
写测试类
如何保证自己的服务可以运行,其中最重要的方式是写测试类。在第 3 章中所有 Restful 服务都将通过测试类进行测试,所以在这一小节介绍如何写一个测试类,后面的测试类主要测试函数。首先引入测试的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
在第 2 章的基础上讲解,所以仍然使用 Demo 项目。新建包并包括 UserControllerTest 类,测试类的大纲如图 3.1 所示。
然后,先写一个测试类程序,导入的包不另外添加,代码如下所示。
//如何运行程序
@RunWith(SpringRunner.class)
//说明这是一个Spring Boot测试用例
@SpringBootTest
public class UserControllerTest {
//伪造测试用例,不需要执行tomcat,运行速度会快一些
@Autowired
private WebApplicationContext wac;
//伪造一个MVC的环境
private MockMvc mockMvc;
@Before
public void setUp() {
mockMvc=MockMvcBuilders.webAppContextSetup(wac).build();
}
/**
* @throws Exception
* @Desciption 测试用例
*/
@Test
public void whenQuerySuccess() throws Exception {
//发送GET请求
mockMvc.perform(MockMvcRequestBuilders.get("/user")
.contentType(MediaType.APPLICATION_JSON_UTF8))
//判断是否符合预期
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
最后我们运行测试。也许读者会很疑惑,控制类还没写,明知错误为什么还要运行?其实,这里的测试类也是一个程序,我们首先要保证程序正确,可以运行,如图 3.2 所示。
服务返回的 HTTP 状态值是 404。虽然报错,但还是符合预期,这说明测试类程序没有问题,后面会补充控制类,让这个测试类程序运行起来。
知识点
解释一下测试类用到的知识点。MockMvc 是服务端 Spring MVC 测试支持的主入口点,可以用来模拟客户端请求。这里主要用于测试,因此需要引入 MockMvc。
注解 @RunWith:指定测试要运行的运行器,例如 SpringRunner.class。
注解 @Autowired:自动加载 Bean,在后文会仔细介绍。
WebApplicationContext:WebMVC 的 IOC 容器对象,需要声明并通过 @Autowired 自动装配进来。
MockMvcRequestBuilders:用于构建 MockHttpServletRequestBuilder。为什么要构建 MockHttpServletRquestBuilder?这是因为 MockHttpServletRequestBuilder 是用于构建 MockHttpRequest,作为 MockMvc 的请求对象。
MockMvc:通过 MockMvcBuilders 的 webAppContextSetup(WebApplicationContext context) 方法获取 DefaultMockMvcBuilder。再调用 build 方法,初始化 MockMvc。
perform 方法:perform(RequestBuilder requestBuilder) throws Exception 执行请求,需要传入 MockHttpServletRequest对象,作为请求对象。
andExpect 方法:andExpect(ResultMatcher matcher) throws Exception,进行预期匹配。例如 MockMvcResultMatchers.status().isOk() 表示预期匹配响应成功。
常用注解
Spring Boot 的注解特别多,我们只介绍常用的注解。
-
@RestController:表明此Controller提供的是Restful服务。 -
@RequestMapping及其变体:将URL映射到Java。 -
@RequestParam:映射请求参数到Java上的参数。 -
@PageableDefault:指定分页参数的默认值。
下面,我们以查询全部用户为例,分别讲解这几个常用的注解。
@RestController与@RequestMapping小测试
在图 3.2 中出现的报错还没有解决,在这个基础上讲解注解时,我们就顺便解决出现的 404 服务访问不到的问题,其中图3.3 就是执行的效果。控制类代码如下所示。
@RestController
public class UserController {
@RequestMapping(value="/user",method=RequestMethod.GET)
public List<User> query(){
List<User> userList=new ArrayList();
//将list中添加了三个空的user对象,这里暂时没对user进行属性赋值
userList.add(new User());
userList.add(new User());
userList.add(new User());
return userList;
}
}
测试类代码如下所示。
/**
* @throws Exception
* @Desciption RestController与RequestMapping的测试用例
*/
@Test
public void whenQuerySuccess() throws Exception {
//发送GET请求
mockMvc.perform(MockMvcRequestBuilders.get("/user")
.contentType(MediaType.APPLICATION_JSON_UTF8))
//判断是否符合预期
.andExpect(MockMvcResultMatchers.status().isOk())
andExpect(MockMvcResultMatchers.jsonPath("$.length()"). value(3));
}
其中,User.java 中的代码如下所示。
public class User {
private String username;
private String password;
/**
GET与SET方法省略
*/
}
执行结果如图 3.3 所示。
在控制类中,使用了 @RestController 注解,说明当前的控制类可以提供 Restful 的服务。在 query 方法中添加了 @RequestMapping 注解,将 URL 映射到 Java 上,其参数 value 是请求资源 URL,method 是对该资源的操作行为。
在测试类中,模拟了一个 Restful 服务,使用 GET 方法,其访问的路径为 /user。根据规则,这个时候,程序会执行控制器中的 query 方法。
@RequestParam测试
@RequestParam 测试的知识点稍微有点多,这里主要介绍接收单个参数值、其他的属性,以及接收多个参数值的不足。接收单个参数值,控制类代码如下所示。
/**
* @Desciption 测试@RequestParam
* @return
*/
@RequestMapping(value="/user",method=RequestMethod.GET)
public List<User> query2(@RequestParam String username){
//打印观察是否接收到username参数
System.out.println("username is :"+username);
List<User> userList=new ArrayList();
//将list中添加了三个空的user对象,这里暂时没对user进行属性赋值
userList.add(new User());
userList.add(new User());
userList.add(new User());
return userList;
}
测试类代码如下所示。
/**
* @throws Exception
* @Desciption 主要用于测试@RequestParam的单个参数接收
*/
@Test
public void whenQuerySuccess2() throws Exception {
//发送请求
mockMvc.perform(MockMvcRequestBuilders.get("/user")
.param("username", "tom")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk());
}
最终效果如图 3.4 所示。
@RequestParam的其他属性
其他属性如图 3.5 所示。
-
defaultValue:参数的默认值。当存在这个参数的时候,如果访问的时候没有传递参数,程序中的参数会获取defaultValue中的值。 -
required:指定参数是否为必需传递的参数,默认是true。
name 与 value:name 别名是 value,所以 name 与 value 等价。在程序中,如果我们使用这个属性,就可以不在控制类的程序中使用访问时的参数,可以使用其别名。下面是一个控制类程序的实例,测试时可以使用前面的测试类进行测试。
@RequestMapping(value="/user",method=RequestMethod.GET)
public List<User> query3(@RequestParam(name="username") String myname){
//打印观察是否接收到username参数
System.out.println("username is :"+myname);
List<User> userList=new ArrayList();
//将list中添加了三个空的user对象,这里暂时没对user进行属性赋值
userList.add(new User());
userList.add(new User());
userList.add(new User());
return userList;
}
@RequestParam的不足
通过上面的实例,我们可以接收单个参数,但如果有多个参数怎么办?这明显不能满足需求,那么我们是否可以通过对象进行传递?这种情况下,控制类代码如下所示。
/**
* @Desciption 测试多参数传递
* @return
*/
@RequestMapping(value="/user",method=RequestMethod.GET)
public List<User> query4(UserQuery userQuery){
//通过反射的方式进行打印效果
System.out.println(ReflectionToStringBuilder.toString(userQuery, ToStringStyle.MULTI_LINE_STYLE));
System.out.println("username:"+userQuery.getUsername());
List<User> userList=new ArrayList();
//将list中添加了三个空的user对象,这里暂时没对user进行属性赋值
userList.add(new User());
userList.add(new User());
userList.add(new User());
return userList;
}
测试类代码如下所示。
/**
* @throws Exception
* @Desciption 主要用于测试多个参数的传递
*/
@Test
public void whenQuerySuccess3() throws Exception {
//发送请求
mockMvc.perform(MockMvcRequestBuilders.get("/user")
.param("username", "tom")
.param("age", "18")
.param("toAge","20")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk());
}
UserQuery.java 相关代码如下所示。
public class UserQuery {
private String username;
private int age;
private int ageTo;
private String address;
public String getUsername() {
return username;
}
//get and set
}
执行结果如图 3.6 所示。
在上面的程序中使用反射的方式输出了对象的属性,下面做一些分析。
ReflectionToStringBuilder 是 commons-lang 里的一个类,系统中一般都要打印日志,因为所有实体的 ToString 方法都用的是简单的 “”,而每 “” 一个就会新建一个 String 对象,这样如果系统内存小就会爆内存(前提是系统实体比较多)。使用 ToStringBuilder 可以避免爆内存这种问题。
上面的输出是多行的,因为使用了多行输出的模式 MULTI_LINE_STYLE。默认是单行模式 NO_FIELD_NAMES_STYLE。其原理就是通过 Java 的反射功能获取值,然后组成一个 Buffer。
注意:transient 和 static 修饰的属性不能显示出来,但父类的可以显示出来。
@PageableDefault
控制类代码如下所示。
@RequestMapping(value="/user",method=RequestMethod.GET)
public List<User> query5(UserQuery userQuery,@PageableDefault Pageable pageable){
// 通过反射的方式进行输出效果
System.out.println(ReflectionToStringBuilder.toString(userQuery, ToStringStyle.MULTI_LINE_STYLE));
//
System.out.println(pageable.getPageNumber());
System.out.println(pageable.getPageSize());
System.out.println(pageable.getSort());
//
List<User> userList = new ArrayList();
// 将list中添加了三个空的user对象,这里暂时没对user进行属性赋值
userList.add(new User());
userList.add(new User());
userList.add(new User());
return userList;
}
测试类代码如下所示。
/**
* @throws Exception
* @Desciption 主要用于测试PageableDefault
*/
@Test
public void whenQuerySuccess4() throws Exception {
//发送请求
mockMvc.perform(MockMvcRequestBuilders.get("/user")
.param("username", "tom")
//分页参数,查询第三页,每页15条数据,按照age降序
.param("page", "3")
.param("size", "15")
.param("sort", "age,desc")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk());
}
执行结果如图 3.7 所示。
查询用户详情
本小节在讲解查询用户详情时,需要结合 @PathVariable 注解。不过考虑到一些情况,也会讲解一下 @JsonView 注解。
@PathVariable
@PathVariable 是映射 URL 片段到 Java 的注解。这里的注解也是获取 URL 中的参数,那么它与 @RequestParam 注解有什么区别?
举例子说明,使用 @RequestParam 注解时,URL 是 http://host:port/path?参数名=参数值 ,这个注解获取的是这里的参数值;使用 @PathVariable 注解时,URL 是 http://host:port/path/参数值 。 控制类代码如下所示。
/**
* @Desciption 查询用户详情
*/
@RequestMapping(value="/user/{id}",method=RequestMethod.GET)
public User getInfo(@PathVariable(value="id") String idid) {
System.out.println("id is :"+idid);
User user=new User();
user.setUsername("tom");
return user;
}
测试类代码如下所示。
/**
* @throws Exception
* @Desciption 主要用于测试PageableDefault
*/
@Test
public void whenGetInfoSuccess() throws Exception {
//发送请求
mockMvc.perform(MockMvcRequestBuilders.get("/user/2")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.username"). value("tom"));
}
控制台内容如下所示。
id is :2
通过控制类,我们需要学会如何使用 @PathVariable 注解,通过注解可以获取路径中参数,这个参数是用户资源的一个查询条件,通过获取具体的条件就可以查询到具体的信息。上面的程序中没有使用数据库连接操作数据库,而是使用静态的方式模拟了这个效果。
@JsonView
@JsonView 是 Jackson Json 中的一个注解,Spring webmvc 也支持这个注解,它的作用就是控制输入/输出后的 Json。使用步骤如下。
-
使用接口声明多个视图。
-
在值对象的
GET方法上指定视图。 -
在
Controller方法上指定视图。
程序前两步代码如下所示。
public class User {
/*
创建两个接口,
一个为用户简单视图,即精简版的Json数据视图。
另一个为用户详细视图,即完整版的Json数据视图
*/
public interface UserSimpleView{};
public interface UserDetailView extends UserSimpleView{};
private String username;
private String password;
//在对象的GET方法上指定视图
@JsonView(UserSimpleView.class)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@JsonView(UserDetailView.class)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
程序第三步代码如下所示。
/**
* @Desciption 查询用户详情,测试JsonView的功能
*/
@RequestMapping(value="/user/{id:\\d+}")
@JsonView(User.UserSimpleView.class)
public User getInfo2(@PathVariable(value="id") String id) {
System.out.println("id is :"+id);
User user=new User();
user.setUsername("tom");
user.setPassword("123456");
return user;
}
测试类代码如下所示。
/**
* @Desciption 查询用户详情,测试JsonView的功能
*/
@RequestMapping(value="/user/{id:\\d+}")
@JsonView(User.UserSimpleView.class)
public User getInfo2(@PathVariable(value="id") String id) {
System.out.println("id is :"+id);
User user=new User();
user.setUsername("tom");
user.setPassword("123456");
return user;
}
执行结果如下所示。
id is :1
result: {"username":"tom"}
从结果上可以看出,虽然 user 对象中已经有了 password 字段的值,但是在返回的值中却没有这个字段,很明显 @JsonView 注解过滤了这个字段。
所以,在一些场景中,我们可以使用这个注解来过滤返回的字段。同时,在上文的控制类程序中我们可以看到 @RequestMapping(value="/user/{id:\\d+}"),这里是 URL 声明的正则表达式,当需要参数满足一定的要求时,可以考虑使用。