Spring Boot API 服务测试指南

凯文86 等级 302 0 0

Spring Boot 除了简化了 Spring 应用的开发,同时也简化了 Spring 应用的测试。它内置支持各种常用测试工具,包括 Spring Test、JUnit、TestNG、Mockito、AssertJ 等。本文将讲解如何编写单元测试和集成测试来保障 Spring Boot API 应用不同层级代码的质量,其中会涉及到使用嵌入式的 H2 数据库来测试 Repository,通过 Mock 依赖接口来对用例进行单元测试,以及对应用整体进行 API 集成测试。

Spring Boot 测试支持

自动化测试的重要性不言而喻,尤其是对于业务复杂的企业应用,单靠人工测试很难保障对业务逻辑的全覆盖。如果项目采用敏捷开发,那么人工测试的工作量将非常大。Spring Boot 自然不会忽视对应用测试的支持,与应用开发一样,只需引入少量依赖,即可自动开启相关测试工具的支持。

Spring Boot 提供了 spring-boot-testspring-boot-test-autoconfigure 两个测试相关的模块,前者提供了核心组件,后者提供了自动配置。只需引入 spring-boot-starter-test 这个 Starter,即可自动引入这两个模块,以及 JUnit 5(Jupiter)、AssertJ、Hamcrest、Mockito、JSONassert、JsonPath 等其它常用的测试工具。

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency> 

上面的 exclusions 里排除了对 JUnit 4 的支持,如果你的项目里需要使用 JUnit 4,那么可以删除。

接下来将分别讲解如何测试仓库、用例和 API 等应用各层级的代码,完整代码可从 GitHub 获取 Spring Boot in Practice

测试仓库(Repository)

在我们的 API 服务里,仓库就是 DAO(数据访问对象)。如果仓库只是进行简单的增删改查操作,那么测试的必要性不大,但如果里面使用了复杂的自定义 SQL,那还是有必要对其进行测试的。测试仓库依赖数据库服务,数据库服务不好 Mock,并且 Mock 的意义也不大。但如果直接使用外部的数据库服务,测试又会变得很重。因此可以采取折中的方式,使用一个内嵌的内存数据库服务,比如 H2。它支持标准 SQL,资源消耗少,启动和销毁都非常快。

首先需要引入 H2 数据库依赖。

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

然后准备 H2 数据库的迁移脚本 db/migration/h2/V1__Initial_create_tables.sql。因为 MySQL 对标准 SQL 进行了许多扩展,因此无法直接使用 MySQL 的迁移脚本来迁移 H2 数据库。Flyway 会自动根据当前连接的数据类型(vendor)来到对应的目录中去寻找迁移脚本。有关迁移脚本的具体内容这里就不展开了,无非就是按照标准 SQL 语法把 MySQL 的迁移脚本修改一下。运行测试用例之前,Flyway 会自动完成数据库的迁移,创建好相关表。

最后来编写测试用例。

package net.jaggerwang.sbip.adapter.repository;

...

@DataJpaTest
@ContextConfiguration(classes = {CommonConfig.class, JpaConfig.class})
@EnabledIfSystemProperty(named = "test.repository.enabled", matches = "true")
public class UserRepositoryTests {
    @Autowired
    private JPAQueryFactory jpaQueryFactory;

    @Autowired
    private UserJpaRepository userJpaRepository;

    @Autowired
    private UserFollowJpaRepository userFollowJpaRepository;

    private UserRepository userRepository;

    @BeforeEach
    void setUp() {
        userRepository =
                new UserRepositoryImpl(jpaQueryFactory, userJpaRepository, userFollowJpaRepository);
    }

    @Test
    void save() {
        var userEntity = UserEntity.builder().username("jaggerwang").password("123456").build();
        var savedUser = userRepository.save(userEntity);
        assertThat(savedUser).hasFieldOrPropertyWithValue("username", userEntity.getUsername())
                .hasFieldOrPropertyWithValue("password", userEntity.getPassword());
    }
} 

