SpringBoot基础特性

SpringBoot基础特性

SpringApplicaiton

SpringApplication类提供了一种便捷的方式来引导(bootstrap)从main()方法启动的Spring应用程序。在很多情况下,你可以直接委托给静态方法SpringApplication.run(Class,String...)如下面示例所示:

@SpringBootApplication
public class MyApplication {

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

}

当你的应用程序启动时,你应该会看到类似于以下内容的输出:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::               (v3.5.16)

2026-06-25T08:25:46.158Z  INFO 66476 --- [           main] o.s.b.d.f.logexample.MyApplication       : Starting MyApplication using Java 17.0.19 with PID 66476 (/opt/apps/myapp.jar started by myuser in /opt/apps/)
2026-06-25T08:25:46.175Z  INFO 66476 --- [           main] o.s.b.d.f.logexample.MyApplication       : No active profile set, falling back to 1 default profile: "default"
2026-06-25T08:25:48.451Z  INFO 66476 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2026-06-25T08:25:48.474Z  INFO 66476 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2026-06-25T08:25:48.474Z  INFO 66476 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.55]
2026-06-25T08:25:48.576Z  INFO 66476 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2026-06-25T08:25:48.584Z  INFO 66476 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2237 ms
2026-06-25T08:25:49.286Z  INFO 66476 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2026-06-25T08:25:49.317Z  INFO 66476 --- [           main] o.s.b.d.f.logexample.MyApplication       : Started MyApplication in 4.565 seconds (process running for 5.424)
2026-06-25T08:25:49.333Z  INFO 66476 --- [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown        : Commencing graceful shutdown. Waiting for active requests to complete
2026-06-25T08:25:49.361Z  INFO 66476 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown        : Graceful shutdown complete

自定义banner

启动时打印的Banner(横幅)可以通过在classpath中添加一个banner.txt文件来修改(文件默认位置为classpath:banner.txt),也可以通过将spring.banner.location属性设置为该文件所在的位置来修改。如果该文件使用的时UTF-8以外的编码,你可以通过spring.banner.charset属性来指定。在banner.txt文件内部,你可以使用Environment中可用的任何键(key),以及以下任意占位符:

变量 描述
$ 应用程序的版本号,如在MANIFEST.MF的Implementation-Version中所声明。例如Implementation-Version: 1.0 被打印为1.0
$ 应用程序的版本号,如在MANIFEST.MF中所声明,并经过格式化以便展示(使用括号包围,并以v为前缀),例如(v1.0)
$ 你所使用的Spring Boot版本号。例如3.5.16
$ 你所使用的Spring Boot版本号,并经过格式化以便展示(使用括号包围,并以v为前缀),例如(v3.5.16)

推荐一个在线制作banner的网址:https://patorjk.com/software/taag/

自定义SpringApplication

如果SpringApplication的默认配置不符合你的编好,你可以改为创建一个本地实例(local instance)并对其进行自定义。例如关闭Banner:

@SpringBootApplication
public class MyApplication {

	public static void main(String[] args) {
		SpringApplication application = new SpringApplication(MyApplication.class);
		application.setBannerMode(Banner.Mode.OFF);
		application.run(args);
	}

}

当然也可以通过application.properties文件来配置SpringApplication,在配置文件中的配置参数生效的优先级高于这种代码式的配置

FluentBuilder API

如果你需要构建一个ApplicationContext层次结构(多个具有父/子关系的上下文),或者更喜欢使用流式构建器API(fluent builder API),那么你可以使用SpringApplicationBuilder。

SpringApplicationBuilder允许你将多个方法调用链式连接起来,并提供parent和child方法用于创建层次结果,如下所示:

		new SpringApplicationBuilder().sources(Parent.class)
			.child(Application.class)
			.bannerMode(Banner.Mode.OFF)
			.run(args);

Profiles

Spring Profiles提供了一种机制,可以将应用程序中的部分配置进行隔离,并使这些配置仅在特定环境中可用。

任何使用@Component、@Configuration或@ConfigurationProperties标注的类,都可以进一步使用@Profile注解来限制其加载条件,也就是说,可以指定这些组件或配置只在某些特定的Profile被激活时才加载。如下例所示:

@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {
	// ...
}

你可以使用spring.profiles.active属性来指定哪些Profile处于激活状态。例如在application.properties文件中配置它:

spring.profiles.active=dev,hsqldb

你也可以通过命令行参数来指定要激活的Profile,例如:

--spring.profiles.active=dev,hsqldb

如果当前没有任何Profile被激活,Spring会自动启用一个默认Profile。

默认Profile的名称是default。当然你可以通过spring.profiles.default属性来修改默认Profile名称,比如:

spring.profiles.default=none

注:spring.profiles.active和spring.profiles.default只能用于非Profile专属的配置文件中。这意味着,它们不能写在Profile专属文件中,也不能写在通过spring.config.activate.on-profile激活的配置文档中。

追加激活的Profile

有时候,我们希望某些配置用于向当前已经激活的Profile中追加新的Profile,而不是将已有的Profile替换掉。

spring.profiles.include属性可以用于在spring.profiles.active已经激活的Profile基础上,额外添加其他Profile。

SpringApplication入口类同样提供了用于设置附加Profile的Java API。可以参考SpringApplication中的setAdditionalProfiles()方法。

比如:当应用使用下面的配置运行时,即使启动应用时使用了--spring.profiles.active命令行参数,common和local这两个Profile仍然会被激活:

spring.profiles.include[0]=common
spring.profiles.include[1]=local

Profile组

有时,你在应用中定义和使用的Profile粒度过细,会导致使用起来比较繁琐。

例如,你可能分别定义了proddb和prodmq两个Profile,用来独立启用数据库和消息相关的功能。

为了解决这个问题,Spring Boot允许你定义Profile组(Profile Groups)。

Profile组允许你为一组相互关联的Profile定义一个统一的逻辑名称。

例如,我们可以创建一个名为production的Profile组,并让该组由proddb和prodmq两个Profile组成。

spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq

现在,我们可以通过下面的方式启动应用

--spring.profiles.active=production

这样就可以一次性激活production、proddb和prodmq这三个Profile。

以编程方式设置Profile

你可以在应用启动之前,通过调用SpringApplication.setAdditionalProfiles(...),以编程方式设置需要激活的Profile。另外也可以通过Spring提供的ConfigurableEnvironment接口来激活Profile。

Profile专属配置文件

除了普通的application配置文件之外,Spring Boot还会按照application-{profile}的命名规则尝试加载Profile专属配置文件。

例如,如果应用激活了一个名为prod的Profile,并且使用YAML配置文件,那么Spring Boot会同时考虑加载:

  • application.yaml
  • application-prod.yaml

Profile专属配置文件与标准的application.properties从相同的位置进行加载,并且Profile专属配置文件中的属性始终会覆盖非Profile专属配置文件中的同名属性。

如果同时指定了多个Profile,则遵循"后者优先"(last-wins)的策略。

例如:如果通过spring.profiles.active指定:prod,live 。那么application-prod.properties中的配置值,可以被application-live.properties中的同名配置值覆盖。

Profile使用示例

通过对Bean组件使用@Profile注解,并添加Profile专属配置文件,测试Profile的特定环境激活效果

POJO类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Cat {
    private Long id;
    private String name;
}
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
@Profile({"test","dev"})
public class Dog {
    private Long id;
    private String name;
}

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
@Profile({"default"})
public class Pig {
    private Long id;
    private String name;
    private String age;
}
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
@Profile({"dev"})
public class Sheep {
    private Long id;
    private String name;
}

配置类

@Profile({"test"}) // 只有指定环境被激活整个类的所有配置才能生效
@Configuration
public class MyConfig {

    @Profile("dev")
    @Bean
    public Cat cat() {
        return new Cat();
    }
}

配置文件

application.properties

server.port=8080
# 激活环境,默认为default,激活环境可以为多个
spring.profiles.active=test
# 指定默认环境,但不推荐
#spring.profiles.default=test
# 包含指定环境,不管激活哪个环境,包含的环境总是生效
#spring.profiles.include=dev,test
# 环境分组
#spring.profiles.group.mysql=prod,test
#spring.profiles.group.psql[0]=dev
#spring.profiles.group.psql[1]=test

application-dev.properties

server.port=8081

application-prod.properties

server.port=8082

application-test.properties

server.port=8083

启动类

/**
 * 1.标识环境:
 *   a.区分出几个环境:dev(开发环境)、test(测试环境)、prod(生产环境)
 *   b.指定每个组件在哪个环境下生效; default环境:默认环境,通过:@Profile({"test"})标注,组件没有标注@Profile代表任意环境都生效
 *   c.默认只有激活指定的环境,这些组件才会生效
 * 2.激活环境
 *   a.配置文件激活:spring.profiles.active=dev;
 *   b.命令行激活: java -jar xx.jar --spring.profiles.active=dev
 * 3.配置文件使用Profile功能
 *   a.application.properties: 主配置文件。任何情况都生效
 *   b.其他Profile环境下命名规范:application-{profile标识}。properties: 比如application-dev.properties
 *   c.效果:项目的所有生效配置项=激活环境配置文件的所有项+主配置文件和激活文件不冲突的所有项
 *          朱配置和激活的配置都生效,优先以激活配置为准
 *
 */
@Slf4j
@SpringBootApplication
public class FeaturesApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder()
                .main(FeaturesApplication.class)
                .sources(FeaturesApplication.class)
                .bannerMode(Banner.Mode.OFF)
                .run(args);

        try{
            Cat cat = context.getBean(Cat.class);
            log.info("组件cat:{}", cat);
        } catch (Exception e) {
            log.error("组件cat不在容器中");
        }
        try{
            Dog dog = context.getBean(Dog.class);
            log.info("组件dog:{}", dog);
        } catch (Exception e) {
            log.error("组件dog不在容器中");
        }
        try{
            Pig pig = context.getBean(Pig.class);
            log.info("组件pig:{}", pig);
        } catch (Exception e) {
            log.error("组件pig不在容器中");
        }
        try{
            Sheep sheep = context.getBean(Sheep.class);
            log.info("组件sheep:{}", sheep);
        } catch (Exception e) {
            log.error("组件sheep不在容器中");
        }
    }
}

