04.请求与响应

请求与响应

完整示例

在构建Spring MVC项目时,我们会依赖于以下注解:

  • @Controller:修饰class,用来创建处理http请求的对象
  • @RestController:Spring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回json格式
  • @RequestMapping:配置url映射。现在更多的也会直接用以Http Method直接关联的映射注解来定义,比如:GetMapping、PostMapping、DeleteMapping、PutMapping

下面我们通过使用Spring MVC来实现一组对User对象操作的RESTful API,配合注释详细说明在Spring MVC中如何映射HTTP请求、如何传参、如何编写单元测试。

@Data
public class User {

    private Long id;
    private String name;
    private Integer age;

}

然后实现对User对象的操作接口:

import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RestController
@RequestMapping(value = "/users")     // 通过这里配置使下面的映射都在/users下
public class UserController {

    // 创建线程安全的Map,模拟users信息的存储
    static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());

    /**
     * 处理"/users/"的GET请求,用来获取用户列表
     *
     * @return
     */
    @GetMapping("/")
    public List<User> getUserList() {
        // 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递
        List<User> r = new ArrayList<User>(users.values());
        return r;
    }

    /**
     * 处理"/users/"的POST请求,用来创建User
     *
     * @param user
     * @return
     */
    @PostMapping("/")
    public String postUser(@RequestBody User user) {
        // @RequestBody注解用来绑定通过http请求中application/json类型上传的数据
        users.put(user.getId(), user);
        return "success";
    }

    /**
     * 处理"/users/{id}"的GET请求,用来获取url中id值的User信息
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        // url中的id可通过@PathVariable绑定到函数的参数中
        return users.get(id);
    }

    /**
     * 处理"/users/{id}"的PUT请求,用来更新User信息
     *
     * @param id
     * @param user
     * @return
     */
    @PutMapping("/{id}")
    public String putUser(@PathVariable Long id, @RequestBody User user) {
        User u = users.get(id);
        u.setName(user.getName());
        u.setAge(user.getAge());
        users.put(id, u);
        return "success";
    }

    /**
     * 处理"/users/{id}"的DELETE请求,用来删除User
     *
     * @param id
     * @return
     */
    @DeleteMapping("/{id}")
    public String deleteUser(@PathVariable Long id) {
        users.remove(id);
        return "success";
    }

}

下面针对该Controller编写测试用例验证正确性,具体如下。当然也可以通过浏览器插件等进行请求提交验证。

@RunWith(SpringRunner.class)
@SpringBootTest
public class Chapter21ApplicationTests {

    private MockMvc mvc;

    @Before
    public void setUp() {
        mvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
    }

    @Test
    public void testUserController() throws Exception {
        // 测试UserController
        RequestBuilder request;

        // 1、get查一下user列表,应该为空
        request = get("/users/");
        mvc.perform(request)
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("[]")));

        // 2、post提交一个user
        request = post("/users/")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"id\":1,\"name\":\"测试大师\",\"age\":20}");
        mvc.perform(request)
                .andExpect(content().string(equalTo("success")));

        // 3、get获取user列表,应该有刚才插入的数据
        request = get("/users/");
        mvc.perform(request)
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"测试大师\",\"age\":20}]")));

        // 4、put修改id为1的user
        request = put("/users/1")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"name\":\"测试终极大师\",\"age\":30}");
        mvc.perform(request)
                .andExpect(content().string(equalTo("success")));

        // 5、get一个id为1的user
        request = get("/users/1");
        mvc.perform(request)
                .andExpect(content().string(equalTo("{\"id\":1,\"name\":\"测试终极大师\",\"age\":30}")));

        // 6、del删除id为1的user
        request = delete("/users/1");
        mvc.perform(request)
                .andExpect(content().string(equalTo("success")));

        // 7、get查一下user列表,应该为空
        request = get("/users/");
        mvc.perform(request)
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("[]")));

    }

}

常用注解

@RestController

@RestControllerSpring Boot新增的一个注解,我们看一下该注解都包含了哪些东西。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    String value() default "";
}

可以看出, @RestController注解包含了原来的@Controller@ResponseBody注解,使用过Spring的朋友对@Controller注解已经非常了解了,这里不再赘述, @ResponseBody注解是将返回的数据结构转换为Json格式。所以@RestController可以看作是@Controller@ResponseBody的结合体,相当于偷个懒,我们使用@RestController之后就不用再使用@Controller了。但是需要注意一个问题:如果是前后端分离,不用模板渲染的话,比如Thymeleaf,这种情况下是可以直接使用@RestController将数据以json格式传给前端,前端拿到之后解析;但如果不是前后端分离,需要使用模板来渲染的话,一般Controller中都会返回到具体的页面,那么此时就不能使用@RestController了,比如:

public String getUser() {
	return "user";
}

其实是需要返回到user.html页面的,如果使用@RestController的话,会将user作为字符串返回的,所以这时候我们需要使用@Controller注解。

@RequestMapping

@RequestMapping是一个用来处理请求地址映射的注解,它可以用于类上,也可以用于方法上。在类的级别上的注解会将一个特定请求或者请求模式映射到一个控制器之上,表示类中的所有响应请求的方法都是以该地址作为父路径;在方法的级别表示进一步指定到处理方法的映射关系。该注解有6个属性,一般在项目中比较常用的有三个属性:value、methodproduces

  • value属性:指定请求的实际地址,value可以省略不写
  • method属性:指定请求的类型,主要有GET、PUT、POST、DELETE,默认为GET
  • produces属性:指定返回内容类型,如produces = “application/json; charset=UTF-8”

