【Springboot】介绍启动类和启动过程

【Springboot】介绍启动类和启动过程

【一】Spring Boot 启动类的注解

Spring Boot 启动类通常就是一个标注了 @SpringBootApplication的类,但这个注解是一个组合注解(Composite Annotation)​,理解它包含的元注解是关键。

【1】核心注解:@SpringBootApplication

这是启动类上唯一必须的注解,它整合了三个核心注解的功能:

(1)​@SpringBootConfiguration​:Spring容器会从该类中加载Bean定义

(1)​作用​:表明当前类是一个配置类。它的底层是 @Configuration,这意味着Spring容器会从该类中加载Bean定义(@Bean注解的方法)。

(2)​为什么不用 @Configuration​:@SpringBootConfiguration是Spring Boot提供的,语义上更明确地指出这是主配置类,但在功能上与 @Configuration无异。

(2)​@EnableAutoConfiguration​:自动配置依赖

(1)​作用​:​启用Spring Boot的自动配置机制。这是Spring Boot魔法(Convention over Configuration)的核心。

(2)​原理​:这个注解会通知Spring Boot根据你添加的jar包依赖(如classpath下是否存在DataSource、SpringMVC等类),自动猜测并配置你需要的Bean。例如,当你引入了spring-boot-starter-web,它会自动配置Tomcat和Spring MVC。

(3)​@ComponentScan​:​自动扫描并注册Bean

(1)​作用​:​自动扫描并注册Bean。默认会扫描启动类所在包及其所有子包下的所有组件,包括@Component, @Service, @Repository, @Controller等注解的类,并将它们注册到Spring容器中。

(2)​重要提示​:这是为什么通常要把启动类放在项目根包(root package)下的原因。如果放在一个很深的包里,@ComponentScan可能无法扫描到其他重要的组件。

【2】其他常用注解(可根据需要添加)

虽然 @SpringBootApplication已经足够,但在某些场景下,你可能会在启动类上额外添加一些注解。

(1)​@EnableScheduling​

​作用​:启用Spring的定时任务功能。添加后,可以使用 @Scheduled注解来创建定时任务。

(2)​@EnableAsync​

​作用​:启用Spring的异步方法执行功能。添加后,可以使用 @Async注解来标记异步执行的方法。

(3)​@EnableTransactionManagement​

​作用​:启用注解式事务管理。不过,当Spring Boot检测到存在事务管理器(如引入了JDBC或JPA starter)时,此功能实际上是默认开启的。显式添加此注解只是为了代码意图更清晰。

(4)​@EnableCaching​

​作用​:启用Spring的注解驱动缓存功能。添加后,可以使用 @Cacheable, @CacheEvict等注解。

(5)​@Import​

​作用​:用于导入其他配置类(通常是@Configuration类),这些配置类可能不在@ComponentScan的扫描路径下。例如:@Import({CustomConfig.class, AnotherConfig.class})。

​总结​:对于大多数标准应用,​只使用 @SpringBootApplication注解就足够了。其他注解如 @EnableScheduling等,应根据具体功能需求选择性添加。

【3】@MapperScan

@MapperScan注解的主要目的是告诉 MyBatis 应该去哪个或哪些包路径下扫描 Mapper 接口,并自动将其注册为 Spring 容器中的 Bean(MapperFactoryBean)​。

(1)​解决了什么问题?​​

在没有这个注解之前,你需要在每个 Mapper 接口上手动添加 @Mapper注解,或者手动配置 MapperFactoryBean,非常繁琐。@MapperScan通过包扫描的方式,实现了批量、自动的注册,极大简化了配置。

(2)​底层机制:​​

当 Spring 容器启动时,它会处理 @MapperScan注解。MyBatis-Spring 整合模块会为指定的包路径创建 ClassPathMapperScanner,该扫描器会找到所有接口,并为每个接口动态创建一个 MapperFactoryBean的 BeanDefinition 注册到 Spring 容器中。MapperFactoryBean是一个 FactoryBean,它的 getObject()方法会使用 SqlSession为原始 Mapper 接口创建一个动态代理实例,并将这个代理实例作为 Bean 交给 Spring 管理。这就是为什么你可以在 Service 层直接 @Autowired一个接口的原因。

java 复制代码
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import @SpringBootApplication

// 方式一:直接扫描一个包
@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