外部化配置

SpringBoot使用配置优先级+外部配置简化配置更新、简化运维

配置优先级

Spring Boot允许你将应用配置外部化,这样就可以在不同环境中使用同一套应用程序代码。

你可以使用多种外部配置来源,包括Java Properties配置文件、YAML文件、环境变量以及命令行参数。

属性值可以通过@Value注解直接注入到Bean中,也可以通过Spring提供的Environment抽象来访问,还可以通过@ConfigurationProperties将配置属性绑定到结构化对象中。

SpringBoot对PropertySource定义了一套明确的优先级顺序,其设计目的是让配置值能够以合理的方式被覆盖。后面的属性源可以覆盖前面属性源中定义的值。各配置来源按照以下顺序进行处理:

  1. 默认属性,即通过SpringApplication.setDefaultProperties(Map)设置的属性。
  2. 在@Configuration配置类上通过@PropertySource注解加载的属性。
  3. 配置数据(Config Data),例如applicaion.properties这种配置文件。
  4. RandomValuePropertySource,它只提供random.*相关属性。
  5. 操作系统(OS)环境变量。
  6. Java系统属性,即System.getProperties()。
  7. 来自java:comp/env的JNDI属性。
  8. ServletContext初始化参数。
  9. ServletConfig初始化参数。
  10. 来自SPRING_APPLICATION_JSON的属性,即嵌入在环境变量或系统属性中的内联JSON配置。
  11. 命令行参数。
  12. 测试中的properties属性,可用于@SpringBootTest以及针对应用特定功能切片进行测试的相关测试注解。
  13. 测试中的@DynamicPropertySource注解。
  14. 测试中的@TestPropertySource注解。
  15. 当Devtools被启用时,位于$HOME/,config/spring-boot目录中的Devtools全局配置属性。