其中需要注意的点如下:

  • 最重要的是使用 @DataJpaTest 来注解测试类,这个注解会启用 JPA 相关的自动配置,其它的自动配置不会启用,以免影响测试性能。
  • 默认会从当前包开始往上搜寻 @SpringBootConfiguration 注解的应用配置,以便从中找到应用里的 JPA Entities 和 Repositories,也可以使用 @ContextConfiguration 注解来显示指定应用配置。
  • @EnabledIfSystemProperty 注解用来控制是否启用该测试,以方便外部通过系统属性来开启或关闭该测试。
  • @DataJpaTest 不会自动配置组件扫描,因此需要自己创建要被测试的 UserRepositoryImpl 对象,它是底层 JPA Repository 的包装,实现了用例层里定义的接口 UserRepository
  • 被测试方法返回的结果使用 AssertJ 提供的 assertThat() 方法转化成了 ObjectAssert 对象,以方便验证结果对象的属性及其值是否符合预期。

测试用例(Usecase)

由于项目采用了 干净架构,用例层的所有外部依赖都抽象成为了接口,因此单元测试变得非常容易。单元测试不依赖 Spring Boot 提供的任何功能,包括自动配置和 ApplicationContext 等,就是标准的 JUnit 测试用例。不过需要提供依赖接口的模拟实现,借助于 Mockito 可以很容易地办到。

package net.jaggerwang.sbip.usecase;

...

@ExtendWith(SpringExtension.class)
@EnabledIfSystemProperty(named = "test.usecase.enabled", matches = "true")
public class UserUsecaseTests {
    private UserUsecase userUsecase;

    @MockBean
    private UserRepository userRepository;

    @MockBean
    private RoleRepository roleRepository;

    @MockBean
    private RandomGenerator randomGenerator;

    @MockBean
    private PasswordEncoder passwordEncoder;

    @BeforeEach
    void setUp() {
        userUsecase = new UserUsecase(userRepository, roleRepository, randomGenerator,
                passwordEncoder);
    }

    @Test
    void register() {
        given(passwordEncoder.encode(anyString())).will((invocation) -> invocation.getArgument(0));

        var userEntity = UserEntity.builder().username("jaggerwang").password("123456").build();
        given(userRepository.findByUsername(userEntity.getUsername())).willReturn(Optional.empty());

        var savedUser = UserEntity.builder().username(userEntity.getUsername())
                .password(passwordEncoder.encode(userEntity.getPassword())).build();
        given(userRepository.save(any(UserEntity.class))).willReturn(savedUser);

        var registeredUser = userUsecase.register(userEntity);
        assertThat(registeredUser).hasFieldOrPropertyWithValue("username", userEntity.getUsername())
                .hasFieldOrPropertyWithValue("password",
                        passwordEncoder.encode(userEntity.getPassword()));
    }
} 

来看一下其中重要的关注点:

  • 测试类使用了 @ExtendWith(SpringExtension.class) 来注解,以便将 Spring TestContext 框架集成到 JUnit 的模型里,这样就可以在 JUnit 的测试用例里使用 @MockBean 这样的由 Spring TestContext 提供的注解。
  • 通过 @MockBean 注解来自动提供实现了某个接口的模拟对象,包括 UserRepositoryRandomGeneratorPasswordEncoder 等接口,模拟对象里的所有方法默认都是空的。
  • register() 测试用例里,首先需要提供被测试的 UserUsecases.register() 方法里所调用的 PasswordEncoder.encode()UserRepository.findByUsername()UserRepository.save() 等方法的模拟实现,也就是给每个方法指定一组或多组输入及其对应的输出。

测试 API

为了更接近于真实的运行环境,API 测试采用集成测试,使用真实的 MySQL 和 Redis 等外部依赖服务。相比于单元测试,集成测试的运行时间更长,但更能反映真实情况。实际测试中可以结合两者,单元测试可以在每次提交代码时都运行,而集成测试可以每小时或每天运行一次。

初始化和清理数据库

测试有访问数据库的代码需要保证每个测试用例都运行在一个干净的环境中,互不干扰。在每个测试用例执行之前需要初始化数据库,包括创建相关表,插入测试数据等。在执行之后还需要清理掉本次测试产生的数据,可以直接删除表,或者删除测试数据。我们的 API 测试需要模拟用户登录,因此需要准备一个用来登录的用户。

首先编写一个 init-db-test.sql 文件来初始化数据库。

