woshikid / blog

Apache License 2.0
8 stars 1 forks source link

Spring Boot学习笔记 #161

Open woshikid opened 3 years ago

woshikid commented 3 years ago

Spring Boot CLI

下载地址 https://repo.spring.io/release/org/springframework/boot/spring-boot-cli/2.5.3/spring-boot-cli-2.5.3-bin.zip

Maven配置文件

~/.m2/settings.xml

运行groovy

@RestController
class ThisWillActuallyRun {

    @RequestMapping("/")
    String home() {
        "Hello World!"
    }

}
spring run app.groovy

创建项目

spring help init
spring init --list
spring init -g com.github -a test -d web,actuator

创建项目

可以使用https://start.spring.io创建Spring Boot项目 POM

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.3</version>
    <relativePath/>
</parent>

<properties>
    <java.version>11</java.version>
</properties>

<dependencies>
    <!-- non-web -->
    <!-- <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency> -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- war -->
    <!-- <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency> -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

如需依赖特定版本的Maven,可以安装Maven Wrapper 如果私服需要密码,需使用http://username:password@nexus形式的url下载wrapper,Maven配置文件~/.m2/settings.xml

mvn -N io.takari:maven:0.7.7:wrapper -Dmaven=3.8.1

启动项目

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        //new SpringApplication(Application.class).run(args);
        //new SpringApplicationBuilder(Application.class).run(args);
    }

}
// <packaging>war</packaging>
public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

}
mvn spring-boot:run
java -jar target\test-0.0.1.jar # Spring Boot通过org.springframework.boot.loader.JarLauncher启动@SpringBootApplication所在的类
java -jar target\test-0.0.1.war # Spring Boot通过org.springframework.boot.loader.WarLauncher启动@SpringBootApplication所在的类

开发模式自动重启服务

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

生成瘦jar包

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot.experimental</groupId>
            <artifactId>spring-boot-thin-layout</artifactId>
            <version>1.0.27.RELEASE</version>
        </dependency>
    </dependencies>
</plugin>

运行瘦jar包(下载spring-boot-thin-launcher时不支持私服),Maven配置文件~/.m2/settings.xml

java [-Dthin.dryrun=true] [-Dthin.debug=true] [-Dmaven.repo.local=D:\Repository] -jar target\test-0.0.1.jar

启动后自动执行

可使用ApplicationRunner

@Component
public class TestApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(Arrays.asList(args.getSourceArgs()));
    }

}

或使用CommandLineRunner

@Component
public class TestCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println(Arrays.asList(args));
    }

}

或使用@PostConstructInitializingBeanApplicationListener等传统方式

配置项目

可通过@ValueEnvironment@ConfigurationProperties获取配置 bash环境变量名称带.的可通过env "key=value" bash设置

配置优先级

1. 测试参数:@SpringBootTest("key=value")
2. 测试配置:@TestPropertySource
3. 程序参数:--key=value
4. 程序JSON参数:--spring.application.json='{"key":"value"}'
5. JVM JSON参数:-Dspring.application.json='{"key":"value"}'
6. 环境变量JSON:export SPRING_APPLICATION_JSON='{"key":"value"}'
7. JVM参数:-Dkey=value
8. 环境变量:export key=value
9. 配置文件

配置文件优先级 默认spring.config.name=application

1. application-{profile}.properties
2. application-{profile}.yml
3. application-{profile}.yaml
4. application.properties
5. application.yml
6. application.yaml

配置路径优先级 特别注意:路径优先级大于文件(profile)优先级 使用spring.config.location将忽略后续路径