注:
虽然有15个层级,但常规使用配置时,只要记住命令行 > 配置文件 > SpringApplication手动配置即可

配置数据文件(Config Data Files)按照以下顺序进行处理:

  1. 打包在jar内部的普通应用配置文件,如:application.properties
  2. 打包在jar内部的Profile专属应用配置文件,如:application-{profile}.properties
  3. 位于打包jar外部的普通应用配置文件,如:application.properties
  4. 位于打包jar外部的Profile专属应用配置文件,如:application-{profile}.properties
注:
建议用一种格式的配置文件。如果.properties和.yml同时存在,则.properties优先。

优先级为包外>保内;同级情况profile配置>application配置。

所有参数均可由命令行传入,使用--参数项=参数值,将会被添加到环境变量中,并优先于配置文件。
比如:java -jar app.jar --name="Spring",可以使用@Value("${name}")获取

外部配置

SpringBoot应用启动时会自动寻找application.properties和application.yaml位置,进行加载。顺序如下(后面覆盖前面):

  1. 从classpath(类路径)中查找:
    1. classpath根目录
    2. classpath下的/config目录
  2. 从当前目录中(项目所在路径)查找:
    1. 当前目录
    2. 当前目录下的config/子目录
    3. config/子目录的直接子目录
最终效果:
优先级:命令行>包外config直接子目录>包外config目录>包外根目录>包内目录