INSERT INTO `user` (`id`, `username`, `password`) VALUES (1, 'jaggerwang', '$2a$10$UOCgLxghU78h4UvlZcjvIup9YrETv6tGmRjPPpMTQ.EjSRUsJzJJS'); 

这里只是插入了一条用户数据,表的创建交给了 Flyway,它会在启动测试时自动执行数据库迁移任务来创建,类似于应用启动时所做的工作。

然后编写一个 clean-db-test.sql 文件来清理测试数据。

DELETE FROM `user`; 

这里只需清理初始化脚本里插入的数据。执行每个测试时默认会开启一个事务,在该事务中产生的数据,在测试用例结束时会自动回滚掉,因此无需清理。

测试 REST API

集成测试要比单元测试做更多的准备工作,每个集成测试用例都可以理解为是一个完整的应用。

package net.jaggerwang.sbip.api;

...

@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
@Sql({"/db/init-db-test.sql"})
@Sql(scripts = {"/db/clean-db-test.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
@EnabledIfSystemProperty(named = "test.api.enabled", matches = "true")
public class RestApiTests {
    @Autowired
    private MockMvc mvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void login() throws Exception {
        var userEntity = UserEntity.builder().username("jaggerwang").password("123456").build();
        mvc.perform(post("/auth/login").contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(userEntity))).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.code").value("ok"))
                .andExpect(jsonPath("$.data.user.username").value(userEntity.getUsername()));
    }

    @WithUserDetails("jaggerwang")
    @Test
    void logout() throws Exception {
        mvc.perform(get("/auth/logout")).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.code").value("ok"))
                .andExpect(jsonPath("$.data.user.username").value("jaggerwang"));
    }

    @WithUserDetails("jaggerwang")
    @Test
    void logged() throws Exception {
        mvc.perform(get("/auth/logged")).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.code").value("ok"))
                .andExpect(jsonPath("$.data.user.username").value("jaggerwang"));
    }

    @Test
    void register() throws Exception {
        var userEntity = UserEntity.builder().username("jagger001").password("123456").build();
        mvc.perform(post("/user/register").contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(userEntity))).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.code").value("ok"))
                .andExpect(jsonPath("$.data.user.username").value(userEntity.getUsername()));
    }

    @WithUserDetails("jaggerwang")
    @Test
    void info() throws Exception {
        mvc.perform(get("/user/info").param("id", "1")).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.code").value("ok"))
                .andExpect(jsonPath("$.data.user.id").value(1));
    }
} 
  • 首先需要使用 @SpringBootTest 注解来标注这是一个 Spring Boot 测试类,类似于 @DataJpaTest, 它会从当前包开始往上搜寻 @SpringBootConfiguration 注解的应用配置,以便开启自动配置和组件扫描。只不过这里会开启所有可用的自动配置和扫描全部组件,而不仅仅是 JPA 相关的。
  • 使用 @AutoConfigureMockMvc 来自动配置 MockMvc,这样不用启动真实的应用容器(比如 Tomcat)就可以进行 MVC 层的测试,以便提升测试速度。
  • 通过 @ActiveProfiles 注解启用了 test 属性配置文件 application-test.yml,可以在这里面去覆盖测试时所用的 MySQL 和 Redis 等服务的连接地址。切忌不可使用真实在用的服务,因为测试时会清理数据。
  • 两次使用 @Sql 注解来分别指定了在每个测试用例执行前后要执行的 SQL 脚本。
  • 使用 MockMvc 对象来发起 HTTP 请求并对返回结果执行验证。
  • 对于有认证要求的 API,可以使用 @WithUserDetails 注解来设置登录用户,使用该注解需要引入 spring-security-test 依赖。

测试 GraphQL API

GraphQL API 的测试跟 REST API 很类似,它们都是通过 HTTP 协议来对外提供服务,只不过 GraphQL API 只使用了一个 Endpoint,而不是多个。

package net.jaggerwang.sbip.api;

...

