Spring Boot 基础,配置与启动

本文最后更新于 2025年7月15日 上午

Spring Boot 核心思想

基于Spring框架派生的脚手架

SpringBoot 提供了各种技术组件的一站式启动器,开发者只要定义好对应技术组件的配置参数,SpringBoot 就会进行自动配置

SpringBoot 框架的核心思想是:约定大于配置,即按照约定进行编程

1.独立运行

Spring Boot通过内置servlet容器 可以直接完成 web应用的部署

2.简化配置

通过注解和SPI的方法简化了配置过程,并且允许用户自定义自动配置启动的部署过程。

3.自动配置

提供了自动配置功能,通过自动配置注解完成 对应对应依赖的查询,实例化以及装载过程,可以快速的开箱即用

配置类定义

1
使用SpringBootConfiguration注解 定义SpringBoot 配置类

SpringBootConfiguration 只是Cofiguration 注解的别名,用于区分SpringBoot与原生Spring应用

1
2
3
4
5
6
7
8
9
@Taget({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration{
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true ;
}

配置

application

application (注册.properties 文件到spring项目中,从文件中读取字符串信息用于对对象进行赋值)

springboot 通过查询 配置文件信息加载值到对应的bean变量声明中

1
2
static final String CONFIG_NAME_PROPERTY = "spring.config.name";
static final String[] DEFAULT_CONFIG_NAMES = { "application" };

配置格式

.properties :

1
2
server.port = 8080
server.servlet.contextPath = /javastack

.yml

1
2
3
4
server : 
port : 8080
servlet :
contextPath : /javastack

bootstrap

bootstrap 文件用于在加载阶段,为Spring Cloud 项目提供环境配置信息

向Bean对象中进行注入

使用@Value注解 和 Spel 表达式完成 值对属性的注入

1
2
3
4
5
6
7
8
@Value("${user.username}")
private String username ;

@Value("${user.age}")
private int age ;

@Value("${user.sex}")
private int sex ;

使用@PropertySource 执行需要注入的properties文件(默认application.properties)

1
2
3
4
5
6
7
8
9
@Data
@Component
@PropertySource(value = {"config/db-config.properties"})
public class DbProperties {
@Value("${db.username}")
private String username ;
@Value("{db.password}")
private String password ;
}

使用 @ConfigurationProperties 完成注入

1
2
3
4
5
6
7
8
9
10
11
12
@Data
@ConfigurationProperties( prefix ="javastack")
public class JavastackProperties{
private boolean enabled ;
private String name ;
private String site ;
private String author ;
private List<String> users ;
private Map<String , String> params ;
private Security security ;

}

在启动类中添加@EnableConfigurationProperties 注解,用于从目标类中创建/配置Bean ,如果有需要将该Bean注入到 被注解类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootApplication
@RequiredArgsConstructor
@EnableConfigurationProperties(value = {JavastackProperties.class})
@Slf4j
public class Application{
private final JavastackProperties javastatckProperties ;

@Bean
public CommandLineRunner commandLineRunner(){
return( args ) -> {
log.info("javastack properties:{}", javastackProperties);
};
}
}

构造器绑定

定义配置参数

1
2
3
4
5
member :
name : Tom
sex : 1
age : 18
birthday : 2000-12-12 12:00:00

创建一个MemberProperties参数类进行绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Data
@NoArgsConstructor
@ConfigurationProperties(prefix = "member")
public class MemberProperties {
private String name ;
private int sex ;
private int age ;
private String country ;
private Date birthday ;

public MemberProperties(String name , int sex , int age){
this.name = name ;
this.sex = sex ;
this.age = age ;
}
@ConstructorBinding
public MemberProperties(String name ,
int sex ,
int age ,
@DefaultValue("Chiane") String country ,
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday){
this.name = name ;
this.sex = sex ;
this.age = age ;
this.country = country ;
this.birthday = birthday ;
}
}

在@Bean 中使用 @ConfigurationProperties 完成 属性配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
@NoArgsConstructor
public class OtherMember {
private String name ;
private int sex ;
private int age ;
}

@SpringBootConfiguration
public class MainConfig {
@Bean
@ConfigurationProperties(prefix = "member")
public OtherMember otherMember(){
return new OtherMember();
}
}

在应用类中进行 相关调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpringBootApplication
@RequiredArgsConstructor
@EnableConfigurationProperties(value = {JavastackProperties.class, MemberProperties.class})
@Slf4j
public class Application {
private final JavastackProperties javastackProperties ;
private final MemberProperties memberProperties ;
private final OtherMember otherMember ;

@Bean
public CommandLineRunner commandLineRunner() {
return (args) -> {
log.info("javastack properties : {}", javastackProperties);
log.info("member properties : {}", memberProperties);
log.info("other member: {}", otherMember);
}
}
}

参数类扫描

1
2
3
4
5
6
7
@SpringBootApplication
@RequiredArgsConstructor
@ConfigurationPropertiesScan // 扫描当前包中的配置类信息
@Slf4j
public class Application {

}

Profile

使用profile指定环境配置信息

1
2
3
spring :
profiles :
default : dev
1
2
3
spring : 
profiles :
active : dev, test

加载对应的Profile信息

1
2
3
4
5
6
@Profile("main")
@SpringBootConfiguration
@Import({Configuration1.class , Configuration2.class})
public class MainConfig{

}

自动装配

Spring Boot的自动装配过程

SpringBoot 通过 @EnableAutoConfiguration完成自动装配过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackeage
// 注册需要自动配置的包,如果不指定技术当前注解所在的包
@Import(AutoConfigurationImportSelector.class)
// 导入配置类
public @interface EnableAutoConfiguration{
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

Class<?>[] exclude() default {};

String[] excludeName() default {};
}

实现的ImportSelector 接口描述

1
2
3
4
5
6
7
8
9
public interface ImportSelector{
String[] selectImports(AnnotationMetadata importingClassMetadata);

@Nullable
default Predicate<String> getExclusionFilter(){
return null;
}
}

方法实现

1
2
3
4
5
6
7
8
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata){
if(!isEnabled(annotationMetadata)){
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

在2.7中的实现:

1
2
3
4
5
6
7
8
9
10
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
//1.获取自动配置列表(Spring Boot 2.7)
List<String> configurations = new ArrayList<>(
SpringFactories.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
//2.导入候选自动配置列表
ImportCanditates.load(AutoConfiguration.class , getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,"No auto configuration classes found ...");
return configurations;
}

3.0 :

1
2
3
4
5
6
7
protected List<String> getCandidateConfigurations(AnnotationMedata metadata,
AnnotationAttributes attributes){
List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
.getCandidates();
Assert.notEmpty(configurations,"No auto configuration classes found ...");
return configurations;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public static List<String> LoadFactoryNames(Class<?> factoryType , @Nullable ClassLoader classLoader) {
return forDefaultResourceLoader(classLoader).loadFactory(factoryType);
}

public static SpringFactoriesLoader forDefaultResourceLocation(@Nullable ClassLoader classLoader){
return forResourceLocation(classLoader, FACTORIES_RESOURCE_LOCATION);
}

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static SpringFactoriesLoader forResourceLocation(@Nullable ClassLoader classLoader, String resrouceLocation){
Assert.hasText(resourceLocation, "'resourceLocation' must not be empty");
ClassLoader resourceClassLoader = (classLoader != null) ? classLoader : SpringFactoriesLoader.class.getClassLoader();
Map<String , SpringFactoriesLoader> loaders = SpringFactoriesLoader.cache.get(resourceClassloader);
if(loaders == null){
loaders = new ConcurrentReferenceHashMap<>();
SpringFactoriesLoader.cache.put(resourceClassLoader,loaders);
}
SpringFactoriesLoader lodaer = loaders.get(resourceLocation);
if(loader == null){
Map<String, List<String>> factories = loadFactoriesResource(resourceClassLoader, resourceLocation);
loader = new SpringFactoriesLoader(classLoader, factories);
loaders.put(resourceLocation,loader);
}
return loader;
}

static Map<String , List<String>> loadFactoriesResource(ClassLoader classLoader , String resourceLocation){
Map<String, List<String>> result = new LinkedHashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(resourceLocation);
while(urls.hasMoreElements()){
UrlResource resource = new UrlResource(urls.nexElement());
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
properties.forEach((name, value) -> {
List<String> implementations = result.computeIfAbsent(((String) name),trim(),
key -> new ArrayList<>());
Arrays.stream(StringUtils.commaDelimitedListToStringArray((String)vale))
.map(Strintg::trim).forEach(implementations::add);
});
}
result.replaceAll(SpringFactoriesLoader::toDistinctUnmodifiableList);
}
catch(IOException ex){
throw new IllegalArgumentException("Unable to load factories from location ...");
}
return Collections.unmodifiableMap(result);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static final String LOCATION = "META-INF/spring/%s.imports";

public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader){
Assert.notNull(annotation, "ananotation must not be null");
ClassLoader classLoaderToUse = decideClassloader(classLoader);
String location = String.format(LOCATION,annotation.getName());
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse , location);
List<String> importCandidates = new ArrayList<>();
while(urls.hasMoreElements()){
URL url = urls.nextElement();
importCandidates.addAll(readACandidateConfigurations(url));
}
return new ImportCandidates(importCandidates);
}

从 加载位置加载自动配置类进行注册

以DataSrouce 为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@AutoConfiguration(before = SqlInitializationAutoConfiguration.class)
@ConditionalOnClass({DataSrouce.class , EmbeddedDatabaseType.class})
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import(DataSourcePoolMetadataProvidersConfiguration.class)
public class DataSourceAutoConfiguration{
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({DataSource.class,XADAataSource.class})
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmebddedDatabaseConfiguration{
}
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({DataSource.class, XADataSource.class})
@Import({DataSourceConfiguration.Hikari.class , DataSourceConfiguration.Tomcat.class ,
DataSourceConfiguration.Dbcp2.class , DataSourceConfiguration.OracleUcp.class ,
DataSourceConfiguration.Generic.class , DataSourceJmxConfiguration.class } )
protected static class PooledDataSourceConfiguration{

}
...
}

@ConditionalOn 注解

当满足条件时 触发配置类的自动配置

1
2
3
4
5
6
7
8
9
@Target({ElementType.TYPE,  ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
Class <?>[] value() default {};
String[] name() default {};
}

exclude

排除部分类的加载

1
2
3
4
5
@EnableAutoConfiguration(exclude = {})
public class Applicaiton{

}
...

Spring Boot 启动过程

1 初始化SpringApplication 从spring.factories 读取listener ApplicationContextInitializer

2 运行run方法

3 读取环境信息 配置信息

4 创建SpringApplication上下文

5 预初始化上下文: 读取启动类

6 调用refresh加载IOC容器 加载所有的自动配置类 创建servlet 容器

7 在这个过程中springboot会调用很多监听器对外进行扩展

创建的Spring Boot 应用 通过main方法启动

1
2
3
4
5
6
@SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
1
2
3
4
5
6
7
8
9
10
@Target(ElementType.TYPE)
@Retetion(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM , classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigrationExcludeFilter.class})
public @interface SpringBootApplication{
}

@SpringBootConfiguration alias为@Configuration

@EnableAutoConfiguration 启动自动配置功能

@ComponentScan 自动扫描Spring组件

Springboot 会通过一个run方法 来启动程序

1
2
3
4
public static ConfigurableApplicationContext run(Class<?>[] primarySources , String[] args){
return new SpringApplication(primarySources).run(args);
}

创建Spring Application 应用 ,进行相关的资源加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.additionalProfiles = Collections.emptySet();
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.properties = new ApplicationProperties();
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
// 装载启动类到容器
this.properties.setWebApplicationType(WebApplicationType.deduceFromClasspath());
// 推断应用类型
this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 从spring.factories 中去获取所有key:BootstrapRegistryInitializer 进行资源绑定
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 从spring.factories 中去获取所有key:ApplicaitonContextInitializer 进行资源绑定
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
// 从spring.factories 中获取所有的key:ApplicaitonListener 进行资源绑定
this.mainApplicationClass = this.deduceMainApplicationClass();
// 根据main方法推算出mainApplicationClass
}

加载类资源后,进行应用类型推断

1
2
3
4
5
6
7
8
9
10
11
12
static WebApplicationType deduceFromClasspath() {
if(ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)){
return WebApplicationType.REACTIVE;
}
for(String className : SERVLET_INDICATOR_CLASSES){
if(!ClassUtils.isPresent(className, null)){
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}

最终在实例化过程中组织了 SpringApplication 对象。 执行run方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public ConfigurableApplicationContext run(String... args) {
Startup startup = SpringApplication.Startup.create();
// 定义启动计时统计
if (this.properties.isRegisterShutdownHook()) {
shutdownHook.enableShutdownHookAddition();
}
// 关闭钩子,触发销毁方法
// 环境配置信息上下文
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
// 任何spring 上下文的接口,可以接收任何springcontext 实现
ConfigurableApplicationContext context = null;
// 无头模式,部分IO设备未正常加载时,进行的设置。
this.configureHeadlessProperty();
// 去spring.factoies中读取了SpringApplicationListeners组件 用于发布事件 或者运行监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
// 发布启动事件
try {
// 根据命令行参数 实例化一个ApplicationArguments 的对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 预初始化环境,设置环境变量
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 横幅信息
Banner printedBanner = this.printBanner(environment);
// 创建应用上下文(ApplicationContext),负责管理所有的 Bean。
context = this.createApplicationContext();'
// 设置应用启动监控器,用于记录启动过程中的各种指标。
context.setApplicationStartup(this.applicationStartup);
// 环境准备 设置环境变量,应用初始化器,注册监听器,加载著配置类
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 加载/解析配置,创建和初始化Bean,解决依赖关系,启动内嵌服务器
this.refreshContext(context);
// 执行刷新后的回调处理,标记启动阶段完成。
this.afterRefresh(context, applicationArguments);
startup.started();
if (this.properties.isLogStartupInfo()) {
(new StartupInfoLogger(this.mainApplicationClass, environment)).logStarted(this.getApplicationLog(), startup);
}

listeners.started(context, startup.timeTakenToStarted());
this.callRunners(context, applicationArguments);
} catch (Throwable ex) {
throw this.handleRunFailure(context, ex, listeners);
}

try {
if (context.isRunning()) {
listeners.ready(context, startup.ready());
}

return context;
} catch (Throwable ex) {
throw this.handleRunFailure(context, ex, (SpringApplicationRunListeners)null);
}
}

prepareEnvironment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 加载环境信息
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
// 向加载的环境信息中进行属性赋值
this.configureEnvironment(environment, applicationArguments.getSourceArgs());
// 更准确的描述:将环境转换为配置属性源并附加到环境中
ConfigurationPropertySources.attach(environment);
// 发起事件
listeners.environmentPrepared(bootstrapContext, environment);
// 属性信息配置是一个查询过程,而不是一个重复加载过程
ApplicationInfoPropertySource.moveToEnd(environment);
// 将环境配置信息加载到最后,最低优先级配置
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
this.bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
EnvironmentConverter environmentConverter = new EnvironmentConverter(this.getClassLoader());
environment = environmentConverter.convertEnvironmentIfNecessary(environment, this.deduceEnvironmentClass());
}

ConfigurationPropertySources.attach(environment);
return environment;
}

prepareContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// 设置环境信息
context.setEnvironment(environment);
this.postProcessApplicationContext(context);
this.addAotGeneratedInitializerIfNecessary(this.initializers);
this.applyInitializers(context);
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.properties.isLogStartupInfo()) {
this.logStartupInfo(context.getParent() == null);
this.logStartupInfo(context);
this.logStartupProfileInfo(context);
}

ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}

if (beanFactory instanceof AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) {
autowireCapableBeanFactory.setAllowCircularReferences(this.properties.isAllowCircularReferences());
// 在Sping中 如果出现重名的bean 后出现的会覆盖前面的
// 在SpringBoot中 设置了不允许覆盖
if (beanFactory instanceof DefaultListableBeanFactory listableBeanFactory) {
listableBeanFactory.setAllowBeanDefinitionOverriding(this.properties.isAllowBeanDefinitionOverriding());
}
}
// 设置 spring容器 是不是懒加载
if (this.properties.isLazyInitialization()) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}

if (this.properties.isKeepAlive()) {
context.addApplicationListener(new KeepAlive());
}

context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
if (!AotDetector.useGeneratedArtifacts()) {
Set<Object> sources = this.getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
this.load(context, sources.toArray(new Object[0]));
}
// 读取主启动类
listeners.contextLoaded(context);
// 读取完毕后 发送ApplicationPreparedEvent
}