// 方式二:扫描多个包
@MapperScan(basePackages = {"com.example.mapper", "com.example.other.dao"})

// 方式三:类型安全的扫描(推荐)
// 创建一个空的标记接口,放在 com.example.mapper 包下
public interface MapperScanMarker {
    // 无任何方法,仅作为包路径标记
}

@SpringBootApplication
// 扫描标记接口所在的包
@MapperScan(basePackageClasses = MapperScanMarker.class)
public class DemoApplication {
    // ...
}

【4】自动装配原理

(1)自动装配的核心思想

​要解决的问题:​​ 传统 Spring 应用需要大量手动配置(如 XML、Java Config)来集成第三方库或框架(例如数据源、事务管理器、MVC 等)。这个过程繁琐且容易出错。

​解决方案:​​ Spring Boot 自动装配通过预先定义的条件,在检测到项目的特定依赖、配置和类路径后,自动为应用注入所需的 Bean 并完成配置。简单来说,就是 ​​"如果我在类路径上看到了 X,并且你没有自己配置 Y,那么我就自动给你配置一个默认的 Y"​。

(2)实现原理的核心组件

(1)@SpringBootApplication与 @EnableAutoConfiguration

一切的起点是主启动类上的 @SpringBootApplication注解。它是一个组合注解​(Composite Annotation),其核心功能之一由 @EnableAutoConfiguration提供。

java 复制代码
@SpringBootApplication // 这是一个元注解,整合了其他注解的功能
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

@EnableAutoConfiguration本身又是一个开关注解,它通过 @Import导入了最核心的加载器:AutoConfigurationImportSelector。

(2)AutoConfigurationImportSelector

这个类是自动装配的大脑。它的核心任务是决定需要导入哪些自动配置类。

​工作原理​:AutoConfigurationImportSelector会读取项目 classpath 下所有 JAR 包中的 ​META-INF/spring.factories​ 文件。

​关键文件​:在 spring-boot-autoconfigure-x.x.x.x.jar中,META-INF/spring.factories文件是一个键值对形式的配置文件。其中 EnableAutoConfiguration这个 key 后面列出了一长串的自动配置类(XXXAutoConfiguration)。

​spring.factories文件片段示例:​​

java 复制代码
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
# ... 省略上百个配置类
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

AutoConfigurationImportSelector会获取所有这些配置类的全限定名,并将它们作为候选配置类。

(3)@Conditional条件注解家族 - ​灵魂所在​

仅仅将候选配置类全部加载是不行的,Spring Boot 需要根据当前应用的环境来智能判断哪些配置类应该真正生效。这就是 @Conditional系列注解的作用。

这些注解是自动装配的"开关",它们会在配置类或 Bean 被加载前进行条件判断,只有满足所有条件,配置才会生效。

(4)XXXAutoConfiguration自动配置类

这些是具体的执行者。每个 XXXAutoConfiguration都是一个标准的 ​Spring 配置类​(@Configuration),其内部使用 @Bean方法来定义组件,并且方法上通常装饰着各种 @Conditional注解。

【二】Spring Boot 项目启动的详细流程

Spring Boot 的启动流程可以看作是传统 Spring 应用启动流程的一个高度封装和自动化版本。其核心是 SpringApplication.run(Application.class, args)方法。

以下是其详细步骤:

【1】​启动 Main 方法​

JVM 调用应用程序的 main方法。

【2】​创建 SpringApplication 实例​:

(1)在 run方法内部,首先会创建一个 SpringApplication实例。

(2)这个实例会进行一些初始化工作,例如:

1-​推断应用类型​:判断是普通的Servlet应用(Spring MVC)还是响应式Web应用(WebFlux)。

​2-初始化器(Initializers)​​:加载 META-INF/spring.factories文件中配置的 ApplicationContextInitializer。

​3-监听器(Listeners)​​:加载 META-INF/spring.factories文件中配置的 ApplicationListener(应用监听器)。这些监听器将用于接收整个启动过程中发布的各种事件。

【3】​运行 SpringApplication 实例(run方法)​​:这是最复杂的核心阶段。

(1)​a. 启动计时器 & 发布开始事件​:启动一个计时器,并发布 ApplicationStartingEvent事件。此时容器还未创建,任何Bean都未初始化。

(2)​b. 准备环境(Environment)​​:创建并配置应用运行环境(Environment),读取所有配置源(application.properties/yaml、系统属性、环境变量等)。发布 ApplicationEnvironmentPreparedEvent事件。

