前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Security技术栈开发企业级认证与授权(二)使用Spring MVC开发RESTful API

Spring Security技术栈开发企业级认证与授权(二)使用Spring MVC开发RESTful API

作者头像
itlemon
发布2020-04-03 15:43:29
1K0
发布2020-04-03 15:43:29
举报
文章被收录于专栏:深入理解Java深入理解Java

RESTful一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。本篇博客主要讲述使用Spring MVC开发RESTful风格的API

一、传统API和RESTful API

传统的APIRESTful API如下表所示: | 行为 | 传统API | RESTful API | 方法 | | -------- | ------------- | ------------- | | 查询 | /user/query?name=lemon |/user?name=lemon | GET| | 详情 | /user/getInfo?id=1 |/user/1 | GET | | 创建 | /user/create?name=lemon |/user | POST | | 修改 | /user/update?id=1&name=tom|/user/1 | POST | | 删除 | /user/delete?id=1 |/user/1 | GET | RESTful风格的API有如下几个特点:

  • 使用URL描述资源
  • 使用HTTP方法描述行为,使用HTTP状态码来表示不同的结果
  • 使用JSON进行数据交互
  • RESTful只是一种风格,并不是一种强制的标准

二、常用注解介绍

这里介绍几个常用的注解:

  • @RestController标明此Controller提供RESTful API
  • @RequestMapping及其变体(@GetMappingPostMapping等),映射HTTP请求到Java方法
  • @RequestParam映射请求参数到Java方法的参数
  • @PathVariable映射URL片段到Java方法的参数
  • @PageableDefault指定默认分页参数
  • @JsonView按照指定方式序列化Java对象

代码案例:这里有UserUserController以及UserControllerTest三个类,其中UserControllerTest的四个测试方法分别对应UserController类中的四个方法。

  • User
代码语言:javascript
复制
package com.lemon.security.web.dto;

import lombok.Data;

/**
 * @author lemon
 * @date 2018/3/22 下午3:40
 */
@Data
public class User {

    private String username;

    private String password;

}
  • UserController
代码语言:javascript
复制
package com.lemon.security.web.controller;

import com.lemon.security.web.dto.User;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

/**
 * @author lemon
 * @date 2018/3/22 下午3:39
 */
@RestController
public class UserController {

    @RequestMapping(value = "/user1", method = RequestMethod.GET)
    public List<User> query1() {
        return generateUsers();
    }

    @GetMapping("/user2")
    public List<User> query2(@RequestParam String username) {
        System.out.println(username);
        return generateUsers();
    }

    @GetMapping("/user3/{username}")
    public List<User> query3(@PathVariable String username) {
        System.out.println(username);
        return generateUsers();
    }

    @GetMapping("/user4")
    public List<User> query4(@PageableDefault(page = 1, size = 2, sort = "username") Pageable pageable) {
        System.out.println(pageable.getPageNumber());
        System.out.println(pageable.getPageSize());
        System.out.println(pageable.getSort());
        return generateUsers();
    }

    private List<User> generateUsers() {
        List<User> users = new ArrayList<>();
        users.add(new User());
        users.add(new User());
        users.add(new User());
        return users;
    }
}
  • UserControllerTest
代码语言:javascript
复制
package com.lemon.security.web;

import com.lemon.security.web.application.MainApplication;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