@SpringBootTest()
@AutoConfigureMockMvc
@ActiveProfiles("test")
@Sql({"/db/init-db-test.sql"})
@Sql(scripts = {"/db/clean-db-test.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
@EnabledIfSystemProperty(named = "test.api.enabled", matches = "true")
public class GraphQLApiTests {
    @Autowired
    private MockMvc mvc;

    @Value("${graphql.url}")
    private String graphqlUrl;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void login() throws Exception {
        var userEntity = UserEntity.builder().username("jaggerwang").password("123456").build();
        var content = new ObjectMapper().createObjectNode();
        content.put("query", "mutation($user: UserInput!) { authLogin(user: $user) { id username } }");
        content.putObject("variables").putObject("user").put("username", userEntity.getUsername())
                .put("password", userEntity.getPassword());
        mvc.perform(post(graphqlUrl).contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(content))).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.errors").doesNotExist())
                .andExpect(jsonPath("$.data.authLogin.username").value(userEntity.getUsername()));
    }

    @WithUserDetails("jaggerwang")
    @Test
    void logout() throws Exception {
        var content = new ObjectMapper().createObjectNode();
        content.put("query", "query { authLogout { id username } }");
        mvc.perform(post(graphqlUrl).contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(content))).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.errors").doesNotExist())
                .andExpect(jsonPath("$.data.authLogout.username").value("jaggerwang"));
    }

    @WithUserDetails("jaggerwang")
    @Test
    void logged() throws Exception {
        var content = new ObjectMapper().createObjectNode();
        content.put("query", "query { authLogged { id username } }");
        mvc.perform(post(graphqlUrl).contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(content))).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.errors").doesNotExist())
                .andExpect(jsonPath("$.data.authLogged.username").value("jaggerwang"));
    }

    @Test
    void register() throws Exception {
        var userEntity = UserEntity.builder().username("jagger001").password("123456").build();
        var content = new ObjectMapper().createObjectNode();
        content.put("query", "mutation($user: UserInput!) { userRegister(user: $user) { id username } }");
        content.putObject("variables").putObject("user").put("username", userEntity.getUsername())
                .put("password", userEntity.getPassword());
        mvc.perform(post(graphqlUrl).contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(content))).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.errors").doesNotExist())
                .andExpect(jsonPath("$.data.userRegister.username").value(userEntity.getUsername()));
    }

    @WithUserDetails("jaggerwang")
    @Test
    void info() throws Exception {
        var content = new ObjectMapper().createObjectNode();
        content.put("query", "query(id: Int!) { userInfo(id: $id) { id username } }");
        content.putObject("variables").put("id", 1);
        mvc.perform(post(graphqlUrl).contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(content))).andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.errors").doesNotExist())
                .andExpect(jsonPath("$.data.userInfo.id").value(1));
    }
} 

相比于 REST API 测试,需要额外注意的点如下:

  • 通过 @Value 注解来得到 GraphQL API Endpoint 的路径,而不是写死,这样测试用例的适应性更强。
  • 使用 ObjectMapper 来构造请求体,因为 GraphQL API 的请求体比较复杂,除了业务数据,还包含了路由等信息。

运行测试

本地运行

每种类型的测试用例默认都是关闭的,需要通过对应的系统属性来开启。

./mvnw -Dtest.repository.enabled=true test
./mvnw -Dtest.usecase.enabled=true test
./mvnw -Dtest.api.enabled=true test 

对于 API 测试,需要先在测试属性文件 application-test.yml 里配置测试用的 MySQL 和 Redis 服务的地址。切忌使用真实在用的服务,以免数据被清除。

在 Docker 容器里运行

为了减轻准备测试环境的工作量,尤其是 API 集成测试,还可以使用 Docker 容器来运行测试用例。

docker-compose -p spring-boot-in-practice-usecase-test -f docker-compose.usecase-test.yml up
docker-compose -p spring-boot-in-practice-repository-test -f docker-compose.repository-test.yml up
docker-compose -p spring-boot-in-practice-api-test -f docker-compose.api-test.yml up 

对于 API 集成测试,会自动通过 Docker 容器来启动一个临时的 MySQL 和 Redis 服务。下面是 API 集成测试的 Docker Compose 配置文件:

