查询用户以及用户详情
最简单的,也最常用的方式是 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
声明的正则表达式,当需要参数满足一定的要求时,可以考虑使用。