上周帮公司IT同事排查一个线上服务启动慢的问题,查到最后发现是某个自定义Starter里重复加载了DataSourceAutoConfiguration。这事儿让我又翻了一次Spring Boot的启动源码——原来我们天天写的@SpringBootApplication,背后真没那么简单。
main方法之后发生了什么?
你写完public static void main(String[] args) { SpringApplication.run(App.class, args); },按下回车,控制台刷出一堆INFO日志。但SpringApplication.run()干了啥?不是黑盒,它就藏在spring-boot包里。
点进去看,核心逻辑在SpringApplication#run方法里:先是准备环境(environment),再创建ApplicationContext,最后刷新上下文(refreshContext)。最关键的refreshContext(),会一路调用到AbstractApplicationContext#refresh(),这里才是Spring容器真正“活过来”的地方。
@SpringBootApplication到底合并了啥?
这个注解看着轻巧,其实是三个注解的组合:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan其中@EnableAutoConfiguration最值得细看。它通过SpringFactoriesLoader.loadFactoryNames()去META-INF/spring.factories里找org.springframework.boot.autoconfigure.EnableAutoConfiguration下的所有自动配置类。比如spring-boot-autoconfigure.jar里就写着:
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration这些类不是全都被加载,而是按条件筛选。比如DataSourceAutoConfiguration上标着@ConditionalOnClass(DataSource.class),意味着只有classpath里有DataSource类(比如HikariCP或Druid)时,这个配置才生效。
自动配置里的“条件开关”怎么工作的?
以RedisAutoConfiguration为例,它上面有这么一行:
@ConditionalOnClass({ RedisOperations.class })Spring Boot启动时会通过ConditionEvaluator判断这个条件是否成立。原理很简单:用ClassLoader尝试加载RedisOperations这个类,能加载成功就继续,否则跳过整个配置类。这种机制让自动配置既灵活又安全,不会因为少引一个依赖就直接报错退出。
再比如你项目里没配spring.redis.host,默认情况下RedisAutoConfiguration虽然被扫描到了,但内部的LettuceConnectionConfiguration因为@ConditionalOnMissingBean(RedisConnectionFactory.class)而不会生效——直到你手动加了配置,条件满足,连接工厂才真正创建出来。
动手验证:删掉spring.factories试试
你可以自己做个实验:新建一个空的starter模块,在resources/META-INF/spring.factories里只写一行:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.demo.MyTestAutoConfiguration然后写个极简的MyTestAutoConfiguration,里面只注册一个String Bean。启动后用@Autowired注入试试,你会发现它真的进容器了。这就是自动配置最朴素的样子——没有魔法,只有约定和反射。
办公网络环境里,很多中间件集成(比如LDAP认证、RabbitMQ连接池、Prometheus监控端点)都靠这套机制实现“开箱即用”。理解了源码这一层,下次遇到自动配置不生效,你就知道该去看日志里的ConditionEvaluationReport,而不是盲目改yml。