分类
spring-boot

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供我们使用。

分类
spring spring-boot

Spring Boot注解

1、概述

Spring Boot自动配置的特性使得Spring在配置上很简约。本文中,我们将围绕Spring中两个核心包org.springframework.boot.autoconfigure以及org.springframework.boot.autoconfigure.condition包中的注解展开介绍。

2、@SpringBootApplication

@SpringBootApplication是Spring Boot项目接触到的第一个注解,用与标识应用的启动主类。

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

从根本上讲@SpringBootApplication其实是@Configuration,@EnableAutoConfiguration和@ComponentScan三个注解的缩写,也可以认为@SpringBootApplication封装了上述三个注解。稍有些不同的是,@SpringBootApplication在封装@ComponentScan注解时,加入了一些默认的属性:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
		excludeFilters = {@ComponentScan.Filter(
				type = FilterType.CUSTOM,
				classes = {TypeExcludeFilter.class}
		), @ComponentScan.Filter(
				type = FilterType.CUSTOM,
				classes = {AutoConfigurationExcludeFilter.class}
		)}
)

也就是说在 Spring Boot项目中,完全可以使用上述注解代码段来代替@SpringBootApplication。当然了,实际的项目中肯定没有人这么做,试想谁又会舍近求远放着方便的不用,非要把简单的事情复杂化呢。

下面,让我们深入的了解一下Spring Boot的核心注解。

3、@EnableAutoConfiguration

@EnableAutoConfiguration的英文原意是:启用自动配置。该注解使得Spring Boot可以根据环境来自动检测并对项目进行一些自动化设置。比如当我们在pom.xml中同时引入Spring Data JPA以及H2数据库时,@EnableAutoConfiguration则会将Spring Data JPA的数据源自动的配置为H2。

需要注意的是该注解必须与@Configuration一起使用:

@Configuration
@EnableAutoConfiguration
class SpringBootAnnotationsConfig {}

4、条件注入(配置)

通常情况下,我们需要针对不同的环境(条件)来启用不同的配置,这一般被称为条件注入,可以借助本节中的条件注解来实现。

我们可以在使用@Configuration注解的类或使用@Bean注解的方法上放置条件注解,从而达到在特定的情况下使用特定的类或特定的方法的目的。在此,本文仅对其基本的使用方法进行介绍,预了解更多有关于本方面的知识,请访问此文

4.1. @ConditionalOnClass 以及@ConditionalOnMissingClass注解

@ConditionalOnClass表示:当某些类存在时,启用当前的配置;相反@ConditionalOnMissingClass表示:当某些类不存在时,启用当前配置。

@Configuration
@ConditionalOnClass(DataSource.class)
class MySQLAutoconfiguration {
    public MySQLAutoconfiguration() {
        System.out.println("DataSource.class存在")}
}

以下代码实现了:当DataSource.class存在时,Spring将使用该bean。

@Configuration
@ConditionalOnMissingClass("club.codedemo.springbootannotations.DataSource")
public class MySQLAutoconfiguration {
    public MySQLAutoconfiguration() {
        System.out.println("DataSource.class不存在“); }
}

以上代码实现了:当DataSource.class不存在时,Spring将使用该bean

4.2. @ConditionalOnBean以及@ConditionalOnMissingBean注解

除了可以基于类存在与否进行条件注入以外,还可以根据Bean是否存在来进行条件注入:

@Bean
@ConditionalOnBean(name = "dataSource")
LocalContainerEntityManagerFactoryBean entityManagerFactory() {
    // ...
}

以上代码实现了:当名称为dataSource的bean存在时,注入本方法返回的Bean。

@Bean
@ConditionalOnMissingBean(name = "dataSource")
LocalContainerEntityManagerFactoryBean entityManagerFactory() {
    // ...
}

以上代码实现了:当名称为dataSource的bean不存在时,注入本方法返回的Bean。

具体的验证代码请参阅本文提供的code demo。

4.3. @ConditionalOnProperty

使用@ConditionalOnProperty可以实现依据项目的属性值来进行注入。

@Bean
@ConditionalOnProperty(
    name = "usemysql", 
    havingValue = "local"
)
DataSource dataSource() {
    // ...
}

以上代码实现了:只有在项目的配置信息满足usemysql值为local时,注入该Bean。

4.4. @ConditionalOnResource

除根据项目的属性进行条件注入外,还可以根据项目资源文件夹中是否存在某配置文件来进行注入:

@Bean
//资源文件夹中存在mysql.properties文件时,该Bean生效
@ConditionalOnResource(resources = "classpath:mysql.properties")
Properties additionalProperties() {
    // ...
}
4.5.@ConditionalOnWebApplication以及@ConditionalOnNotWebApplication注解

@ConditionalOnWebApplication以及@ConditionalOnNotWebApplication注解可以基于当前应用程序是否为Web应用程序来进行配置。

@Bean
@ConditionalOnWebApplication
HealthCheckController healthCheckController() {
    // ...
}

如果当前应用属于Web应用,则上述Bean生效。

4.6. @ConditionalExpression

处理些稍复杂的注入需求,还可以使用@ConditionalExpression结合SpEL表达式来完成:

@Bean
@ConditionalOnExpression(${usemysql} &amp;&amp; ${mysqlserver == 'local'})
DataSource dataSource() {
    // ...
}

@ConditionalOnExpression注解接收的是SpEL表达式,当该表达式返回true时,该Bean生效;返回false,不生效。

4.7. @Conditional

对于更复杂的条件,还可以通过@Conditional结合创建自定义类的方式来实现:

@Conditional(CustomCondition.class)
Properties additionalProperties() {
    //...
}

5. 结论

本从由SpringBootApplication注解入手,对Spring Boot的条件配置进行了讲解。在实际的使用过程中,还需要根据项目的实际情况选择适合的技术。我们说适用的就是最好的,切不可在实际的项目为了实现技术而实现技术。

本文资源: