Spring Boot 3.2 启动原理:从 main() 到 Web 应用的完整旅程
为什么Spring Boot应用只需要一个main方法就能启动Web服务器?背后发生了什么魔法?今天我们一起揭开神秘面纱。
一、Spring Boot启动全景图
让我们先看一张完整的启动流程图,建立宏观认知:
二、启动入口:SpringApplication.run()
一切从这行熟悉的代码开始:
@SpringBootApplication
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
1. SpringApplication 的构造过程
跟踪SpringApplication.run()源码,我们看到:
// SpringApplication.java
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
关键构造步骤:
-
推断应用类型
WebApplicationType.deduceFromClasspath():通过类路径判断- SERVLET:有
javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext - REACTIVE:有
org.springframework.web.reactive.DispatcherHandler - NONE:非Web应用
-
设置初始器(Initializers)
- 从
META-INF/spring.factories加载ApplicationContextInitializer - 在
ApplicationContext刷新前执行,用于上下文初始化
- 从
-
设置监听器(Listeners)
- 同样从
spring.factories加载ApplicationListener - 监听Spring事件,在启动各个阶段执行
- 同样从
让我们通过代码验证这个过程:
// 自定义监听器,跟踪启动过程
@Component
@Slf4j
public class StartupTraceListener implements ApplicationListener<ApplicationEvent> {
private final Map<String, Long> startTime = new ConcurrentHashMap<>();
@Override
public void onApplicationEvent(ApplicationEvent event) {
String eventName = event.getClass().getSimpleName();
if (event instanceof ApplicationStartingEvent) {
startTime.put("total", System.currentTimeMillis());
log.info("🚀 应用开始启动: {}", event.getTimestamp());
}
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
log.info("🌍 环境准备完成,耗时: {}ms",
System.currentTimeMillis() - startTime.get("total"));
}
else if (event instanceof ApplicationContextInitializedEvent) {
log.info("🔧 ApplicationContext 初始化完成,耗时: {}ms",
System.currentTimeMillis() - startTime.get("total"));
}
else if (event instanceof ApplicationPreparedEvent) {
log.info("⚙️ Application准备就绪,耗时: {}ms",
System.currentTimeMillis() - startTime.get("total"));
}
else if (event instanceof ContextRefreshedEvent) {
log.info("✅ ApplicationContext 刷新完成,耗时: {}ms",
System.currentTimeMillis() - startTime.get("total"));
}
else if (event instanceof ApplicationStartedEvent) {
log.info("🚀 应用启动完成,总耗时: {}ms",
System.currentTimeMillis() - startTime.get("total"));
}
else if (event instanceof AvailabilityChangeEvent<?> avEvent) {
if (avEvent.getState().equals(ReadinessState.ACCEPTING_TRAFFIC)) {
log.info("🌐 应用已就绪,可接收流量,总耗时: {}ms",
System.currentTimeMillis() - startTime.get("total"));
}
}
}
}
运行结果:
2024-05-20 10:00:00.000 INFO 12345 --- [main] c.e.demo.listener.StartupTraceListener : 🚀 应用开始启动: 1716170400000
2024-05-20 10:00:00.350 INFO 12345 --- [main] c.e.demo.listener.StartupTraceListener : 🌍 环境准备完成,耗时: 350ms
2024-05-20 10:00:01.150 INFO 12345 --- [main] c.e.demo.listener.StartupTraceListener : 🔧 ApplicationContext 初始化完成,耗时: 1150ms
2024-05-20 10:00:01.850 INFO 12345 --- [main] c.e.demo.listener.StartupTraceListener : ⚙️ Application准备就绪,耗时: 1850ms
2024-05-20 10:00:02.550 INFO 12345 --- [main] c.e.demo.listener.StartupTraceListener : ✅ ApplicationContext 刷新完成,耗时: 2550ms
2024-05-20 10:00:02.650 INFO 12345 --- [main] c.e.demo.listener.StartupTraceListener : 🚀 应用启动完成,总耗时: 2650ms
2024-05-20 10:00:02.750 INFO 12345 --- [main] c.e.demo.listener.StartupTraceListener : 🌐 应用已就绪,可接收流量,总耗时: 2750ms
可以看到Spring Boot启动的完整时间线。
三、核心阶段解析
阶段1:环境准备(Environment)
在prepareEnvironment()方法中:
// 创建并配置环境
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 1. 根据应用类型创建环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 2. 配置环境:处理命令行参数、系统属性、配置文件等
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 3. 发布ApplicationEnvironmentPreparedEvent事件
listeners.environmentPrepared(environment);
// 4. 将环境绑定到SpringApplication
bindToSpringApplication(environment);
return environment;
}
配置文件加载顺序(重要!):
1. 默认属性(通过SpringApplication.setDefaultProperties设置)
2. @PropertySource注解
3. 配置文件(application.properties/yml)
- 当前目录的/config子目录
- 当前目录
- 类路径的/config目录
- 类路径根目录
4. 随机属性(RandomValuePropertySource)
5. 操作系统环境变量
6. Java系统属性(System.getProperties())
7. JNDI属性(来自java:comp/env)
8. ServletContext初始化参数
9. ServletConfig初始化参数
10. SPRING_APPLICATION_JSON属性(内联JSON)
11. 命令行参数
我们可以通过代码验证配置加载顺序:
@Component
@Slf4j
public class ConfigOrderValidator {
@Value("${demo.config.source}")
private String configSource;
@PostConstruct
public void validateConfigOrder() {
log.info("✅ 最终生效的配置来源: {}", configSource);
// 获取所有属性源
ConfigurableEnvironment env = (ConfigurableEnvironment)
SpringContextHolder.getBean(Environment.class);
log.info("📋 所有属性源(按优先级从高到低):");
for (PropertySource<?> ps : env.getPropertySources()) {
log.info(" - {}", ps.getName());
}
}
}
application.yml:
demo:
config:
source: "配置文件"
# 测试不同位置的配置文件优先级
通过命令行启动:
java -jar app.jar --demo.config.source="命令行参数"
运行结果:
2024-05-20 10:00:01.500 INFO 12345 --- [main] c.e.demo.validator.ConfigOrderValidator : ✅ 最终生效的配置来源: 命令行参数
2024-05-20 10:00:01.501 INFO 12345 --- [main] c.e.demo.validator.ConfigOrderValidator : 📋 所有属性源(按优先级从高到低):
- commandLineArgs
- systemProperties
- systemEnvironment
- random
- Config resource 'class path resource [application.yml]'
验证了命令行参数优先级高于配置文件。
阶段2:ApplicationContext创建与准备
2.1 创建ApplicationContext
Spring Boot根据应用类型创建不同的ApplicationContext:
// 在SpringApplication的createApplicationContext方法中
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
// applicationContextFactory的create方法
ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
try {
return switch (webApplicationType) {
case SERVLET -> new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE -> new AnnotationConfigReactiveWebServerApplicationContext();
case NONE -> new AnnotationConfigApplicationContext();
};
} catch (Exception ex) {
// 异常处理
}
}
2.2 Bean定义加载
关键类:AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScanner
// 演示Bean定义扫描过程
@Component
@Slf4j
public class BeanDefinitionLogger implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
String[] beanNames = beanFactory.getBeanDefinitionNames();
log.info("📊 当前共有 {} 个Bean定义:", beanNames.length);
// 按类型统计
Map<String, Integer> typeCount = new HashMap<>();
for (String beanName : beanNames) {
BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
String source = bd.getResourceDescription() != null ?
bd.getResourceDescription() : "未知来源";
if (source.contains("spring-boot")) {
typeCount.merge("Spring Boot", 1, Integer::sum);
} else if (source.contains("springframework")) {
typeCount.merge("Spring Framework", 1, Integer::sum);
} else if (bd.getBeanClassName() != null &&
bd.getBeanClassName().startsWith("com.example")) {
typeCount.merge("业务Bean", 1, Integer::sum);
} else {
typeCount.merge("其他", 1, Integer::sum);
}
}
log.info("📈 Bean来源统计: {}", typeCount);
// 打印前10个Bean
log.info("🏆 前10个Bean定义:");
Arrays.stream(beanNames)
.limit(10)
.forEach(name -> {
BeanDefinition bd = beanFactory.getBeanDefinition(name);
log.info(" - {}: {}", name, bd.getBeanClassName());
});
}
}
运行结果:
2024-05-20 10:00:01.800 INFO 12345 --- [main] c.e.demo.processor.BeanDefinitionLogger : 📊 当前共有 152 个Bean定义:
2024-05-20 10:00:01.801 INFO 12345 --- [main] c.e.demo.processor.BeanDefinitionLogger : 📈 Bean来源统计: {Spring Boot=45, Spring Framework=89, 业务Bean=12, 其他=6}
2024-05-20 10:00:01.802 INFO 12345 --- [main] c.e.demo.processor.BeanDefinitionLogger : 🏆 前10个Bean定义:
- org.springframework.context.annotation.internalConfigurationAnnotationProcessor: org.springframework.context.annotation.ConfigurationClassPostProcessor
- org.springframework.context.annotation.internalAutowiredAnnotationProcessor: org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
- org.springframework.context.annotation.internalCommonAnnotationProcessor: org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
- org.springframework.context.event.internalEventListenerProcessor: org.springframework.context.event.EventListenerMethodProcessor
- org.springframework.context.event.internalEventListenerFactory: org.springframework.context.event.DefaultEventListenerFactory
- application: com.example.PaymentApplication$$SpringCGLIB$$0
- beanDefinitionLogger: com.example.demo.processor.BeanDefinitionLogger
- org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory: org.springframework.core.type.classreading.CachingMetadataReaderFactory
- propertySourcesPlaceholderConfigurer: org.springframework.context.support.PropertySourcesPlaceholderConfigurer
- org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor: org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
阶段3:刷新上下文(Refresh Context)
这是最核心、最复杂的阶段,对应AbstractApplicationContext.refresh()方法:
// AbstractApplicationContext.refresh() 主要步骤
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1. 准备刷新
prepareRefresh();
// 2. 获取BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 准备BeanFactory
prepareBeanFactory(beanFactory);
try {
// 4. 后处理BeanFactory
postProcessBeanFactory(beanFactory);
// 5. 执行BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// 6. 注册BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 7. 初始化消息源
initMessageSource();
// 8. 初始化事件广播器
initApplicationEventMulticaster();
// 9. 初始化特殊Bean
onRefresh();
// 10. 注册监听器
registerListeners();
// 11. 实例化所有非懒加载的单例Bean
finishBeanFactoryInitialization(beanFactory);
// 12. 完成刷新
finishRefresh();
} catch (BeansException ex) {
// 异常处理
destroyBeans();
cancelRefresh(ex);
throw ex;
} finally {
// 重置缓存
resetCommonCaches();
}
}
}
让我们重点关注几个关键步骤:
3.1 执行BeanFactoryPostProcessor
这里会处理@Configuration类、@ComponentScan、@Import等注解:
@Component
@Slf4j
public class BeanLifecycleTracker implements BeanPostProcessor, PriorityOrdered {
private final Map<String, Instant> creationTime = new ConcurrentHashMap<>();
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
creationTime.put(beanName, Instant.now());
log.info("🔧 Bean初始化前: {} (Class: {})",
beanName, bean.getClass().getSimpleName());
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
Instant start = creationTime.get(beanName);
if (start != null) {
long duration = Duration.between(start, Instant.now()).toMillis();
log.info("✅ Bean初始化完成: {} (耗时: {}ms)", beanName, duration);
}
return bean;
}
}
运行结果:
2024-05-20 10:00:02.100 INFO 12345 --- [main] c.e.demo.processor.BeanLifecycleTracker : 🔧 Bean初始化前: dataSource (Class: HikariDataSource)
2024-05-20 10:00:02.350 INFO 12345 --- [main] c.e.demo.processor.BeanLifecycleTracker : ✅ Bean初始化完成: dataSource (耗时: 250ms)
2024-05-20 10:00:02.351 INFO 12345 --- [main] c.e.demo.processor.BeanLifecycleTracker : 🔧 Bean初始化前: transactionManager (Class: DataSourceTransactionManager)
2024-05-20 10:00:02.352 INFO 12345 --- [main] c.e.demo.processor.BeanLifecycleTracker : ✅ Bean初始化完成: transactionManager (耗时: 1ms)
2024-05-20 10:00:02.400 INFO 12345 --- [main] c.e.demo.processor.BeanLifecycleTracker : 🔧 Bean初始化前: userController (Class: UserController)
2024-05-20 10:00:02.450 INFO 12345 --- [main] c.e.demo.processor.BeanLifecycleTracker : ✅ Bean初始化完成: userController (耗时: 50ms)
3.2 Spring Boot的魔法:自动配置(Auto-Configuration)
自动配置是Spring Boot的核心特性。在invokeBeanFactoryPostProcessors()阶段,ConfigurationClassPostProcessor会处理所有@Configuration类。
自动配置原理:
@SpringBootApplication包含@EnableAutoConfiguration@EnableAutoConfiguration通过@Import(AutoConfigurationImportSelector.class)导入自动配置AutoConfigurationImportSelector从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports加载配置
让我们看看实际效果:
@Component
@Slf4j
public class AutoConfigAnalyzer {
@Autowired
private ApplicationContext context;
@EventListener(ContextRefreshedEvent.class)
public void analyzeAutoConfigurations() {
// 获取所有自动配置类
String[] autoConfigs = context.getBeanNamesForType(
org.springframework.boot.autoconfigure.AutoConfiguration.class, false, false);
log.info("🔍 共加载了 {} 个自动配置类", autoConfigs.length);
// 按条件统计
Map<String, Integer> categoryCount = new HashMap<>();
for (String config : autoConfigs) {
if (config.contains("DataSource")) categoryCount.merge("数据源", 1, Integer::sum);
else if (config.contains("Web")) categoryCount.merge("Web", 1, Integer::sum);
else if (config.contains("Jackson")) categoryCount.merge("JSON", 1, Integer::sum);
else if (config.contains("Cache")) categoryCount.merge("缓存", 1, Integer::sum);
else if (config.contains("Security")) categoryCount.merge("安全", 1, Integer::sum);
else categoryCount.merge("其他", 1, Integer::sum);
}
log.info("📊 自动配置分类统计: {}", categoryCount);
// 查看条件注解判断结果
ConditionEvaluationReport report = ConditionEvaluationReport.get(
context.getBeanFactory());
log.info("📈 条件注解匹配结果:");
report.getConditionAndOutcomesBySource().forEach((source, outcomes) -> {
if (source.contains("AutoConfiguration")) {
outcomes.forEach(outcome -> {
if (!outcome.getOutcome().isMatch()) {
log.info(" ❌ 未匹配: {} - 原因: {}",
source, outcome.getOutcome().getMessage());
}
});
}
});
}
}
运行结果:
2024-05-20 10:00:02.600 INFO 12345 --- [main] c.e.demo.analyzer.AutoConfigAnalyzer : 🔍 共加载了 78 个自动配置类
2024-05-20 10:00:02.601 INFO 12345 --- [main] c.e.demo.analyzer.AutoConfigAnalyzer : 📊 自动配置分类统计: {数据源=5, Web=12, JSON=3, 缓存=4, 安全=2, 其他=52}
2024-05-20 10:00:02.602 INFO 12345 --- [main] c.e.demo.analyzer.AutoConfigAnalyzer : 📈 条件注解匹配结果:
❌ 未匹配: DataSourceAutoConfiguration - 原因: required property 'spring.datasource.url' not found
❌ 未匹配: RedisAutoConfiguration - 原因: required property 'spring.redis.host' not found
❌ 未匹配: SecurityAutoConfiguration - 原因: @ConditionalOnClass did not find required class 'org.springframework.security.authentication.AuthenticationManager'
阶段4:Web服务器启动
对于Web应用,onRefresh()方法会创建Web服务器:
// ServletWebServerApplicationContext.onRefresh()
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer(); // 创建Web服务器
} catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
// 获取ServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
// 创建WebServer
this.webServer = factory.getWebServer(getSelfInitializer());
}
内嵌Tomcat启动过程:
@Component
@Slf4j
public class TomcatStartupTracker {
@EventListener(WebServerInitializedEvent.class)
public void trackWebServerStart(WebServerInitializedEvent event) {
WebServer webServer = event.getWebServer();
log.info("🌐 Web服务器已启动:");
log.info(" - 类型: {}", webServer.getClass().getSimpleName());
log.info(" - 端口: {}", event.getWebServer().getPort());
if (webServer instanceof TomcatWebServer tomcatWebServer) {
Tomcat tomcat = tomcatWebServer.getTomcat();
Server server = tomcat.getServer();
// 打印Tomcat组件信息
for (Service service : server.findServices()) {
log.info(" - Service: {}", service.getName());
for (Connector connector : service.findConnectors()) {
log.info(" * Connector: {}:{}",
connector.getScheme(),
connector.getPort());
}
}
// 线程池信息
Executor executor = tomcat.getConnector().getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor tpe) {
log.info(" - 线程池: core={}, max={}, queueSize={}",
tpe.getCorePoolSize(),
tpe.getMaximumPoolSize(),
tpe.getQueue().size());
}
}
}
}
运行结果:
2024-05-20 10:00:02.700 INFO 12345 --- [main] c.e.demo.tracker.TomcatStartupTracker : 🌐 Web服务器已启动:
2024-05-20 10:00:02.701 INFO 12345 --- [main] c.e.demo.tracker.TomcatStartupTracker : - 类型: TomcatWebServer
2024-05-20 10:00:02.702 INFO 12345 --- [main] c.e.demo.tracker.TomcatStartupTracker : - 端口: 8080
2024-05-20 10:00:02.703 INFO 12345 --- [main] c.e.demo.tracker.TomcatStartupTracker : - Service: Tomcat
2024-05-20 10:00:02.704 INFO 12345 --- [main] c.e.demo.tracker.TomcatStartupTracker : * Connector: http:8080
2024-05-20 10:00:02.705 INFO 12345 --- [main] c.e.demo.tracker.TomcatStartupTracker : - 线程池: core=10, max=200, queueSize=0
四、Spring Boot 3.2 新特性在启动中的体现
1. 虚拟线程支持(JDK 21+)
Spring Boot 3.2支持虚拟线程,但需要JDK 21+:
# application.yml
spring:
threads:
virtual:
enabled: true # 启用虚拟线程
在启动时,Spring Boot会配置Tomcat使用虚拟线程:
@Configuration
@ConditionalOnThreading(Threading.VIRTUAL)
@ConditionalOnWebApplication(type = Type.SERVLET)
static class VirtualThreadsTomcatConfiguration {
@Bean
@ConditionalOnMissingBean
TomcatProtocolHandlerCustomizer<?> virtualThreadsProtocolHandlerCustomizer() {
return protocolHandler -> {
// 使用虚拟线程执行器
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}
2. 启动性能优化
Spring Boot 3.2在启动性能上做了多项优化:
// 查看启动性能统计
@Component
@Slf4j
public class StartupPerformanceReporter {
@EventListener(ApplicationReadyEvent.class)
public void reportStartupPerformance(ApplicationReadyEvent event) {
ApplicationContext context = event.getApplicationContext();
StartupInfoLogger startupInfoLogger = new StartupInfoLogger(
context.getEnvironment().getProperty("spring.main.application-startup", "default"));
// 获取启动步骤
if (context instanceof ConfigurableApplicationContext cac) {
ApplicationStartup startup = cac.getApplicationStartup();
if (startup instanceof FlightRecorderApplicationStartup flightRecorder) {
// 使用JDK Flight Recorder记录启动信息
log.info("📊 启动性能统计已记录到JFR");
}
}
// 记录Bean初始化统计
ConfigurableListableBeanFactory beanFactory =
((ConfigurableApplicationContext) context).getBeanFactory();
String[] beanNames = beanFactory.getBeanDefinitionNames();
long singletonCount = Arrays.stream(beanNames)
.filter(name -> beanFactory.getBeanDefinition(name).isSingleton())
.count();
long prototypeCount = Arrays.stream(beanNames)
.filter(name -> beanFactory.getBeanDefinition(name).isPrototype())
.count();
log.info("📈 Bean统计: 单例={}, 原型={}, 总数={}",
singletonCount, prototypeCount, beanNames.length);
}
}
五、企业级启动优化实践
1. 延迟初始化
Spring Boot 2.2+支持延迟初始化,可以加快启动速度,但首次请求会变慢:
# application.yml
spring:
main:
lazy-initialization: true # 启用延迟初始化
# 或者针对特定Bean
cloud:
function:
lazy: true
2. 组件扫描优化
默认情况下,@SpringBootApplication会扫描主类所在包及其子包。如果包结构复杂,可以优化:
@SpringBootApplication
// 精确指定扫描包,减少不必要的扫描
@ComponentScan(basePackages = {
"com.example.service",
"com.example.controller",
"com.example.repository"
})
// 或者排除不需要的自动配置
@EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class, // 如果没有数据库
CacheAutoConfiguration.class // 如果没有缓存
})
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
// 设置启动优化参数
app.setDefaultProperties(Collections.singletonMap(
"spring.main.log-startup-info", "false" // 关闭启动信息日志
));
app.run(args);
}
}
3. 分层初始化策略
对于大型应用,可以采用分层初始化策略:
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class PhasedBeanInitializer implements SmartInitializingSingleton {
@Autowired
private ConfigurableApplicationContext context;
@Override
public void afterSingletonsInstantiated() {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 第一阶段:初始化基础设施Bean
initializeInfrastructureBeans(beanFactory);
// 第二阶段:初始化业务核心Bean
initializeCoreBusinessBeans(beanFactory);
// 第三阶段:初始化辅助功能Bean
initializeAuxiliaryBeans(beanFactory);
}
private void initializeInfrastructureBeans(
ConfigurableListableBeanFactory beanFactory) {
String[] infrastructureBeans = {
"dataSource", "transactionManager", "entityManagerFactory"
};
for (String beanName : infrastructureBeans) {
if (beanFactory.containsBean(beanName)) {
log.info("🔧 初始化基础设施Bean: {}", beanName);
beanFactory.getBean(beanName);
}
}
}
// ... 其他初始化方法
}
六、常见启动问题与解决方案
问题1:Bean循环依赖
错误信息:
Requested bean is currently in creation: Is there an unresolvable circular reference?
解决方案:
- 使用
@Lazy延迟加载 - 使用Setter注入代替构造器注入
- 使用
@DependsOn明确依赖顺序
@Service
public class ServiceA {
// 使用@Lazy解决循环依赖
private final ServiceB serviceB;
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
}
问题2:配置文件加载失败
错误信息:
Could not resolve placeholder 'xxx' in value "${xxx}"
解决方案:
@Configuration
public class ConfigValidation implements EnvironmentAware {
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
validateRequiredProperties();
}
private void validateRequiredProperties() {
String[] requiredProps = {
"spring.datasource.url",
"spring.datasource.username",
"app.secret-key"
};
for (String prop : requiredProps) {
if (!environment.containsProperty(prop)) {
throw new IllegalStateException(
"Required property '" + prop + "' is missing!");
}
}
}
}
问题3:启动超时
优化方案:
# application.yml
spring:
main:
banner-mode: "off" # 关闭Banner
log-startup-info: false # 关闭启动信息日志
# 连接池优化
datasource:
hikari:
initialization-fail-timeout: 30000 # 初始化超时时间
connection-timeout: 30000 # 连接超时时间
# Redis连接优化
data:
redis:
timeout: 2000ms
lettuce:
pool:
max-active: 20
max-wait: 1000ms
七、启动监控与诊断
1. 启动时间监控
@RestController
@RequestMapping("/actuator")
public class StartupMonitorController {
private final long startupTime = System.currentTimeMillis();
@GetMapping("/startup-info")
public Map<String, Object> getStartupInfo() {
Map<String, Object> info = new LinkedHashMap<>();
Runtime runtime = Runtime.getRuntime();
info.put("启动时间", new Date(startupTime));
info.put("运行时长", formatDuration(System.currentTimeMillis() - startupTime));
info.put("JVM启动参数", ManagementFactory.getRuntimeMXBean().getInputArguments());
info.put("JVM内存", Map.of(
"总内存", runtime.totalMemory() / 1024 / 1024 + "MB",
"已用内存", (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024 + "MB",
"最大内存", runtime.maxMemory() / 1024 / 1024 + "MB"
));
// Bean数量统计
ApplicationContext context = SpringContextHolder.getApplicationContext();
String[] beanNames = context.getBeanDefinitionNames();
info.put("Bean总数", beanNames.length);
return info;
}
private String formatDuration(long millis) {
long seconds = millis / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
return String.format("%d小时%d分%d秒",
hours, minutes % 60, seconds % 60);
}
}
访问 http://localhost:8080/actuator/startup-info 获取:
{
"启动时间": "2024-05-20 10:00:00",
"运行时长": "0小时2分30秒",
"JVM启动参数": ["-Xmx512m", "-Xms256m", "-Dspring.profiles.active=dev"],
"JVM内存": {
"总内存": "256MB",
"已用内存": "128MB",
"最大内存": "512MB"
},
"Bean总数": 152
}
2. 使用Spring Boot Actuator
# application.yml
management:
endpoints:
web:
exposure:
include: "health,info,metrics,startup"
endpoint:
health:
show-details: always
startup:
enabled: true
动手实践:在你的Spring Boot项目中添加启动监控,记录:
- 应用启动总耗时
- 各阶段耗时分布
- Bean初始化数量和时间
- 自动配置的匹配情况
小思考:如果应用启动时需要从远程配置中心加载配置,应该在哪个阶段进行?如何保证在Bean初始化之前完成配置加载?
欢迎在评论区分享你的启动优化经验和好的建议哦~
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)