/**
 * @author lemon
 * @date 2018/3/22 下午3:14
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MainApplication.class)
public class UserControllerTest {

    // 注入一个web应用环境(容器)
    @Autowired
    private WebApplicationContext webApplicationContext;

    // MVC环境对象
    private MockMvc mockMvc;

    @Before
    public void init() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    @Test
    public void query1() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/user1")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));
    }

    @Test
    public void query2() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/user2")
                .param("username", "lemon")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));
    }

    @Test
    public void query3() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/user3/lemon")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));
    }

    @Test
    public void query4() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/user4")
                .param("size", "3")
                .param("page", "1")
                .param("sort", "username,desc")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));
    }
}

第三个类,也就是UserControllerTestRESTful API的测试类,现在对其进行简单介绍: 由于RESSTful风格的API不能通过浏览器地址栏来进行测试,因为地址栏发送的请求都是GET类型的,而RESTful API正是通过请求方法来判断请求行为是查询、修改、删除、增加中的哪一种的,所以测试RESSTful风格的API都是通过编码来进行测试的。

  • 通过@Autowired WebApplicationContext webApplicationContext:注入web环境的ApplicationContext容器;
  • 然后通过MockMvcBuilders.webAppContextSetup(webApplicationContext).build()创建一个MockMvcMVC环境进行测试;
  • MockMvcRequestBuilders.get()方法是发送一个GET请求,param()是设置请求参数,contentType()是我设置内容类型(JSON格式),andExpect()方法是希望得到什么样的测试结果,MockMvcResultMatchers()是返回结果的匹配是否正确。jsonPath()方法是解析返回的JSON数据,关于它的介绍可以在github上找到。

运行上面的四个测试方法都可以通过测试。对于@PathVariable再写一个测试案例:

  • Controller方法:
代码语言:javascript
复制
@GetMapping("/getInfo/{id:\\d+}")
public User getInfo(@PathVariable String id) {
    System.out.println("查询的对象ID为:".concat(id));
    User user = new User();
    user.setUsername("lemon");
    return user;
}

上面的方法URL片段进行了正则表达式的验证,ID只能是数字。

  • 测试方法:
代码语言:javascript
复制
@Test
public void getInfo() throws Exception {
     mockMvc.perform(MockMvcRequestBuilders.get("/getInfo/1")
             .contentType(MediaType.APPLICATION_JSON_UTF8))
             .andExpect(MockMvcResultMatchers.status().isOk())
             .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("lemon"));
 }

接下来详细介绍@JsonView这个注解的使用。 @JsonView的使用步骤

  • 使用接口来声明多个视图
  • 在值对象的get方法上指定视图
  • Controller方法上指定视图

对于上面的步骤,进行如下解释如下: 一般对Java对象进行序列化Json的时候,会考虑到只序列化部分字段,那么就可以使用@JsonView这个注解。在这里使用User实体类进行举例,首先,在实体类上定义两个接口,第一个接口是简单视图(UserSimpleView),表示之序列化username这个字段,而第二个接口是详情视图(UserDetailView extends UserSimpleView),表示不仅序列化username字段,还序列化password字段。然后使用@JsonView注解将两个视图绑定到对应的字段的get方法上面,由于UserDetailView继承了UserSimpleView这个视图,所以在Controller方法上使用UserDetailView视图的时候,会同时序列化两个字段,而使用UserSimpleView的时候仅仅只会序列化username这一个字段。下面进行代码展示:

  • User类
代码语言:javascript
复制
package com.lemon.security.web.dto;

import com.fasterxml.jackson.annotation.JsonView;

/**
 * @author lemon
 * @date 2018/3/22 下午3:40
 */
public class User {

    public interface UserSimpleView {}

    public interface UserDetailView extends UserSimpleView {}

    private String username;

    private String password;

    @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;
    }
}
  • UserController的两个方法
代码语言:javascript
复制
@GetMapping("/getSimpleUser")
@JsonView(User.UserSimpleView.class)
public User getSimpleUser() {
    User user = new User();
    user.setUsername("lemon");
    user.setPassword("123456");
    return user;
}

@GetMapping("/getDetailUser")
@JsonView(User.UserDetailView.class)
public User getDetailUser() {
    User user = new User();
    user.setUsername("lemon");
    user.setPassword("123456");
    return user;
}

从上面的步骤分析可知,第一个方法返回的user对象在序列化为json的时候,只会序列化username字段,而第二个方法则会同时序列化两个字段。

  • 两个测试方法
代码语言:javascript
复制
@Test
public void getSimpleUser() throws Exception {
    String result = mockMvc.perform(MockMvcRequestBuilders.get("/getSimpleUser")
            .contentType(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andReturn().getResponse().getContentAsString();
    System.out.println(result);
}

@Test
public void getDetailUser() throws Exception {
    String result = mockMvc.perform(MockMvcRequestBuilders.get("/getDetailUser")
            .contentType(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andReturn().getResponse().getContentAsString();
    System.out.println(result);
}

两个方法打印的结果分别为:

代码语言:javascript
复制
{"username":"lemon"}
{"username":"lemon","password":"123456"}

三、编写RESTful API

1、用户详情请求(GET)

对于RESTful API,一般都不再使用传统的参数传递,而是使用资源映射的方式,也就是使用@PathVariable,为了保持文档的完整性,这里再次使用上面已经举过的案例:

  • Controller方法:
代码语言:javascript
复制
@GetMapping("/getInfo/{id:\\d+}")
public User getInfo(@PathVariable String id) {
    System.out.println("查询的对象ID为:".concat(id));
    User user = new User();
    user.setUsername("lemon");
    return user;
}

上面的方法URL片段进行了正则表达式的验证,ID只能是数字。

  • 测试方法:
代码语言:javascript
复制
@Test
public void getInfo() throws Exception {
     mockMvc.perform(MockMvcRequestBuilders.get("/getInfo/1")
             .contentType(MediaType.APPLICATION_JSON_UTF8))
             .andExpect(MockMvcResultMatchers.status().isOk())
             .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("lemon"));
 }
2、用户创建请求(POST)

这里主要介绍三个知识点:

  • @RequestBody映射请求体到Java方法参数
  • @Valid注解和BindingResult验证请求参数的合法性并处理校验结果
  • @RequestBody是将前台传递过来的JSON字符串转换成Java对象,
1)第一个知识点的案例,将JSON字符串映射到Java对象中

在之前的User类上加上一个id字段,然后进行下面的测试。 Controller方法:用户创建的方法

代码语言:javascript
复制
@PostMapping("/user1")
public User create1(@RequestBody User user) {
    System.out.println(ReflectionToStringBuilder.reflectionToString(user, ToStringStyle.MULTI_LINE_STYLE));
    user.setId(1);
    return user;
}

测试方法:测试创建用户方法

代码语言:javascript
复制
@Test
public void create1() throws Exception {
    String content = "{\"username\":\"lemon\",\"password\":\"123456\"}";
    mockMvc.perform(MockMvcRequestBuilders.post("/user1")
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(content))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1));
}