事件与监听过程

在Spring Boot 中通过构建监听器,在执行过程中在不同的阶段触发对应的监听器,来完成数据和资源的准备工作

观察者模式

观察者模式 提供了一种 注册-通知机制。在流程开始时,将所有关注被观察对象的对象注册到观察者容器中,当被观察对象状态发生变化时,遍历观察者容器中的对象,执行其中的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
interface Subject {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}

// 观察者接口
interface Observer {
void update(String message);
}

// 具体主题
class NewsAgency implements Subject {
private List<Observer> observers = new ArrayList<>();
private String news;

@Override
public void addObserver(Observer observer) {
observers.add(observer);
}

@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(news); // 通知所有观察者
}
}

public void setNews(String news) {
this.news = news;
notifyObservers(); // 状态改变时自动通知
}
}

// 具体观察者
class NewsChannel implements Observer {
private String name;

@Override
public void update(String news) {
System.out.println(name + " 收到新闻: " + news);
}
}

// 使用示例
NewsAgency agency = new NewsAgency();
agency.addObserver(new NewsChannel("CNN"));
agency.addObserver(new NewsChannel("BBC"));
agency.setNews("重大新闻!"); // 自动通知所有频道

事件与监听

Spring boot 中 实现了更加复杂的观察者模式

定义了一个事件对象

