为什么Spring Boot应用只需要一个main方法就能启动Web服务器?背后发生了什么魔法?今天我们一起揭开神秘面纱。

一、Spring Boot启动全景图

让我们先看一张完整的启动流程图,建立宏观认知:

SpringApplication.run

SpringApplication初始化

运行监听器开始事件

准备环境Environment

打印Banner

创建ApplicationContext

准备ApplicationContext

刷新ApplicationContext

执行Runner接口

运行监听器结束事件

应用启动完成

BeanFactory准备

执行BeanFactoryPostProcessor

注册BeanPostProcessor

实例化单例Bean

发布ContextRefreshed事件

二、启动入口: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);
}

关键构造步骤

  1. 推断应用类型

    • WebApplicationType.deduceFromClasspath():通过类路径判断
    • SERVLET:有javax.servlet.Servletorg.springframework.web.context.ConfigurableWebApplicationContext
    • REACTIVE:有org.springframework.web.reactive.DispatcherHandler
    • NONE:非Web应用
  2. 设置初始器(Initializers)

    • META-INF/spring.factories加载ApplicationContextInitializer
    • ApplicationContext刷新前执行,用于上下文初始化
  3. 设置监听器(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定义加载

关键类AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner

// 演示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类。

自动配置原理

  1. @SpringBootApplication包含@EnableAutoConfiguration
  2. @EnableAutoConfiguration通过@Import(AutoConfigurationImportSelector.class)导入自动配置
  3. AutoConfigurationImportSelectorMETA-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?

解决方案

  1. 使用@Lazy延迟加载
  2. 使用Setter注入代替构造器注入
  3. 使用@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项目中添加启动监控,记录:

  1. 应用启动总耗时
  2. 各阶段耗时分布
  3. Bean初始化数量和时间
  4. 自动配置的匹配情况

小思考:如果应用启动时需要从远程配置中心加载配置,应该在哪个阶段进行?如何保证在Bean初始化之前完成配置加载?

欢迎在评论区分享你的启动优化经验和好的建议哦~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