本章从第7章开始:
7. Task Execution and Scheduling
在上下文中没有Executor bean的情况下,Spring Boot会自动配置一个ThreadPoolTaskExecutor,它具有合理的默认值,可以自动关联到异步任务执行(@EnableAsync)和Spring MVC异步请求处理。
如果你在上下文中定义了自定义Executor,常规任务执行(即@EnableAsync)将透明地使用它,但不会配置Spring MVC支持,因为它需要AsyncTaskExecutor实现(名为applicationTaskExecutor)。根据您的目标安排,您可以将Executor更改为ThreadPoolTaskExecutor或定义ThreadPoolTaskExecutor和AsyncConfigurer封装您的自定义Executor。
自动配置的TaskExecutorBuilder允许您轻松地创建实例来重现默认情况下自动配置所做的事情。
线程池使用8个核心线程,这些线程可以根据负载增长和收缩。这些默认设置可以使用spring.task.execution命名空间进行微调,如下例所示:
spring.task.execution.pool.max-size=16
spring.task.execution.pool.queue-capacity=100
spring.task.execution.pool.keep-alive=10s
测试:
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class TestService {
@Async("taskExecutor")
public void test() {
// do something
}
}
在这个测试代码中,@Async注解表示这个方法是异步执行的,"taskExecutor"表示使用的线程池的名称。在使用时,只需要注入TestService并调用test()方法即可。
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
@ConfigurationPropertiesScan(basePackages = "com.example.demo.demos")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(DemoApplication.class);
application.run(args);
}
}
开启一下Spring的异步特性的。
这会将线程池更改为使用有界队列,以便当队列满时(100个任务),线程池增加到最多16个线程。当线程空闲10秒(而不是默认的60秒)时回收它们,池的收缩会更加激进。
如果需要与计划任务执行相关联(例如使用@ enablesscheduling), ThreadPoolTaskScheduler也可以自动配置。线程池默认使用一个线程,可以使用spring.task.scheduling命名空间对其设置进行微调,如下例所示:
spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.scheduling.pool.size=2
如果需要创建自定义执行器或调度器,那么TaskExecutorBuilder bean和TaskSchedulerBuilder bean都可以在上下文中使用。
8. Testing
Spring Boot提供了许多实用工具和注释,可以在测试应用程序时提供帮助。测试支持由两个模块提供:spring-boot-test包含核心项,spring-boot-test-autoconfigure支持测试的自动配置。
大多数开发人员使用Spring - Boot - Starter -test“Starter”,它导入Spring - Boot测试模块以及JUnit Jupiter、AssertJ、Hamcrest和许多其他有用的库。
如果您有使用JUnit 4的测试,那么可以使用JUnit 5的老式引擎来运行它们。要使用复古引擎,需要在junit-vintage-engine上添加一个依赖项,如下例所示:
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
8.1. Test Scope Dependencies
spring-boot-starter-test“Starter”(在测试范围内)包含以下提供的库:
JUnit 5:单元测试Java应用程序的事实标准
Spring Test & Spring Boot Test: 对Spring Boot应用程序的实用程序和集成测试支持。
AssertJ: 一个流畅的断言库
Hamcrest: 匹配器对象库(也称为约束或谓词)。
Mockito: 一个Java模拟框架。
JSONassert: 一个JSON断言库。
JsonPath: 用于JSON的XPath。
我们通常发现这些公共库在编写测试时很有用。如果这些库不能满足您的需求,您可以添加自己的额外测试依赖项。
8.2. Testing Spring Applications
依赖注入的一个主要优点是,它可以使代码更容易进行单元测试。您可以通过使用new操作符实例化对象,甚至不需要使用Spring。您还可以使用模拟对象来代替真正的依赖项。
通常,您需要超越单元测试并开始集成测试(使用Spring ApplicationContext)。能够在不需要部署应用程序或连接到其他基础设施的情况下执行集成测试是很有用的。
Spring框架包括一个专门用于这种集成测试的测试模块。你可以直接声明一个依赖于org.springframework:spring-test或使用spring-boot-starter-test“Starter”来传递地拉入它。
8.3. Testing Spring Boot Applications
Spring Boot应用程序是一个Spring ApplicationContext,所以除了通常使用普通Spring上下文所做的测试之外,不需要做任何特别的事情来测试它。
默认情况下,只有在使用SpringApplication创建时,Spring Boot的外部属性、日志记录和其他特性才会安装在上下文中。
SpringBoot提供了一个@SpringBootTest注释,当您需要SpringBoot特性时,它可以作为标准Spring -test @ContextConfiguration注释的替代。注释的工作原理是通过SpringApplication创建测试中使用的ApplicationContext。除了@SpringBootTest之外,还提供了许多其他注释,用于测试应用程序的更具体的片段。
如果您使用的是JUnit 4,不要忘记在测试中添加@RunWith(sprinrunner .class),否则注释将被忽略。如果你使用的是JUnit 5,就不需要添加@ extendwith (SpringExtension.class)作为@ springboottest和其他@…Test注释已经用它进行了注释
默认情况下,@SpringBootTest不会启动服务器。你可以使用@SpringBootTest的webEnvironment属性来进一步优化测试的运行方式:
MOCK(默认):加载web ApplicationContext并提供模拟web环境。使用此注释时,嵌入式服务器不会启动。如果在你的类路径上没有一个web环境可用,这种模式就会透明地退回到创建一个常规的非web ApplicationContext。它可以与@AutoConfigureMockMvc或@AutoConfigureWebTestClient一起使用,用于基于模拟的web应用程序测试。
RANDOM_PORT:加载WebServerApplicationContext并提供一个真实的web环境。嵌入式服务器启动并在随机端口上侦听。
DEFINED_PORT:加载WebServerApplicationContext并提供一个真实的web环境。嵌入式服务器在一个定义的端口(来自application.properties)上启动并侦听,或者在默认端口8080上侦听。
NONE:通过使用SpringApplication加载ApplicationContext,但不提供任何web环境(mock或其他)。
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
class DemoApplicationTests {
或者:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DemoApplicationTests {
如果您的测试是@Transactional的,默认情况下,它会在每个测试方法结束时回滚事务。然而,使用RANDOM_PORT或DEFINED_PORT的这种安排隐式地提供了一个真正的servlet环境,HTTP客户端和服务器在单独的线程中运行,因此在单独的事务中运行。在这种情况下,服务器上发起的任何事务都不会回滚。
@SpringBootTest with webEnvironment = webEnvironment。如果应用程序为管理服务器使用不同的端口,RANDOM_PORT还将在一个单独的随机端口上启动管理服务器。
8.3.1. Detecting Web Application Type
如果Spring MVC可用,则配置一个常规的基于MVC的应用程序上下文。如果你只有Spring WebFlux,我们会检测并配置一个基于WebFlux的应用上下文。
如果两者都存在,则优先考虑Spring MVC。如果你想在这种情况下测试一个响应式web应用程序,你必须设置spring.main.web-application-type属性:
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(properties = "spring.main.web-application-type=reactive")
class MyWebFluxTests {
// ...
}
8.3.2. Detecting Test Configuration
如果您熟悉Spring测试框架,您可能习惯于使用@ContextConfiguration(classes=…)来指定加载哪个Spring @Configuration。或者,您可能经常在测试中使用嵌套的@Configuration类。
在测试Spring Boot应用程序时,通常不需要这样做。Spring Boot的@*Test注释在您没有显式定义主配置时自动搜索主配置。
搜索算法从包含测试的包开始查找,直到找到带有@SpringBootApplication或@SpringBootConfiguration注释的类。只要以一种合理的方式组织代码,通常就能找到主配置。
如果您使用测试注释来测试应用程序中更具体的部分,那么您应该避免在主方法的应用程序类中添加特定于特定区域的配置设置。
@SpringBootApplication的底层组件扫描配置定义了用于确保切片按预期工作的排除过滤器。如果你在@ springbootapplication注释的类上使用显式的@ComponentScan指令,请注意这些过滤器将被禁用。如果您正在使用切片,则应该重新定义它们。
如果你想定制主配置,你可以使用嵌套的@TestConfiguration类。与嵌套的@Configuration类不同,嵌套的@TestConfiguration类是用来代替应用程序的主要配置的,而嵌套的@TestConfiguration类是用来代替应用程序的主要配置的。
Spring的测试框架在测试之间缓存应用程序上下文。因此,只要您的测试共享相同的配置(无论如何发现它),加载上下文的潜在耗时过程只会发生一次。
8.3.3. Excluding Test Configuration
如果您的应用程序使用组件扫描(例如,如果您使用@SpringBootApplication或@ComponentScan),您可能会发现仅为特定测试创建的顶级配置类意外地在各处被拾取。
正如我们前面看到的,@TestConfiguration可以在测试的内部类上使用,以定制主配置。当放置在顶级类上时,@TestConfiguration表示src/test/java中的类不应该被扫描。然后你可以在需要的地方显式导入这个类,如下面的例子所示:
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
@SpringBootTest
@Import(MyTestsConfiguration.class)
class MyTests {
@Test
void exampleTest() {
// ...
}
}
如果你直接使用@ComponentScan(也就是说,不是通过@SpringBootApplication),你需要注册TypeExcludeFilter。
8.3.4. Using Application Arguments
如果应用程序需要参数,可以让@SpringBootTest使用args属性注入参数。
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(args = "--app.test=one")
class MyApplicationArgumentTests {
@Test
void applicationArgumentsPopulated(@Autowired ApplicationArguments args) {
assertThat(args.getOptionNames()).containsOnly("app.test");
assertThat(args.getOptionValues("app.test")).containsOnly("one");
}
}
8.3.5. Testing With a Mock Environment
默认情况下,@SpringBootTest不会启动服务器,而是为测试web端点设置一个模拟环境。
使用Spring MVC,我们可以使用MockMvc或WebTestClient查询我们的web端点,如下面的例子所示:
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.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class MyMockMvcTests {
@Test
void testWithMockMvc(@Autowired MockMvc mvc) throws Exception {
mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string("Hello World"));
}
// If Spring WebFlux is on the classpath, you can drive MVC tests with a WebTestClient
@Test
void testWithWebTestClient(@Autowired WebTestClient webClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
@AutoConfigureMockMvc它可以自动配置MockMvc并将其注入到测试类中。在使用Spring Boot进行单元测试时,我们可以使用它来模拟HTTP请求和响应,以便测试我们的控制器是否按预期工作。
如果你只想关注web层,而不想启动一个完整的ApplicationContext,可以考虑使用@WebMvcTest。
使用Spring WebFlux端点,你可以使用WebTestClient,如下例所示:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {
@Test
void exampleTest(@Autowired WebTestClient webClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
在模拟环境中进行测试通常比在完整的servlet容器中运行要快。然而,由于mock发生在Spring MVC层,依赖于低级servlet容器行为的代码不能直接用MockMvc进行测试。
例如,Spring Boot的错误处理基于servlet容器提供的“错误页”支持。这意味着,虽然您可以按照预期测试MVC层抛出和处理异常,但您不能直接测试是否呈现了特定的自定义错误页面。如果您需要测试这些较低级别的关注点,您可以启动一个完全运行的服务器,如下一节所述。
8.3.6. Testing With a Running Server
如果您需要启动一个完整运行的服务器,我们建议您使用随机端口。如果您使用@SpringBootTest(webEnvironment= webEnvironment . random_port),那么每次测试运行时都会随机选择一个可用的端口。
可以使用@LocalServerPort注释将实际使用的端口注入到测试中。为方便起见,需要对已启动服务器进行REST调用的测试可以另外@Autowire一个WebTestClient,它解析到正在运行的服务器的相对链接,并附带一个专用的API来验证响应,如下例所示:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortWebTestClientTests {
@Test
void exampleTest(@Autowired WebTestClient webClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
WebTestClient既可以用于活动服务器,也可以用于模拟环境。
这个设置需要类路径上的spring-webflux。如果你不能或不打算添加webflux, Spring Boot也提供了一个TestRestTemplate工具:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortTestRestTemplateTests {
@Test
void exampleTest(@Autowired TestRestTemplate restTemplate) {
String body = restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}
}
记得点关注