同层级优先级:
profile配置>默认配置
properties配置>yaml配置

1

导入配置

应用配置可以通过spring.config.import属性,从其他位置导入更多的配置数据。

这些导入项会在被发现时立即处理,并且会被视为额外的配置文档,插入到声明该spring.config.import的配置文档之后参与配置加载。

例如,你可以在classpath下的application.properties文件中配置如下内容:

spring.application.name=myapp spring.config.import=optional:file:./dev.properties

这会触发对当前目录中dev.properties文件的导入(前提是文件存在)。

从dev.properties中导入的配置值,其优先级高于触发该导入的配置文件中的值。

在properties或YAML文件的同一个配置文档中,spring.config.import属性具体写在什么位置并不重要。

例如,下面两个配置的最终效果完全相同:

spring.config.import=my.properties
my.property=value
和
my.property=value
spring.config.import=my.properties

在这两种情况下,my.properties中的配置值都会优先于触发导入的配置文件中的值。
一个spring.config.import属性还可以同时指定多个导入位置。
这些位置会按照定义顺序依次处理,并且后面导入项具有更高优先级。

属性占位符

配置文件中可以使用${name:default}形式取出之前配置过的值

# ${name:default}:获取name配置参数的值,如果没有取默认配置的值default
test.hello=my port is ${server.port},my name is ${name:Alex}

单元测试

整合测试依赖

Spring Boot提供了许多工具类和注解,用于帮助开发者测试应用程序。测试支持主要由两个模块提供:

  • spring-boot-test:包含测试相关的核心功能。
  • spring-bot-test-autoconfigure:为测试场景提供自动配置支持。

大多数开发者会使用spring-boot-starter-test这个Starter依赖:

        <!--测试场景-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

它会同时引入Spring Boot的两个测试模块,以及以下常用测试库:

  • JUnit 5:Java应用单元测试的事实标准框架。
  • Spirng Test和Spring Boot Test:为Spring Boot应用提供测试工具和集成测试支持。
  • AssertJ:提供流式断言能力的测试断言库。
  • Hamcrest:提供Matcher对象(也称为约束或谓词)的测试库。
  • Mockito:Java Mock模拟框架。
  • JSONassert:用于JSON结构断言的测试库。
  • JSONPath:类似XPath的JSON查询工具。
  • Awaitility:用于测试异步系统的测试库。

通常情况下,我们认为这些常用库能够满足大多数测试编写需求。如果这些库不满足你的需求,你也可以自行添加额外的测试依赖。

组件测试

直接通过@Autowired注解,依赖注入容器中的组件进行测试

测试注解

这里的测试注解是JUnit5的注解