@RequestMapping注解比较简单,举个例子:

@RestController
@RequestMapping(value = "/test", produces = "application/json; charset=UTF-8")
public class TestController {
    @RequestMapping(value = "/get", method = RequestMethod.GET)
    public String testGet() {
        return "success";
    }
}

这个很简单,启动项目在浏览器中输入localhost:8080/test/get测试一下即可。针对四种不同的请求方式,是有相应注解的,不用每次在@RequestMapping注解中加method属性来指定,上面的GET方式请求可以直接使用@GetMapping("/get")注解,效果一样。相应地,PUT方式、POST方式和DELETE方式对应的注解分别为@PutMapping@PostMappingDeleteMapping

@PathVariable

@PathVariable注解主要是用来获取url参数,Spring Boot支持restfull风格的url,比如一个GET请求携带一个参数id过来,我们将id作为参数接收,可以使用@PathVariable注解。如下:


@GetMapping("/user/{id}")
public String testPathVariable(@PathVariable Integer id) {
	System.out.println("获取到的id为:" + id);
	return "success";
}

这里需要注意一个问题,如果想要url中占位符中的id值直接赋值到参数id中,需要保证url中的参数和方法接收参数一致,否则就无法接收。如果不一致的话,其实也可以解决,需要用@PathVariable中的value属性来指定对应关系。如下:


@RequestMapping("/user/{idd}")
public String testPathVariable(@PathVariable(value = "idd") Integer id) {
	System.out.println("获取到的id为:" + id);
	return "success";
}

对于访问的url,占位符的位置可以在任何位置,不一定非要在最后,比如这样也行:/xxx/{id}/user。另外,url也支持多个占位符,方法参数使用同样数量的参数来接收,原理和一个参数是一样的,例如:


@GetMapping("/user/{idd}/{name}")
    public String testPathVariable(@PathVariable(value = "idd") Integer id, @PathVariable String name) {
        System.out.println("获取到的id为:" + id);
        System.out.println("获取到的name为:" + name);
        return "success";
    }

运行项目,在浏览器中请求localhost:8080/test/user/2/zhangsan可以看到控制台输出如下信息:

获取到的id为:2
获取到的name为:zhangsan

所以支持多个参数的接收。同样地,如果url中的参数和方法中的参数名称不同的话,也需要使用value属性来绑定两个参数。

@RequestParam

@RequestParam注解顾名思义,也是获取请求参数的,上面我们介绍了@PathValiable注解也是获取请求参数的,那么@RequestParam@PathVariable有什么不同呢?主要区别在于:@PathValiable是从url模板中获取参数值, 即这种风格的url:http://localhost:8080/user/{id} ;而@RequestParam是从request里面获取参数值,即这种风格的url:http://localhost:8080/user?id=1 。我们使用该url带上参数id来测试一下如下代码:

@GetMapping("/user")
public String testRequestParam(@RequestParam Integer id) {
	System.out.println("获取到的id为:" + id);
	return "success";
}

可以正常从控制台打印出id信息。同样地,url上面的参数和方法的参数需要一致,如果不一致,也需要使用value属性来说明,比如url为:http://localhost:8080/user?idd=1:


@RequestMapping("/user")
public String testRequestParam(@RequestParam(value = "idd", required = false) Integer id) {
	System.out.println("获取到的id为:" + id);
	return "success";
}

除了value属性外,还有个两个属性比较常用:

  • required属性:true表示该参数必须要传,否则就会报404错误,false表示可有可无。
  • defaultValue属性:默认值,表示如果请求中没有同名参数时的默认值。

url中可以看出,@RequestParam注解用于GET请求上时,接收拼接在url中的参数。除此之外,该注解还可以用于POST请求,接收前端表单提交的参数,假如前端通过表单提交usernamepassword两个参数,那我们可以使用@RequestParam来接收,用法和上面一样。

@PostMapping("/form1")
public String testForm(@RequestParam String username, @RequestParam String password) {
    System.out.println("获取到的username为:" + username);
    System.out.println("获取到的password为:" + password);
    return "success";
}

如果表单数据很多,我们不可能在后台方法中写上很多参数,每个参数还要@RequestParam注解。针对这种情况,我们需要封装一个实体类来接收这些参数,实体中的属性名和表单中的参数名一致即可。

public class User {
	private String username;
	private String password;
	// set get
}

使用实体接收的话,我们不能在前面加@RequestParam注解了,直接使用即可。


@PostMapping("/form2")
public String testForm(User user) {
    System.out.println("获取到的username为:" + user.getUsername());
    System.out.println("获取到的password为:" + user.getPassword());
    return "success";
}

@RequestBody

@RequestBody注解用于接收前端传来的实体,接收参数也是对应的实体,比如前端通过json提交传来两个参数usernamepassword,此时我们需要在后端封装一个实体来接收。在传递的参数比较多的情况下,使用@RequestBody接收会非常方便。例如:

public class User {
	private String username;
	private String password;
	// set get
}

@PostMapping("/user")
public String testRequestBody(@RequestBody User user) {
	System.out.println("获取到的username为:" + user.getUsername());
	System.out.println("获取到的password为:" + user.getPassword());
	return "success";
}