Spring Boot基础教程:构建spring-rest-curd + 单元测试

1. 简介

在学习了如何快速创建一个Spring Boot项目和简单使用Spring Boot搭建web项目,接下来要学习的是如何将在mvc模式中使用,本文完成了一个RESTful API + curd的简单例子,并使用TestRestTemplate来进行junit测试。

2. 环境

使用环境参考Spring Boot基础教程汇总中的讲解:详情请点击

3. 快速入门

项目结构

这里写图片描述
上图是Spring Boot推荐的典型结构,通常建议将应用的main类(Application)放到其他类所在的包的顶层(root package),并将@EnableAutoConfiguration注解到你的main类上,其作用是开启自动配置。

The second class-level annotation is @EnableAutoConfiguration. This annotation tells Spring Boot to “guess” how you will want to configure Spring, based on the jar dependencies that you have added. Since spring-boot-starter-web added Tomcat and Spring MVC,the auto-configuration will assume that you are developing a web application and setup Spring accordingly.Starters and Auto-Configuration.

简要概括,@EnableAutoConfiguration会根据添加的jar包对Spring进行相应的设置,比如你加载了spring-boot-starter-web,则会开启Spring Mvc的自动配置,在日志中,可以看到加载了tomcat和spring mvc。想了解更多关于@EnableAutoConfiguration注解,请看一下这篇文章:springboot EnableAutoConfiguration
采用root package的结构,就可以使用@ComponentScan而不需要指定basePackage属性,因为默认从Appliacation由上向下,逐层扫描加了注解的类。

引入依赖

无特别引入其他依赖,该介绍可参考文章创建第一个Spring Boot应用之Hello World中的相关说明。

代码详解

User实体定义:

public class User {

    private long id;
    private String name;
    private String country;

    public User(long id, String name, String country) {
        this.id = id;
        this.name = name;
        this.country = country;
    }

    public User() {

    }

    // 省略setter和getter 

}

UserService定义:实现简单的curd

@Service
public class UserServiceImpl implements UserService{

    private static List<User> users;

    static {
        users = initUser();
    }

    private static List<User> initUser() {
        List<User> users = new ArrayList<>(10);
        users.add(new User(1L,"Mac","USA"));
        users.add(new User(2L,"HuaWei","CHINA"));
        users.add(new User(3L,"3Idiots","INDIA"));
        users.add(new User(4L,"Cartoon","JAPAN"));
        users.add(new User(5L,"TUHao","DUBAI"));
        return users;
    }

    @Override
    public List<User> getUser() {
        return users;
    }

    @Override
    public User findUserById(Long id) {
       for(User user:users){
           if(user.getId() == id){
               return user;
           }
       }
       return null;
    }

    @Override
    public void createUser(User user) {
        users.add(user);
    }

    @Override
    public void update(User user) {
        int index = users.indexOf(user);
        users.add(index,user);
    }

    @Override
    public void deleteUserById(Long id) {
        Iterator<User> iterator = users.iterator();
        while (iterator.hasNext()){
            User user = iterator.next();
            if(user.getId() == id){
                iterator.remove();
            }
        }

    }
}

实现对User对象的rest

  • @Controller 类级别修饰符,修饰class,用来创建处理http请求
  • @ResponseBody 用来返回json对象
  • @RestController 相当于 @Controller + @ResponseBody的作用
  • @RequestMapping 配置url映射
  • @GetMapping 相当于@RequestMapping(method = HttpMethod.GET)
  • produces 返回的数据类型为 MediaType.APPLICATION_JSON_VALUE
  • headers 请求头为 Accept=application/json
@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
    public List<User> getUsers() {
        List<User> users = userService.getUser();
        return users;
    }

    @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<User> findUserById(@PathVariable("id") Long id) {
        User user = userService.findUserById(id);
        if (null == user) {
            return new ResponseEntity<>(user, HttpStatus.NOT_FOUND);
        }
        return new ResponseEntity<>(user, HttpStatus.OK);
    }

    @PostMapping(value = "/create",headers ="Accept=application/json")
    public ResponseEntity<User> createUser(@RequestBody User user) {
        userService.createUser(user);
        User reUser = userService.findUserById(user.getId());
        return new ResponseEntity<>(reUser, HttpStatus.CREATED);
    }

    @PutMapping(value = "/update",headers = "Accept=application/json")
    public ResponseEntity<String> update(@RequestBody User currentUser){
        User user = userService.findUserById(currentUser.getId());
        if(null == user){
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        user.setName(currentUser.getName());
        user.setCountry(currentUser.getCountry());
        userService.update(user);
        return new ResponseEntity<>(HttpStatus.OK);
    }

    @DeleteMapping(value = "/{id}")
    public ResponseEntity<String> deleteUserById(@PathVariable Long id){
        User user = userService.findUserById(id);
        if(null == user){
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        userService.deleteUserById(id);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}

编译运行

应用程序的入口main方法位于Application中。main方法通过调用run,将业务委托给了Spring Boot的SpringApplication类。我们需要将Example.class作为参数传递给run方法,以此决定谁是主要的Spring组件。
一般我们在启动类上添加这三个注解,

  • @Configuration 表明这是一个注解bean
  • @EnableAutoConfiguration 本文已经提及,故不解释
  • @ComponentScan 扫描注解

可以使用@SpringBootApplication来替换以上三个注解

//@Configuration
//@EnableAutoConfiguration
//@ComponentScan
@SpringBootApplication
public class SpringBootRestCurdApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootRestCurdApplication.class, args);
    }
}