测试方法传递过来的数据是一个JSON字符串,正是@RequestBody注解将JSON字符串转化成为Java对象。

2)第二个知识点的案例,@Valid注解和BindingResult验证请求参数的合法性并处理校验结果

当使用Java类来接受参数的是,往往需要对参数进行校验,而校验一般都是使用Hibernate提供的校验器来进行校验,在Java实体类的字段上,我们常常加上@NotBlank@NotNull@Null@Min@Max@NotEmpty等注解进行校验规则定义,然后在Controller方法参数前加上@Valid注解来进行校验,校验的错误结果存储在BindingResult对象内。这里我向后台传递一个JSON字符串,人为使得usernamepassword两个字段为null。这里仅仅简单介绍表单验证的注解,下一篇博客将重点介绍。接下来请看案例:

  • User类字段
代码语言:javascript
复制
private Integer id;

@NotEmpty(message = "用户名不能为空")
private String username;

@NotEmpty(message = "密码不能为空")
private String password;

private Date birthday;
  • UserController的create2()方法
代码语言:javascript
复制
@PostMapping("/user2")
public User create2(@Valid @RequestBody User user, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        bindingResult.getAllErrors().forEach(error -> System.out.println(error.getDefaultMessage()));
    }
    System.out.println(ReflectionToStringBuilder.reflectionToString(user, ToStringStyle.MULTI_LINE_STYLE));
    user.setId(2);
    return user;
}
  • 测试方法
代码语言:javascript
复制
@Test
public void create2() throws Exception {
    String content = "{\"username\":null,\"password\":null}";
    mockMvc.perform(MockMvcRequestBuilders.post("/user2")
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(content))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(2));
}

运行结果为:

代码语言:javascript
复制
用户名不能为空
密码不能为空
com.lemon.security.web.dto.User@58d79479[
  id=<null>
  username=<null>
  password=<null>
  birthday=<null>
]
3、用户修改和删除请求(PUT、DELETE)

由于RESTful风格的API是基于方法来进行区分的,所以设计到数据的修改和删除使用的方法是PUTDELETE,接下来使用案例的方式介绍修改和删除API的开发。

  • 测试方法:
代码语言:javascript
复制
@Test
public void update() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.put("/user/1")
            .contentType(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("lemon"));
}

@Test
public void delete() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.delete("/user/1")
            .contentType(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(MockMvcResultMatchers.status().isOk());
}
  • Controller方法:
代码语言:javascript
复制
@PutMapping("/user/{id:\\d+}")
public User update(@PathVariable Integer id) {
    User user = new User();
    user.setId(id);
    System.out.println("模拟修改");
    user.setUsername("lemon");
    return user;
}

@DeleteMapping("/user/{id:\\d+}")
public void delete(@PathVariable Integer id) {
    System.out.println("模拟修改,修改ID:".concat(String.valueOf(id)));
}

回顾一下RESTful风格的API,都是使用URL描述资源,使用请求方法来区别不同的API。这极大程度地简化了API开发的流程,推荐使用。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-03-22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、传统API和RESTful API
  • 二、常用注解介绍
  • 三、编写RESTful API
    • 1、用户详情请求(GET)
      • 2、用户创建请求(POST)
        • 3、用户修改和删除请求(PUT、DELETE)
        相关产品与服务
        文件存储
        文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档