分类
spring-boot

Spring Boot Starters简介

Intro to Spring Boot Starters

1. 概述

在复杂项中,依赖管理显得非常的关键。手动管理依赖往往并不理想,项目越大依赖往往越多,依赖越多,产生交差依赖的可能性就越大。我们往往不得不在项目的依赖管理上花费大量的时间与精力。当然了,在依赖管理上花费的时间越多,也同时意味着在其它的方面所花的时间就越少。

Spring Boot Starters旨在解决上述问题。Spring Boot提供了超过30个Starters来解决自动依赖的问题。以下将展示较常见的几个。

2. Web Starter

通常在开发一个REST服务时,我们需要添加诸如:Spring MVC,Tomcat以及Jackson等等多种依赖。

而Spring Boot Starters可以简化这一切 ---- 添加一个starter:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

然后,便可以创建REST控制器了(为了简单起见,我们将不使用数据库,而是专注于REST控制器):

@RestController
@RequestMapping("student")
public class StudentController {
    private List<Student> students = new ArrayList<>();

    @GetMapping
    public List<Student> all() {
        return this.students;
    }

    @PostMapping
    public void add(@RequestBody Student student) {
        this.students.add(student);
    }

    @GetMapping("{id}")
    public Student findById(@PathVariable Long id) {
        return this.students.stream()
                .filter(student -> student.getId().equals(id))
                .findFirst().get();
    }
}

Student是一个简单的bean,其id为Long类型,name为String类型。

public class Student {
    private Long id;
    private String name;

    public Student(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

运行应用后,便可以访问:http://localhost:8080/student/来查看控制器是否正常工作了。

如此,通过引用唯一的依赖spring-boot-starter-web,我们轻松的创建了一个具有最小配置的REST应用程序。

3. Test Starter

启用项目单元测试时,我们通常需要使用以下一组库:Spring Test、JUnit、Hamcrest以及Mockito。我们当然可以手动来包含这些库并指名其版本号,但使用Spring Boot starter会显得更加简单:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

注意:你无需指定artifact的版本号。Spring Boot将确定要使用的版本 ---- 根据pom.xml中的spring-boot-starter-parent artifact版本可。如果后期需要升级Spring Boot库以及依赖,在使用Spring Boot Starter的基础上,只需要升级spring-boot-starter-parent的版本的即可,至于其它库的版本将会由Spring Boot Starter自动处理。

下面,让我们使用刚刚引用的单元测试来测试一下StudentController。

对控制器进行测试有两种方式供我们选择:

  1. 使用模拟环境
  2. 使用嵌入式Servlet容器(例如Tomcat或Jetty)

在此示例中,我们将使用模拟环境

package club.codedemo.springbootstarters.controller;

import club.codedemo.springbootstarters.entity.Student;
import org.hamcrest.Matchers;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

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

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@AutoConfigureMockMvc
class StudentControllerTest {
    @SpyBean
    StudentController studentController;

    @Autowired
    MockMvc mockMvc;

    @BeforeEach
    void beforeEach() {
        this.studentController.students =
                new ArrayList<>();
        this.studentController.students.add(new Student(1L, "zhangsan"));
        this.studentController.students.add(new Student(2L, "lisi"));
    }

    @Test
    void all() throws Exception {
        this.mockMvc.perform(MockMvcRequestBuilders.get("/student/"))
                .andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(2)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[0].id").value(1))
                .andExpect(MockMvcResultMatchers.jsonPath("$[0].name").value("zhangsan"))
                .andExpect(MockMvcResultMatchers.jsonPath("$[1].id").value(2))
                .andExpect(MockMvcResultMatchers.jsonPath("$[1].name").value("lisi"))
        ;
    }

    @Test
    void add() throws Exception {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("id", "3");
        jsonObject.put("name", "wangwu");

        this.mockMvc.perform(
                MockMvcRequestBuilders.post("/student/")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(jsonObject.toString()))
                .andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.status().isCreated());

        Assertions.assertEquals(3, this.studentController.students.size());
    }

    @Test
    void findById() throws Exception {
        this.mockMvc.perform(
                MockMvcRequestBuilders.get("/student/1"))
                .andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1))
                .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("zhangsan"))
        ;
    }
}

上述代码分别对all、save以及findById方法进行了测试。其中有属于spring-test 模块@AutoConfigureMockMvc等注解;有属于Hamcrest的hasSize()匹配器;有属于JUnit的而@BeforeEach注解以及属于mockito的SpyBean注解。重要的是:这些依赖都是test starter帮我们自动引入的。

4. Data JPA Starter

大多数的应用程序都依赖于数据库,Spring Boot提供的Data JPA Starter能够快速的完成对数据库的依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

值得一提的是:在引入Data JPA Starter以后我们必须为其提供一个可用的数据库。Spring Boot 可以零配置的支持H2, Derby 以及 Hsqldb数据库,比如我们在项目中添加h2数据库:

<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<scope>runtime</scope>
</dependency>

然后定义一个实体:

@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    public Student() {
    }

一个数据仓库:

public interface StudentRepository extends JpaRepository<Student, Long> {
}

最后进行单元测试:

@DataJpaTest
class StudentRepositoryTest {

    @Autowired
    StudentRepository studentRepository;

    @Test
    void saveAndFind() {
        Student student = new Student();
        student.setName("zhangsan");
        this.studentRepository.save(student);
        assertNotNull(student.getId());

        student = this.studentRepository.findById(student.getId()).get();
        assertEquals("zhangsan",
                student.getName());
    }
}

如你所见,我们引用h2数据库后并没有进行任何配置,Data JPA Starter在检测到H2数据库后,自动的完成了这一切。

5. 自定义配置

Spring Boot Starters在提供了便利性的同时,并没有抹杀用户的自定义配置。比如我们可以如下启用h2控制台并修改系统默认生成的数据库实例名称:

# 启用h2控制台
spring.h2.console.enabled=true

# 将H2数据库名称变更为testdb
spring.datasource.url=jdbc:h2:~/testdb

此时启动应用后便可以访问http://localhost:8080/h2-console来打开H2数据库的登录界面,并将JDBC URL一项更改为jdbc:h2:~/testdb来查看数据库信息了。

6. 总结

本文中我们对Spring Boot 中的Starter进行了简单的介绍。在Spring Boot Starter的帮助下,我们能够:

  • 瘦身pom文件,使其更易读,更易维护。
  • 一站式的配置好开发、测试、生产所需要的依赖。
  • 规避依赖冲突、循环依赖等问题,大幅缩短项目配置时间。

Spring Boot除提供本文提供的Web Starter、Test Starter以及Data JPA Starter以外,还提示了一系列的Starter供我们使用。