1. --spring.config.additional-location
2. -Dspring.config.additional-location
3. export SPRING_CONFIG_ADDITIONALLOCATION
4. --spring.config.location
5. -Dspring.config.location
6. export SPRING_CONFIG_LOCATION
7. ./config/*/
8. ./config/
9. ./
10. classpath:/config/
11. classpath:/
12. @PropertySource

使用Profile

spring.profiles:
  include: dev # 引用其他profile
  active: test # 默认为空
  group: # 分组
    debug: "debugdb,debugmq"

# 默认profile
server:
  address: 0.0.0.0
  port: 8080
---
spring.config.activate.on-profile: test
server.port: 8000 # 覆盖默认profile
---
spring.config.activate.on-profile: production
server.port: 80 # 覆盖默认profile

使用Conditional

@ConditionalOnResource(resources = "application.yml")
@ConditionalOnExpression("${server.port:8080} == 8080")
@ConditionalOnProperty(name = "server.port", havingValue = "8080", matchIfMissing = true)
@ConditionalOnBean(TestBean.class)
@ConditionalOnMissingBean(TestBean.class)
@ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnClass(Test.class)
@ConditionalOnMissingClass("com.github.test.Test")
@ConditionalOnWebApplication
@ConditionalOnNotWebApplication
@ConditionalOnWarDeployment

使用配置类

@Component // 也可使用@EnableConfigurationProperties(ServerProperties.class)或@ConfigurationPropertiesScan来注册/扫描配置类
@ConfigurationProperties("server") // 可配合使用@ConstructorBinding与@DefaultValue进行构造参数注入
//@PropertySource("test.properties") // 默认不支持yml
public class ServerProperties {

    private int port; // server.port

    //@NestedConfigurationProperty
    private Compression compression;

    // 省略getter与setter

}
public class Compression {

    private boolean enabled; // server.compression.enabled

    private List<String> mimeTypes; // server.compression.mime-types

    // 省略getter与setter

}

@PropertySource支持yml

@PropertySource(value = "test.yml", factory = YmlPropertySourceFactory.class)
public class YmlPropertySourceFactory implements PropertySourceFactory {

    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
        factory.setResources(resource.getResource());
        return new PropertiesPropertySource(name != null ? name : resource.getResource().getFilename(), factory.getObject());
    }

}

自动配置

@SpringBootApplication等于以下配置

@SpringBootConfiguration
    @Configuration
@EnableAutoConfiguration
    @AutoConfigurationPackage
        @Import(AutoConfigurationPackages.Registrar.class)
    @Import(AutoConfigurationImportSelector.class)
@ComponentScan

通过加载META-INF/spring.factories获得AutoConfiguration类列表与ApplicationListener类列表等 AutoConfiguration类可使用@AutoConfigureOrder, @AutoConfigureBefore, @AutoConfigureAfter控制加载顺序 AutoConfiguration类不应被@ComponentScan扫描并加载,否则加载顺序不能保证

禁用自动配置

@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

Spring核心

从Spring Boot 2.6开始,默认禁止循环引用

spring.main.allow-circular-references: false

AOP

POM

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

自动配置类为AopAutoConfiguration

默认配置

spring.aop.auto: true
spring.aop.proxy-target-class: true

内部调用AOP

// 通过ThreadLocal暴露代理类
@EnableAspectJAutoProxy(exposeProxy = true)

日志

默认使用Logback作为日志实现 默认配置位于DefaultLogbackConfiguration

自定义配置

debug: true # 或使用--debug
trace: true # 或使用--trace

logging:
  file.name: D:\logs\spring.log
  file.path: D:\logs # spring.log
  level:
    root: debug # 默认为info
    com.github.test: info # 自定义Logger
    sql: debug # 使用日志组
  group: # 定义日志组
    dao: com.github.test.repository, com.github.test.mapper
  logback:
    rollingpolicy:
      #file-name-pattern: D:\logs\spring.log.%d{yyyy-MM-dd}.%i.gz
      max-file-size: 100MB # 默认为10MB
      #total-size-cap: 100GB
      max-history: 30 # 默认为7天
  pattern:
    dateformat: "yyyy-MM-dd HH:mm:ss.SSS"
    level: "%5p"
    #console: "%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } --- [%15.15t] %-40.40logger{39} : %m%n%wEx"
    #file: "%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } --- [%t] %-40.40logger{39} : %m%n%wEx"

使用logback-spring.xmllogback.xml以覆盖默认配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!-- 引入默认配置 -->
    <include resource="org/springframework/boot/logging/logback/base.xml"/>
    <!-- 引入部分默认配置 -->
    <!-- <include resource="org/springframework/boot/logging/logback/defaults.xml"/> -->
    <!-- <include resource="org/springframework/boot/logging/logback/console-appender.xml"/> -->
    <!-- <include resource="org/springframework/boot/logging/logback/file-appender.xml"/> -->

    <!-- 使用Profile(仅限logback-spring.xml) -->
    <springProfile name="test">
        <!-- 此处的配置仅test环境有效 -->
    </springProfile>
</configuration>

Log4j2

POM

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 已包含log4j-slf4j-impl,兼容SLF4J -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

默认配置位于org/springframework/boot/logging/log4j2/log4j2.xmlorg/springframework/boot/logging/log4j2/log4j2-file.xml 使用log4j2-spring.xmllog4j2.xml以覆盖默认配置 仍可通过logging:配置日志属性,但滚动文件输出只能通过log4j2的配置文件进行配置

国际化

自动配置类为MessageSourceAutoConfiguration,默认配置类为MessageSourceProperties

默认配置

spring.messages:
  basename: messages
  encoding: UTF-8
  fallback-to-system-locale: true
  use-code-as-default-message: false

WebServer

自动配置类为ServletWebServerFactoryAutoConfiguration,默认配置类为ServerProperties 默认使用ServletWebServerFactoryCustomizer进行自动配置

默认配置

server:
  address: 0.0.0.0
  port: 8080
  servlet:
    context-path: ""
    register-default-servlet: false
    session:
      timeout: 30m
      persistent: false
      cookie:
        name: JSESSIONID

使用WebServerFactoryCustomizer

@Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> webServerFactoryCustomizer() {
    return new WebServerFactoryCustomizer<>() {
        @Override
        public void customize(ConfigurableServletWebServerFactory factory) { // TomcatServletWebServerFactory
            factory.setInitParameters(Map.of("key", "value")); // 相当于<context-param>
            factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html")); // 相当于<error-page>
            // TomcatServletWebServerFactory
            //factory.addAdditionalTomcatConnectors(connector); // 在启用HTTPS时同时启用HTTP
        }
    };
}

DocumentRoot

注意:使用jar的部署方式时将不包含webapp文件夹 此时将使用外部文件夹src/main/webapppublicstatic(启动时判断) 不允许访问地址以/org/springframework/boot开头的资源

优雅停机

自动配置类为LifecycleAutoConfiguration,默认配置类为LifecycleProperties 开启时,收到kill, Ctrl + C或Actuator的信号后,停止接收新请求并等待现有请求完成

自定义配置

# 开启优雅停机
server.shutdown: GRACEFUL # 默认为IMMEDIATE
# 设置等待时间
spring.lifecycle.timeout-per-shutdown-phase: 60s # 默认为30s

SSL

server:
  port: 443 # 启用HTTPS后不再支持HTTP
  ssl:
    key-store: "classpath:keystore.jks"
    key-store-password: "secret"
    key-password: "another-secret"
    #key-alias: "alias"

HTTP/2

# Tomcat: JDK9+
# Jetty: JDK9+
# Undertow: JDK8+
server.http2.enabled: true

Tomcat

默认使用TomcatWebServerFactoryCustomizer进行自动配置

默认配置

server.tomcat:
  basedir: /tmp/tomcat.{port}.{random}
  accesslog: # 默认append模式
    enabled: false
    directory: logs # 支持相对basedir的路径或绝对路径
    rotate: true
    rename-on-rotate: false # 直到切日时文件名才加上日期
    max-days: -1
  threads.max: 200 # 工作线程数
  max-http-form-post-size: 2MB # application/x-www-form-urlencoded
  uri-encoding: UTF-8
  max-connections: 8192 # 最大连接数
  accept-count: 100 # 达到最大连接数后的等待队列
  connection-timeout: 60s # 连接空闲时间

JSP

POM

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>
<!-- JSTL -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
</dependency>

Jetty

POM

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 注意不能包含tomcat-embed-jasper依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

默认使用JettyWebServerFactoryCustomizer进行自动配置

默认配置

server.jetty:
  accesslog: # 默认rotate模式
    enabled: false
    filename: "" # 默认为System.err,支持相对当前路径或绝对路径,文件名必须包含"yyyy_mm_dd"
    retention-period: 31
    append: false
  threads:
    max: 200 # 工作线程数
    max-queue-capacity: 8192 # 达到最大工作线程数后的等待队列
  max-http-form-post-size: 200000B # application/x-www-form-urlencoded
  connection-idle-timeout: 30s # 连接空闲时间

JSP

POM

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>apache-jsp</artifactId>
</dependency>
<!-- JSTL -->
<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>apache-jstl</artifactId>
</dependency>

HTTP/2

POM

<dependency>
    <groupId>org.eclipse.jetty.http2</groupId>
    <artifactId>http2-server</artifactId>
</dependency>
<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-alpn-java-server</artifactId>
</dependency>

Undertow

POM

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 注意不能包含tomcat-embed-jasper依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

默认使用UndertowWebServerFactoryCustomizer进行自动配置

默认配置

server.undertow:
  accesslog: # 默认append模式
    enabled: false
    dir: logs # 支持相对当前路径或绝对路径
    rotate: true
  threads.worker: 32 # 默认为8 * cpu核数
  url-charset: UTF-8
  no-request-timeout: 60s # 连接空闲时间

JSP

POM

<dependency>
    <groupId>io.undertow.jastow</groupId>
    <artifactId>jastow</artifactId>
    <version>2.1.0.Final</version>
</dependency>
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.3</version>
</dependency>

JspServlet

@Bean
public UndertowDeploymentInfoCustomizer undertowDeploymentInfoCustomizer() {
    return deploymentInfo -> {
        deploymentInfo.addServlet(JspServletBuilder.createServlet("Default Jsp Servlet", "*.jsp"));
        JspServletBuilder.setupDeployment(deploymentInfo, new HashMap<>(), new HashMap<>(), new HackInstanceManager());
    };
}

配置Filter/Servlet/Listener

使用@WebFilter/@WebServlet/@WebListener@ServletComponentScan

@WebFilter("/test")
public class TestFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(request, response);
    }

}
@SpringBootApplication
@ServletComponentScan // 使用jar的部署方式时扫描@WebFilter, @WebServlet, @WebListener

或使用@Bean@Component

@Bean // 默认urlPatterns = "/*"
public Filter testFilter() {
    return new TestFilter(); // 无需@WebFilter,如果使用war的部署方式,将造成重复拦截
}

或使用RegistrationBean

@Bean
public FilterRegistrationBean<Filter> filterRegistrationBean() { // ServletRegistrationBean, ServletListenerRegistrationBean
    FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(); // 默认asyncSupported = true
    bean.setFilter(testFilter());
    //bean.setFilter(new TestFilter());
    bean.addUrlPatterns("/test");
    return bean;
}

或使用DelegatingFilterProxy

@Bean
public DelegatingFilterProxyRegistrationBean delegatingFilterProxyRegistrationBean() {
    DelegatingFilterProxyRegistrationBean bean = new DelegatingFilterProxyRegistrationBean("testFilter");
    bean.addUrlPatterns("/test");
    return bean;
}

Spring MVC

自动配置类为WebMvcAutoConfiguration,默认配置类为WebMvcPropertiesWebProperties 注意:@EnableWebMvc优先级大于WebMvcAutoConfiguration,使用后将导致自动配置失效 默认使用WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter进行自动配置 推荐使用WebMvcConfigurer来扩展默认配置,详见 #141

默认配置

spring.mvc:
  # 内容协商
  contentnegotiation:
    favor-path-extension: false
    favor-parameter: false
    parameter-name: format
    media-types: {}
  # 路径匹配
  pathmatch:
    use-suffix-pattern: false
    use-registered-suffix-pattern: false

# 静态资源映射(自动支持WebJars)
spring.mvc.static-path-pattern: "/**"
spring.web.resources:
  static-locations: # 自动包括路径"/"
    - "classpath:/META-INF/resources/"
    - "classpath:/resources/"
    - "classpath:/static/"
    - "classpath:/public/"
  cache.period: 0s

ContextLoaderListener

通过SpringBootServletInitializer.onStartup()创建

DispatcherServlet

自动配置类为DispatcherServletAutoConfiguration,默认配置类为WebMvcProperties,默认开启异步处理

默认配置

spring.mvc.servlet:
  path: "/"
  load-on-startup: -1

Jackson

自动配置类为JacksonAutoConfiguration,默认配置类为JacksonProperties

自定义配置

spring.jackson:
  date-format: "yyyy-MM-dd HH:mm:ss"
  default-property-inclusion: NON_NULL

自定义转换

@JsonComponent // @Component + @JsonSerialize + @JsonDeserialize

StringHttpMessageConverter

自动配置类为HttpMessageConvertersAutoConfiguration,默认配置类为ServerProperties

默认配置

server.servlet.encoding.charset: UTF-8

文件上传

自动配置类为MultipartAutoConfiguration,默认配置类为MultipartProperties,使用Servlet 3.0

默认配置

spring.servlet.multipart:
  location: /tmp/tomcat.{port}.{random}/work/Tomcat/localhost/ROOT # 注意Linux自动清理/tmp目录
  max-file-size: 1MB
  max-request-size: 10MB
  file-size-threshold: 0

设置字符集

自动配置类为HttpEncodingAutoConfiguration,默认配置类为ServerProperties

默认配置

server.servlet.encoding.charset: UTF-8

JSP

需引入JSP相关依赖

自定义配置

spring.mvc.view:
  prefix: "/WEB-INF/jsp/"
  suffix: ".jsp"

异常处理

自动配置类为ErrorMvcAutoConfiguration,默认配置类为ServerProperties

默认配置

server.error.path: "/error" # BasicErrorController.@RequestMapping

使用ErrorPageRegistrar(相当于<error-page>

@Bean
public ErrorPageRegistrar errorPageRegistrar() {
    return new ErrorPageRegistrar() {
        @Override
        public void registerErrorPages(ErrorPageRegistry registry) {
            registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html"));
            registry.addErrorPages(new ErrorPage(NullPointerException.class, "/npe"));
        }
    };
}

默认ErrorPageRegistrar -> BasicErrorController -> DefaultErrorViewResolver 默认查找顺序:

  1. View: error/{status}
  2. Resource: error/{status}.html
  3. View: error

ErrorAttributes

属性 类型
timestamp Date
status Integer
error String
trace String
message String
path String

WelcomePage

默认使用WelcomePageHandlerMapping(相当于<welcome-file-list>) 默认查找顺序:

  1. Resource: index.html
  2. View: index

嵌入式数据库H2控制台

自动配置类为H2ConsoleAutoConfiguration,默认配置类为H2ConsoleProperties

默认配置

spring.h2.console:
  enabled: false # 使用devtools时默认为true
  path: "/h2-console"
  settings:
    web-allow-others: false
    #web-admin-password: "" # 密码不能为空

JUnit

//@RunWith(SpringRunner.class) // JUnit4

@SpringJUnitWebConfig(classes = Application.class, initializers = ConfigDataApplicationContextInitializer.class) // 在不使用@SpringBootTest时加载application.properties/yml

@SpringBootTest // 使用内部静态@Configuration或自动查找@SpringBootConfiguration
//@TestConfiguration // 作为静态内部类或被@Import导入来追加配置(不影响@SpringBootTest自动查找@SpringBootConfiguration)
@SpringBootTest(classes = Application.class) // 指定配置类
@SpringBootTest({"key=value"}) // 设置属性
@SpringBootTest("spring.main.allow-bean-definition-overriding=true") // 开启bean覆盖
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) // 启动内置服务器,事务无法回滚,可使用@LocalServerPort,TestRestTemplate

JDBC

@AutoConfigureTestDatabase
@ImportAutoConfiguration(SqlInitializationAutoConfiguration.class)

@Autowired MockMvc

@WebMvcTest(TestController.class) // MockMvcBuilders.standaloneSetup()
@AutoConfigureMockMvc // MockMvcBuilders.webAppContextSetup()

Mockito

@MockBean // mock并注入bean
@SpyBean // spy并注入bean // 通过AopTestUtils.getTargetObject()得到原始对象

测试工具

@ExtendWith(OutputCaptureExtension.class) // 捕获System.out/err

AssertJ

org.assertj.core.api.Assertions.assertThat("Frodo").startsWith("Fro").endsWith("do").isEqualToIgnoringCase("frodo");

JSONassert

org.skyscreamer.jsonassert.JSONAssert.assertEquals("{id:1}", "{id:1, name:\"Joe\"}", false); // strict = false

JsonTest

@JsonTest
public class JUnitTest {

    @Autowired
    private JacksonTester<User> json;

    @Test
    public void serialize() throws IOException {
        User user = new User("id", "name");
        assertThat(json.write(user)).isEqualToJson("{\"id\":\"id\"}"); // strict = false
        assertThat(json.write(user)).hasJsonPathStringValue("@.id");
        assertThat(json.write(user)).extractingJsonPathStringValue("@.name").isEqualTo("name");
    }

    @Test
    public void deserialize() throws IOException {
        String content = "{\"id\":\"id\",\"name\":\"name\"}";
        assertThat(json.parse(content)).usingRecursiveComparison().isEqualTo(new User("id", "name"));
        assertThat(json.parseObject(content).getName()).isEqualTo("name");
    }

}