注解 说明
@Test 表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试会由Jupiter提供额外测试
@ParameterizedTest 表示方法是参数化测试
@RepeatedTest 表示方法可重复执行
@DisplayName 为测试类或者测试方法设置展示名称
@BeforeEach 表示在每个单元测试之前执行
@BeforeAll 表示在每个单元测试之后执行
@BeforeAll 表示在所有单元测试之前执行
@AfterAll 表示在所有单元测试之后执行
@Tag 表示单元测试类别,类似于JUnit4的@Categories
@Disabled 表示测试类或测试方法不执行,类似于JUnit4的@Ignore
@Timeout 表示测试方法运行如果超过了指定时间将会返回错误
@ExtendWith 为测试类或测试方法提供扩展类引用

常见断言方法

方法 说明
assertEquals 判断两个对象或两个原始类型是否相等
assertNotEquals 判断两个对象或两个原始类型是否不相等
assertSame 判断两个对象引用是否指向同一个对象
assertNotSame 判断两个对象引用是否指向不同的对象
assertTrue 判断给定的布尔值是否为true
assertFalse 判断给定的布尔值是否为false
assertNull 判断给定的对象引用是否为null
assertNotNull 判断给定的对象引用是否不为null

嵌套测试

JUnit5可以通过Java中的内部类和@Nested注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach和@AfterEach注解,而且嵌套的层次没有限制。

参数化测试

参数话测试是JUnit5很重要的一个新特性,它使得不同的参数多次运行测试成为了可能,也为我们的单元测试带来很多便利。

利用@VlueSource等注解,指定入参,我们可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

注解 说明
@ValueSource 为参数话测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource 表示为参数化测试提供一个null的入参
@EnumSource 表示为参数化测试提供一个枚举入参
@CsvFileSource 表示读取指定CSV文件内容作为参数化测试入参
@MethodSource 表示读取指定方法的返回值作为参数化测试入参(方法返回需要是一个流)

测试示例

业务组件类

@Service
public class HelloService {
    public int sum(int a, int b) {
        return a + b;
    }
}

验证组件测试、测试注解、断言、参数化测试的测试类

//测试类也必须在主程序所在的包及其子包
@SpringBootTest //具备测试SpringBoot应用容器中所有组件的功能
public class FeaturesApplicationTest {

    @Autowired //自动注入任意组件即可
    private HelloService helloService;

    @DisplayName("测试1")
    @Test
    void contextLoads() {
        System.out.println("Hello World");
    }

    @DisplayName("测试2")
    @Test
    void testHelloService() {
        System.out.println(helloService.sum(1, 2));
        // 判断两个对象或两个原始类型是否相等
        Assertions.assertEquals(4, helloService.sum(1, 2));
    }

    @BeforeAll //所有测试方法运行之前运行这个
    public static void beforeAll() {
        System.out.println("Before All Test");
    }

    @BeforeEach //每个测试方法运行之前先运行这个
    public void beforeEach() {
        System.out.println("Before Each Test");
    }

    @ParameterizedTest
    @ValueSource(strings = {"one", "two", "three"})
    @DisplayName("参数化测试")
    public void parameterizedTest1(String string) {
        System.out.println(string);
        Assertions.assertTrue(StringUtils.isNotBlank(string));
    }

    @ParameterizedTest
    @MethodSource("method")     //指定⽅法名,返回值是测试用的参数
    @DisplayName("⽅法来源参数")
    public void testWithExplicitLocalMethodSource(String name) {
        System.out.println(name);
        Assertions.assertNotNull(name);
    }

    static Stream<String> method() {
        return Stream.of("apple", "banana");
    }
}

验证嵌套测试的测试类

// 嵌套测试类
@SpringBootTest
public class NestTest {
    private Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {
        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped() {
            assertThrows(EmptyStackException.class, stack::pop);
        }

        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            assertThrows(EmptyStackException.class, stack::peek);
        }

        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {
            private String anElement = "an element";

            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }

            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped() {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }
}

参考资料

SpringApplication :: Spring Boot

Profiles :: Spring Boot

Externalized Configuration :: Spring Boot

Testing :: Spring Boot

https://docs.junit.org/6.1.3/overview.html

posted @ 2026-09-06 21:02  柯南。道尔  阅读(11)  评论(0)    收藏  举报