1
2
3
4
5
public class MyCustomEvent extends ApplicationEvent {
public MyCustomEvent(Object source) {
super(source);
}
}

监听器通过实现ApplicationListener 接口,重写onApplicationEvent 方法来定义事件发生时所需要调用的方法

1
2
3
4
5
6
7
8
@Component
public class MyEventListener implements ApplicationListener<MyCustomEvent> {
@Override
public void onApplicationEvent(MyCustomEvent event) {
System.out.println("收到事件:" + event.getSource());
}
}

通过ApplicationEventPublisher 对象 发布事件

1
2
3
4
5
6
7
@Autowired
private ApplicationEventPublisher publisher;

public void doSomething() {
publisher.publishEvent(new MyCustomEvent("自定义事件"));
}

在SpringBoot 启动过程中,

1 在创建SpringApplication 对象阶段,检索.factories中的实现了ApplicationListener 接口的类定义,建立映射关系

2 在SpringApplicationRunListeners listeners = this.getRunListeners(args)过程中,创建事件广播其与监听器进行绑定,将所有监听器实例注册到 listeners容器中。

3 使用listeners.function , 触发事件,发布对应的事件类型,在容器中遍历监听器,执行定义的对应事件触发功能。

在启动过程中会发布的事件有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ApplicationStartingEvent
这个事件在Spring Boot 应用运行开始时,且进行任何处理之前发送
ApplicationEnvironmentPreparedEvent
这个事件在当已知要在上下文中使用Spring环境 Environment时,在Spring上下文context创建前发送
ApplicationContextInitializedEvent
这个事件在Spring应用上下文ApplicationContext准备好了,并且应用初始化器Application-ContextInitializers已经被调用
在bean definitions被加载前发送
ApplicationPreparedEvent
这个事件是在Spring上下文context刷新之前,且在bean definitions被加载之后发送
ApplicationStartEvent
这个事件是在Spring上下文coontext刷新后,且在任何application/command-line runners被调用之前发送
AvailabilityChangeEvent
这个事件紧随上个事件之后发送,激活状态ReadinessState.CORRECT 表示应用已处于活动状态
ApplicationReadyEvent
这个事件在任何application/command-line runners 调用之后发送
AvailabilityChangeEvent
这个事件紧随上个事件之后发送,激活状态ReadinessState.ACCEPTING_TRAFFIC,表示应用可以开始准备接收请求了
ApplicationFailedEvent
这个事件在应用启动异常时发送。
WebServerInitializedEvent
这个Web服务器初始化事件在WebServer启动之后发送,实现事件类包括ServletWebServerInitializedEvent,
ReactiveWebServerInitializedEvent 事件
ContextRefreshedEvent
这个上下文刷新事件在Spring应用上下文ApplicationContext刷新之后发送

Spring Boot 基础,配置与启动
http://gadoid.io/2025/07/15/Spring-Boot-基础,配置与启动/
作者
Codfish
发布于
2025年7月15日
许可协议