(3)​c. 创建应用上下文(ApplicationContext)​​:根据第一步推断的应用类型,创建对应的 ApplicationContext(例如,对于Servlet应用,创建 AnnotationConfigServletWebServerApplicationContext)。

(4)​d. 准备应用上下文​:

将环境(Environment)设置到上下文中。

执行所有 ApplicationContextInitializer的 initialize方法,对上下文进行自定义初始化。

发布 ApplicationContextInitializedEvent事件。

(5)​e. 刷新应用上下文(核心中的核心)​​:调用上下文的 refresh()方法。这一步完成了传统Spring应用容器的所有初始化工作:

​加载Bean定义​:解析启动类(因为它是@Configuration),执行@ComponentScan扫描并注册所有Bean定义。

​执行自动配置​:执行 @EnableAutoConfiguration逻辑,加载 spring-boot-autoconfigurejar 包中 META-INF/spring.factories文件里的所有自动配置类(XXXAutoConfiguration),根据条件(@ConditionalOnXxx)判断是否需要配置相应的Bean。

​初始化Bean​:实例化所有非懒加载的单例Bean。

(6)​f. 后置处理​:执行 CommandLineRunner和 ApplicationRunner接口的实现Bean。

(7)​g. 启动完成​:发布 ApplicationStartedEvent事件,启动计时器停止,打印启动耗时日志。

整个流程伴随着事件的发布,允许开发者通过监听这些事件在特定阶段插入自定义逻辑。

【三】Spring Boot 生命周期中的扩展点及使用案例

Spring Boot 在整个生命周期中提供了大量"钩子"(Hook),允许开发者介入并执行自定义逻辑。以下是一些最重要的扩展点:

【1】Application Events(应用事件) - 观察者模式

通过实现 ApplicationListener接口或使用 @EventListener注解来监听特定事件。

​常用事件​:

ApplicationStartingEvent:应用刚启动,任何处理都还未进行。

ApplicationEnvironmentPreparedEvent:环境已准备完毕,上下文还未创建。

ApplicationContextInitializedEvent:上下文已创建且初始化器已被调用,但Bean定义还未加载。

ApplicationPreparedEvent:Bean定义已加载,但Bean还未实例化。

ApplicationStartedEvent:上下文已刷新,所有Bean已实例化,CommandLineRunner/ApplicationRunner还未执行。

ApplicationReadyEvent:应用已完全启动,可以正常接收请求。

​案例:在应用启动成功后打印一条日志​java

java 复制代码
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class StartupNotifier {

    @EventListener(ApplicationReadyEvent.class)
    public void onAppReady() {
        System.out.println("🎉 Application is now ready and can serve traffic!");
        // 可以在这里执行一些启动后的检查,比如检查数据库连接状态等
    }
}

【2】ApplicationContextInitializer(应用上下文初始化器)

在 ApplicationContext刷新(refresh)之前,对其执行自定义的初始化操作。

​案例:在上下文准备阶段设置一个自定义属性​java

java 复制代码
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

public class MyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 向环境中添加一个属性
        applicationContext.getEnvironment().getSystemProperties().put("myCustomProperty", "initialized");
    }
}

​注册方式​:需要在 META-INF/spring.factories文件中注册。

复制org.springframework.context.ApplicationContextInitializer=com.example.MyInitializer

【3】CommandLineRunner / ApplicationRunner

在应用上下文刷新完成后、应用完全启动之前,执行一些特定的代码。非常适合进行数据初始化、缓存预热等操作。两者功能几乎一样,区别在于参数:

CommandLineRunner:提供原始的字符串数组参数 String... args(即main方法的args)。

ApplicationRunner:提供更结构化的 ApplicationArguments对象来解析参数。

​案例:应用启动后初始化一些数据​java