version: '3'
services:
  server:
    image: maven:3-jdk-11
    command: bash -c "cd /app && cp sources.list /etc/apt/ && cp settings.xml /root/.m2/ && mvn -Dtest.api.enabled=true test"
    environment:
      TZ: Asia/Shanghai
      SBIP_DEBUG: 'false'
      SBIP_LOGGING_LEVEL_REQUEST: INFO
      SBIP_SPRING_DATASOURCE_URL: jdbc:mysql://mysql/sbip
      SBIP_SPRING_DATASOURCE_USERNAME: sbip
      SBIP_SPRING_DATASOURCE_PASSWORD: 123456
      SBIP_SPRING_REDIS_HOST: redis
      SBIP_SPRING_REDIS_PORT: 6379
      SBIP_SPRING_REDIS_PASSWORD:
    volumes:
      - ~/.m2:/root/.m2
      - ./:/app
    depends_on:
      - mysql
      - redis
  mysql:
    image: mysql:8.0
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: sbip
      MYSQL_USER: sbip
      MYSQL_PASSWORD: 123456
  redis:
    image: redis:5.0
    environment:
      TZ: Asia/Shanghai 

其中包含了三个容器,server、mysql 和 redis,分别用来执行测试、运行 MySQL 服务和运行 Redis 服务。Server 容器基于 Maven 镜像,它提供了一个运行测试的环境,通过 command 指令我们指定了执行测试的具体命令。Server 配置里还通过环境变量指定测试时使用运行在容器里的 MySQL 和 Redis 服务。为了避免每次执行测试都要去重新下载所有依赖包,这里把宿主机上的本地仓库挂载到了 Server 容器内,这样能大大降低执行测试的时间。

测试结束之后,可以使用下面的对应命令来获取测试结果。如果容器进程退出结果状态为 0 则表示测试通过,其它为失败。

docker inspect spring-boot-in-practice-usecase-test_server_1 --format='{{.State.ExitCode}}'
docker inspect spring-boot-in-practice-repository-test_server_1 --format='{{.State.ExitCode}}'
docker inspect spring-boot-in-practice-api-test_server_1 --format='{{.State.ExitCode}}' 

测试结束后可以使用下面的对应命令来删除测试时创建的容器、网络等资源。

docker-compose -p spring-boot-in-practice-usecase-test -f docker-compose.usecase-test.yml down
docker-compose -p spring-boot-in-practice-repository-test -f docker-compose.repository-test.yml down
docker-compose -p spring-boot-in-practice-api-test -f docker-compose.api-test.yml down 

参考资料

  1. Spring Boot Testing
  2. JUnit 5
  3. Mockito
  4. AssertJ
  5. Flyway

本文转自 https://blog.jaggerwang.net/spring-boot-api-service-test-tour/,如有侵权,请联系删除。

收藏
评论区

相关推荐

