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。
对控制器进行测试有两种方式供我们选择:
- 使用模拟环境
- 使用嵌入式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供我们使用。