java 复制代码
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class DataLoader implements CommandLineRunner {

    private final UserRepository userRepository;

    public DataLoader(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public void run(String... args) throws Exception {
        // 检查数据库是否已有数据,如果没有则插入默认数据
        if (userRepository.count() == 0) {
            User admin = new User("admin", "admin@example.com");
            userRepository.save(admin);
            System.out.println("Initial admin user created.");
        }
    }
}

【4】Spring Bean 生命周期钩子

这是 Spring 框架本身的扩展点,在 Spring Boot 中同样适用。

@PostConstruct:在Bean的依赖注入完成后,初始化方法(InitializingBean.afterPropertiesSet)之前执行。

InitializingBean接口:实现 afterPropertiesSet()方法,在所有属性设置完成后执行。

@PreDestroy:在Bean被容器销毁之前执行。

​案例:Bean初始化后连接资源​java

java 复制代码
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class ResourceConnector {

    @PostConstruct
    public void connect() {
        System.out.println("Connecting to external resource...");
        // 初始化连接
    }

    @PreDestroy
    public void disconnect() {
        System.out.println("Disconnecting from external resource...");
        // 关闭连接,释放资源
    }
}

【四】Spring Bean 生命周期中的重要组件

【1】BeanPostProcessor (BPP) - 容器级后处理器​

(1)​作用​:这是Spring框架最强大、最核心的扩展接口。它作用于整个ApplicationContext,对所有Bean的初始化过程进行拦截和增强。你可以把它想象成一个"Bean加工流水线"。

(2)​两个核心方法​:

1-postProcessBeforeInitialization(Object bean, String beanName):在Bean的初始化回调方法​(如@PostConstruct)之前执行。可以对Bean进行包装或替换(例如返回一个代理对象,AOP就是基于此实现的)。

2-postProcessAfterInitialization(Object bean, String beanName):在Bean的初始化回调方法之后执行。此时Bean已基本完成初始化。

(3)​重要实现​:AutowiredAnnotationBeanPostProcessor(处理@Autowired注解)、CommonAnnotationBeanPostProcessor(处理@PostConstruct、@Resource等)、AnnotationAwareAspectJAutoProxyCreator(负责AOP动态代理)。

【2】BeanFactoryPostProcessor (BFPP) - 工厂级后处理器​

(1)​作用​:在Bean实例化之前,可以读取、修改Bean的定义(BeanDefinition)。它操作的是"蓝图",而不是Bean实例本身。

(2)​核心方法​:postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)

(3)​经典案例​:PropertySourcesPlaceholderConfigurer(处理 ${...}占位符)就是在此时将Bean定义中的占位符替换为实际的属性值。

【3】Aware 接口族 - 感知接口​

​(1)作用​:让Bean能"感知"到容器本身和某些特定的资源。这些接口的回调发生在BeanPostProcessor之前,初始化方法之后。

(2)​常用接口​:

1-BeanNameAware:感知自己在容器中的Bean名称。

2-ApplicationContextAware:感知自己所在的ApplicationContext(这是手动获取Bean的一种方式)。

3-BeanFactoryAware:感知创建自己的BeanFactory。

【4】生命周期回调注解/接口​

(1)​作用​:定义Bean自身初始化和销毁时的行为。

(2)​初始化​:

​JSR-250注解​:@PostConstruct(推荐使用,标准注解)。

​Spring接口​:InitializingBean及其 afterPropertiesSet()方法。

​XML配置​:init-method属性。

(3)​销毁​:

​JSR-250注解​:@PreDestroy(推荐使用)。

​Spring接口​:DisposableBean及其 destroy()方法。

​XML配置​:destroy-method属性。

【5】生命周期执行顺序

​执行顺序​:BeanFactoryPostProcessor-> BeanPostProcessor的Before-> Aware接口 -> @PostConstruct-> InitializingBean-> init-method-> BeanPostProcessor的After-> ... -> @PreDestroy-> DisposableBean-> destroy-method。

相关推荐
绝无仅有2 小时前
面试之MySQL基础和事务实战经验总结与分享
后端·面试·github
程序员爱钓鱼2 小时前
Go语言实战案例 — 工具开发篇:Go 实现二维码生成器
后端·google·go
绝无仅有2 小时前
面试经验之MySQL 锁与索引实战总结分享
后端·面试·github
励志码农6 小时前
JavaWeb 30 天入门:第二十三天 —— 监听器(Listener)
java·开发语言·spring boot·学习·servlet
@小匠6 小时前
Spring Cache 多租户缓存隔离解决方案实践
java·spring·缓存
智码看视界7 小时前
老梁聊全栈系列:(阶段一)架构思维与全局观
java·javascript·架构
黎宇幻生7 小时前
Java全栈学习笔记33
java·笔记·学习
程序员码歌9 小时前
明年35岁了,如何破局?说说心里话
android·前端·后端