一文搞懂Spring依赖注入
前言 提起Spring,大家肯定不陌生,它是每一个Java开发者绕不过去的坎。Spring 框架为基于 java 的企业应用程序提供了一整套解决方案
Spring Cloud 微服务开发指南
如同 Spring Boot 在 Java Web 开发领域中的统治地位,Spring Cloud 在 Java 微服务应用开发领域中同样处于垄断地位。软件系统从单体升级到微服务架构,随之会出现各种分布式系统所特有的问题,包括服务注册发现、认证授权、限流熔断、调用追踪等。Spring Cloud 提供了各种组件来解决这些问题,本文将通过升级改造一个单体 AP
Spring Boot API 服务测试指南
Spring Boot 除了简化了 Spring 应用的开发,同时也简化了 Spring 应用的测试。它内置支持各种常用测试工具,包括 Spring Test、JUnit、TestNG、Mockito、AssertJ 等。本文将讲解如何编写单元测试和集成测试来保障 Spring Boot API 应用不同层级代码的质量,其中会涉及到使用嵌入式的 H2 数据库
Spring Boot API 服务开发指南
Spring Boot 大大简化了使用 Spring 框架开发 Web 应用时的配置工作,使用它只需添加相关依赖包,即可通过零配置或少量配置来运行一个 Web 应用。本文将使用 Spring Boot 来开发一个 API 服务,同时支持 REST 和 GraphQL 两种协议。内容包括使用 Querydsl 来替换 JPQL 以便以类型安全的方式动态构建 S
浅析Spring boot与Spring cloud 之间的关系
浅析Spring boot与Spring cloud 之间的关系 20180515 18:16:10有些童鞋刚接触这块 ,理解不是很深刻会经常问道这样类似的问题,下面我就简单讲解一下Spring boot与Spring cloud 之间的关系!Spring boot 是 Spring 的一
Spring Boot整合Spring Cloud实现微服务架构学习
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接出处:https://blog.csdn.net/qq\_3076499,否则保留追究法律责任的权利。 如果文中有什么错误,欢迎指出。以免更多的人被误导。 当你看到这的时候,温馨提示:在学习springcloud之前,请先学习springboot,因为s
经典JAVA面试题整理,方便统一复习
以下是网上整理的非常全面的面试题,当然,绝大多数人不可能全部用到,但是都罗列在此,大家可根据自己的情况,选择对应的模块进行阅读。面试题模块介绍这份面试题,包含的内容了十九个模块:Java 基础、容器、多线程、反射、对象拷贝、Java Web 模块、异常、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring Cloud、Hi
Spring Boot集成 Sentinel 实现接口流量控制
Hello,大家好,我是麦洛,今天带大家来了解一下SpringBoot如何继承Sentinel来实现接口流量控制 Sentinel控制台搭建在我的上一篇文章阿里出品的Sentinel到底是个什么玩意?中,已经介绍过如何准备Sentinel控制台,大家可以直接参考;Sentinel 客户端 项目搭建首先我们来创建一个测试项目,这里初始化
优雅关闭Spring Boot应用
最新的Spring Boot添加了一个新特性 优雅停机。 官方介绍官方文档地址:https://docs.spring.io/springboot/docs/current/reference/htmlsingle/bootfeaturesgracefulshutdown 这个机制会有一个超时时间,该超时时间提供一个宽限期,在此宽限期内,现有请求将被允
阿里P8亲自教你!2021年Java程序员职业规划
Spring 全家桶:1. Spring 原理2. Spring面试题3. 思维导图4. 面试题5. Spring视频 Spring 原理 Spring特点 Spring 核心组件 Spring常用模块 Spring主要包 Spring常用注解 Sping第三方结合 Spring 10C原理 Spring APO原
阿里一线架构师技术图谱!十年开发经验Java架构师
开头我们面试的时候 ,经常会被问这种到问题:Spring中bean的循环依赖怎么解决? Spring中bean的加载过程? spring相关的问题一直是大厂面试常问到的一个问题,也是一直困扰这我们,不知道从哪里下手,今天举例分析大厂的一些spring相关的面试真题。和分享我学习spring相关问题所整理的一些知识点。 01 并发宝典:面试专题面试专题分为四个
想搞定大厂面试官?被逼无奈开始狂啃底层技术
Part 1微服务架构设计概述1.1 传统应用架构的问题1.2 微服务架构是什么1.3 微服务架构有哪些特点和挑战1.4 如何搭建微服务架构 Part 2微服务开发框架2.1 Spring Boot 是什么2.2 如何使用Spring Boot框架2.3 Spring Boot生产级特性 Part 3微服务网关3.1 Node.js 是什么3.2 如何使用
重磅!这份笔记连阿里P8面试官都说太详细了
一、Spring Boot 相关(1)SpringBoot 面试专题 什么是 Spring Boot? Spring Boot 有哪些优点? 什么是 JavaConfig? 如何重新加载 Spring Boot 上的更改,而无需重新启动服务器? Spring Boot 中的监视器是什么? 如何在 Spring Boot 中禁用 Act
Spring Boot 无侵入式 实现RESTful API接口统一JSON格式返回
前言现在我们做项目基本上中大型项目都是选择前后端分离,前后端分离已经成了一个趋势了,所以总这样·我们就要和前端约定统一的api 接口返回json 格式,这样我们需要封装一个统一通用全局 模版api返回格式,下次再写项目时候直接拿来用就可以了 约定JSON格式一般我们和前端约定json格式是这样的json "code": 200, "message
SpringBoot 默认json解析器详解和字段序列化自定义
前言在我们开发项目API接口的时候,一些没有数据的字段会默认返回NULL,数字类型也会是NULL,这个时候前端希望字符串能够统一返回空字符,数字默认返回0,那我们就需要自定义json序列化处理 SpringBoot默认的json解析方案我们知道在springboot中有默认的json解析器,Spring Boot 中默认使用的 Json 解析技术框架是 ja