SpringBoot自动装配底层全流程
我们先把整套东西的分工先说清楚再顺着程序启动的顺序一步一步拆解底层发生了什么全程结合我们手写的线程池starter来讲。一、先理清我们手写starter三个模块各自的作用这是自动装配的载体我们一共写了三层工程各司其职缺一不可my-starter父工程只用来统一锁定SpringBoot版本、统一管理所有子模块版本本身不提供任何业务代码是Maven多模块的管理壳子threadpool-autoconfigure自动配置核心包这是自动装配真正干活的核心里面有三块关键代码ThreadPoolProperties绑定yml配置的属性类ThreadPoolAutoConfiguration线程池的自动配置类带条件注解负责创建线程池BeanMETA-INF/spring/xxx.imports 文件SPI注册文件告诉SpringBoot“我这里有一个自动配置类需要加载”threadpool-starter启动器空包里面一行Java代码都没有只在pom里依赖上面的autoconfigure包。它的作用就是给使用者提供一个统一依赖坐标别人只需要引入这个starter就能间接引入自动配置核心代码不用自己管理多个依赖。使用者会新建一个demo-test测试项目引入我们写好的starter写Controller注入线程池使用这是自动装配的使用者端。二、SpringBoot项目启动的第一阶段开启自动装配总开关我们demo-test的启动类上标注了SpringBootApplication这个注解是一个复合注解里面包含三个关键能力和自动装配强相关的是EnableAutoConfigurationComponentScan只会扫描当前demo-test项目自己包下的Controller、自定义配置类不会扫描第三方jar包里的代码这也是为什么我们不能把接口写在starter里Configuration标记当前启动类也是一个配置类EnableAutoConfiguration自动装配的总开关没有这个注解后面所有自动配置都不会执行。点开EnableAutoConfiguration源码它内部通过Import(AutoConfigurationImportSelector.class)导入了一个选择器类这个选择器就是自动装配底层的核心执行器整个自动装配的逻辑全部由这个类驱动。三、第二阶段选择器扫描所有jar找到我们线程池的自动配置类当程序走到AutoConfigurationImportSelector的核心方法getAutoConfigurationEntry()时会执行一套固定流程专门读取第三方jar里的自动配置程序遍历当前项目所有引入的依赖jar包包括我们导入的threadpool-starterstarter又传递依赖了autoconfigure包针对每一个jar包去固定路径读取文件SpringBoot3规范路径是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports读到我们autoconfigure包里的这个文件文件里写了我们配置类的全类名com.starter.threadpool.autoconfigure.ThreadPoolAutoConfiguration程序会把这个类名存进一个待加载列表同理如果项目引入redis、web等官方starter也会读取对应imports文件收集所有官方自动配置类。简单说我们手写的imports文件就是给SpringBoot留的“路标”告诉启动程序我的jar包里有一个自动配置类记得加载它。如果不写这个文件SpringBoot完全不知道我们存在ThreadPoolAutoConfiguration自动装配直接失效。四、第三阶段根据条件注解判断我们的线程池配置类是否生效收集完所有自动配置类名后Spring不会直接全部加载会通过条件注解做过滤我们ThreadPoolAutoConfiguration上的注解就在这里生效首先判断类上ConditionalOnClass(ThreadPoolExecutor.class)校验当前JVM中是否存在ThreadPoolExecutor这个类这个类是JDK原生线程池类必然存在所以我们这个配置类不会被过滤保留下来举个对比如果是Redis自动配置会判断项目里有没有RedisTemplate类没引入redis starter就过滤掉配置实现按需装配确认配置类生效后处理EnableConfigurationProperties(ThreadPoolProperties.class)Spring会读取项目resources下的application.yml文件匹配前缀starter.threadpool的配置把yml里写的核心线程、最大线程、队列容量等数值自动注入到ThreadPoolProperties实体类中并且把这个属性类存入Spring容器后续创建线程池时直接读取配置参数此时ThreadPoolAutoConfiguration已经被Spring识别为标准Configuration配置类等待执行里面的Bean方法。五、第四阶段创建线程池Bean实现“自动装配”核心效果Spring处理配置类内部的Bean方法也就是我们创建ThreadPoolExecutor的方法这里ConditionalOnMissingBean注解发挥作用Spring先去当前容器中检索有没有已经存在ThreadPoolExecutor类型的Bean分两种情况情况1用户demo-test项目里没有手动写线程池Bean容器找不到对应Bean满足ConditionalOnMissingBean的条件执行我们的方法方法会拿着之前注入完成的ThreadPoolPropertiesyml配置参数new一个ThreadPoolExecutor线程池对象放入Spring单例容器情况2用户自己写了配置类手动创建了ThreadPoolExecutor Bean容器中已经存在对应实例注解生效跳过我们自动创建Bean的逻辑优先使用用户自定义的线程池满足扩展、覆盖的需求执行完成后Spring容器里已经存在一个可用的线程池实例不需要用户手动new、手动写配置类这就是“自动装配”带来的便捷。六、第五阶段使用者注入使用走完完整链路当Spring容器初始化完成后我们在demo-test的PoolTestController中写Resource private ThreadPoolExecutor threadPoolExecutorSpring会从容器中取出我们自动装配好的线程池Bean直接注入给Controller接口访问时就能调用线程池执行任务全程不需要用户关心线程池是怎么创建、怎么读取yml配置的。七、整条底层流程串联总结简化完整版开发者在测试项目引入自定义线程池starter启动类SpringBootApplication携带EnableAutoConfiguration开启自动装配底层AutoConfigurationImportSelector扫描所有jar包读取autoconfigure中的imports文件拿到线程池自动配置类的完整类名校验配置类上ConditionalOnClass确认配置类可以生效通过EnableConfigurationProperties读取application.yml的自定义线程池配置封装到属性类执行配置类里的Bean方法通过ConditionalOnMissingBean判断无自定义Bean则自动创建线程池放入Spring容器业务Controller直接注入容器中自动生成的线程池完成使用。补充设计思想为什么要这么设计这套自动装配解决了原生Spring的痛点原生Spring想要使用线程池必须自己写配置类、手动读取配置文件、手动注册Bean而通过starter自动装配把通用组件的创建逻辑、配置绑定逻辑全部封装到第三方jar使用者只需要引入依赖、简单写yml参数底层全部由SpringBoot自动完成同时依靠条件注解实现按需加载、支持用户自定义覆盖符合开闭原则。口述版SpringBoot项目启动类标注SpringBootApplication它是组合注解其中EnableAutoConfiguration注解内部带有Import导入自动配置选择器AutoConfigurationImportSelector这个选择器的selectImports方法会扫描项目所有导入的jar包读取每个jar包下META-INF/spring目录的AutoConfiguration.imports文件文件内记录所有候选自动配置类的全类名读取后存入候选列表。随后根据每个自动配置类上的Conditional系列注解做过滤仅满足条件的配置类会保留不满足条件直接丢弃。过滤后保留的自动配置类一般标注EnableConfigurationProperties(XXX.class)属性类XXX.class带有ConfigurationProperties(prefix xxx)注解Spring会读取application.yml中对应前缀的配置封装到该属性类并将属性类存入容器供自动配置类读取配置参数。Spring启动执行refresh()容器刷新流程时会提前解析配置类中的Bean方法根据方法上ConditionalOnMissingBean注解判断若容器已存在同类型Bean则跳过当前Bean创建逻辑否则执行Bean方法完成实例化将Bean存入Spring容器。最终在Controller、Service等业务代码中可直接通过依赖注入拿到容器内提前创建好的Bean直接使用。自动装配的核心好处无需手动编写配置类、无需手动解析yml并封装配置实体底层逻辑全部自动化引入第三方starter后可直接使用其封装好的Bean。