在程序的根目录下(与pom.xml同级),执行

mvn spring-boot:run

以下情况表明启动成功
这里写图片描述
浏览器中使用url进行访问(不修改配置默认为8080端口,启动不成功检查下端口是否被占用)

http://localhost:8080/users/

单元测试

在集成测试中,可以使用TestRestTemplate来获取一个普通的或发送基本HTTP认证(使用用户名和密码)的模板:不允许重定向(这样才可以对相应地址进行断言),忽略cookies(模板本身就是无状态的),对于服务出错不会抛出异常。

如果使用了@SpringBootTest,且配置了WebEnvironment.RANDOM_PORTWebEnvironment.DEFINED_PORT属性,使用@LocalServerPort获取端口,使用@Autowired来加载TestRestTemplate发起REST调用,如下:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringBootRestCurdApplicationTests {

    /** 获取随机端口 */
    @LocalServerPort
    private int port;

    private URL base;

    @Autowired
    private TestRestTemplate restTemplate;

    @Before
    public void setUp() throws Exception {
        this.base = new URL("http://localhost:" + port + "/users/");
    }

    @Test
    public void testGetUsers() {

        // 获取json字符
        ResponseEntity<String> response = restTemplate.getForEntity(base.toString(), String.class);
        assertEquals(HttpStatus.OK,response.getStatusCode());
        ArrayList<User> users =
                JSONObject.parseObject(response.getBody(), new TypeReference<ArrayList<User>>() {});
        assertEquals(5,users.size());
        for (User user:users){
            System.out.println(user);
        }
    }

    @Test
    public void testFindUserById(){

        ResponseEntity<String> response = restTemplate.getForEntity(base + "1", String.class);
        assertEquals(HttpStatus.OK,response.getStatusCode());
        User user = JSON.parseObject(response.getBody(), User.class);
        assertEquals("1",user.getId());
        System.out.println(user);
    }

    @Test
    public void testCreateUser(){
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.ACCEPT,"application/json");
        User user = new User(1000L,"mcrwayfun","China");
        HttpEntity httpEntity = new HttpEntity(user,headers);

        ResponseEntity<String> response =
                restTemplate.exchange(base + "create", HttpMethod.POST, httpEntity, String.class);

        assertEquals(HttpStatus.CREATED,response.getStatusCode());

        User reUser = JSON.parseObject(response.getBody(), User.class);
        assertEquals(reUser.getName(),"mcrwayfun");
    }

    @Test
    public void testUpdateUser(){

        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.ACCEPT,"application/json");
        User user = new User(1L,"mcrwayfun","China");

        HttpEntity httpEntity = new HttpEntity(user,headers);
        // 执行修改操作
        ResponseEntity<String> response =
                restTemplate.exchange(base + "update", HttpMethod.PUT, httpEntity, String.class);

        assertEquals(HttpStatus.OK,response.getStatusCode());

        // 查询刚才修改的user
        // 结果为:user已修改
        response = restTemplate.getForEntity(base + "1", String.class);
        assertEquals(HttpStatus.OK,response.getStatusCode());
        user = JSON.parseObject(response.getBody(), User.class);
        assertEquals("mcrwayfun",user.getName());
    }

    @Test
    public void testDeleteUserById(){

        // 删除操作
        restTemplate.delete(base + "1",String.class);

        // 查询刚才修改的user
        // 结果为:不存在
        ResponseEntity<String> response = restTemplate.getForEntity(base + "1", String.class);
        assertEquals(HttpStatus.NOT_FOUND,response.getStatusCode());
    }

}

4. 总结

至此,我们通过引入了web模块,利用Spring Mvc的功能,实现了对一组User对象操作RESTful API,在此过程中,结合代码讲解了部分注解的作用,说明Spring Boot工程的结构,如何映射HTTP请求,传参以及利用TestRestTemplate编写单元测试。

遇到的问题

  1. 编写单元测试,验证CreateUser发送POST请求时,发生了如下错误

    com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
    Cannot construct instance of xxx(no Creators, like default construct, exist):
    cannot deserialize from Object value (no delegate- or property-based Creator)

    POST请求传参到Controller时,涉及反序列化。在反序列化User对象时,因为pojo实体没有提供默认构造函数,所以没有办法反序列化,故报错。

5. 附录

项目源码,欢迎star,本小节为spring-boot-rest-curdhttps://github.com/mcrwayfun/spring-boot-learning
springboot EnableAutoConfiguration:https://www.cnblogs.com/diegodu/p/7889379.html

猜你喜欢

转载自blog.csdn.net/qingtian_1993/article/details/80258762