SpringBoot原理揭秘--自动装配(终)
上一章节我们理论的讲了讲springBoot自动装配本质上是依赖于springFrameWork的模块装配和条件装配,其中模块装配则主要是自定义注解+@import注解对于普通类,importSelector子类,ImportBeanDefinitionRegister子类和配置类的装配,而对于条件模块则是更加细化的控制哪些情况下装配哪些bean,主要又profile注解和conditional两大注解来控制,profile注解是根据环境,通过硬编码或者环境指令来触发,而conditional则更加灵活可以根据自定义条件来进行条件装配。
下面我们来实际结合代码来看看,springBoot到底是如何装配的
@SpringBootApplication
public class MybatisTestApplication {public static void main(String[] args) {SpringApplication.run(MybatisTestApplication.class, args);}}
当我们使用springBoot的时候一个必备的注解就是@SpringBootApplication这个注解,而这个注解恰恰也蕴含着自动装配的秘密。下面我们来看看
@SpringBootApplication
@SpringBootApplication
是 Spring Boot 应用的核心注解,它是一个组合注解,整合了多个 Spring 框架的注解,用于快速启动和配置 Spring 应用
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {/*** Exclude specific auto-configuration classes such that they will never be applied.* @return the classes to exclude*/@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};/*** Exclude specific auto-configuration class names such that they will never be* applied.* @return the class names to exclude* @since 1.3.0*/@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};}
我们可以看到在@SpringBootApplication这个注解内部含有多个注解,分别是@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
这三个注解则是组成自动装配的核心。下面我们一个一个看
@SpringBootConfiguration
@SpringBootConfiguration实际上是@Configuration的子注解,他的主要作用则是与Configuration注解的作用一样被注解的类充当配置类,唯一的区别是@SpringBootConfiguration被注解的类会被首先加载到ioc容器中,也就是说是@SpringBootConfiguration注解可以控制加载bean的顺序
@ComponentScan
@ComponentScan注解的主要作用则是扫描当前类的包下的所有用户自定义的bean,将这些bean加载到ioc容器中去,从 @SpringBootApplication
注解所在类的同级包开始,递归扫描所有子包。
如果组件位于 @SpringBootApplication
所在包的上级目录,则不会被自动扫描。
@ComponentScan
会识别以下注解标记的类:
@Component
:通用组件注解。@Service
:业务逻辑层组件。@Repository
:数据访问层组件(自动处理数据库异常)。@Controller
:Web 控制器组件。@Configuration
:配置类。- 其他自定义注解(需通过
@Component
元注解派生)。
@EnableAutoConfiguration
@EnableAutoConfiguration则是springBoot的能够完成自动装配的核心,对于SpringBootConfiguration使用来确定程序入口的,而ComponentScan则是扫描用户自定义的bean加载到ioc中,那么对于系统默认的bean以及第三方的默认bean则是通过EnableAutoConfiguration注解来加载进去的
下面我们看看EnableAutoConfiguration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {/*** Exclude specific auto-configuration classes such that they will never be applied.* @return the classes to exclude*/Class<?>[] exclude() default {};/*** Exclude specific auto-configuration class names such that they will never be* applied.* @return the class names to exclude* @since 1.3.0*/String[] excludeName() default {};
}
在EnableAutoConfiguration注解中则是有两个重要注解分别是AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)。下面我们来看看AutoConfigurationPackage注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {/*** Base packages that should be registered with {@link AutoConfigurationPackages}.* <p>* Use {@link #basePackageClasses} for a type-safe alternative to String-based package* names.* @return the back package names* @since 2.3.0*/String[] basePackages() default {};/*** Type-safe alternative to {@link #basePackages} for specifying the packages to be* registered with {@link AutoConfigurationPackages}.* <p>* Consider creating a special no-op marker class or interface in each package that* serves no purpose other than being referenced by this attribute.* @return the base package classes* @since 2.3.0*/Class<?>[] basePackageClasses() default {};}
AutoConfigurationPackage注解的主要作用就是读取当前:用于标记自动配置类应扫描的基础包路径。它与自动配置机制紧密配合,确保组件(如 @Repository
、@Service
等)能被正确发现。
核心作用
指定自动配置扫描包
告诉 Spring Boot 自动配置类应该从哪个包路径开始扫描组件(如@Component
、@Repository
等)。与
@ComponentScan
的区别@ComponentScan
:手动指定需要扫描的包路径。@AutoConfigurationPackage
:由 Spring Boot 自动应用,默认扫描启动类所在的包及其子包。
支持第三方库集成
当第三方库需要自动配置时,可通过@AutoConfigurationPackage
将其组件注册到主应用的上下文中。
注解的两个属性basePackages和basePackageClasses则是可以手动指定的,而之所以能自动锁定当前注解所在包主要是由于AutoConfigurationPackages.Registrar类和AutoConfigurationPackages类来决定的,下面我们来看看这两个类的源代码
public abstract class AutoConfigurationPackages {public static void register(BeanDefinitionRegistry registry, String... packageNames) {if (registry.containsBeanDefinition(BEAN)) {addBasePackages(registry.getBeanDefinition(BEAN), packageNames);}else {RootBeanDefinition beanDefinition = new RootBeanDefinition(BasePackages.class);beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);addBasePackages(beanDefinition, packageNames);registry.registerBeanDefinition(BEAN, beanDefinition);}}static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));}@Overridepublic Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new PackageImports(metadata));}}private static final class PackageImports {private final List<String> packageNames;PackageImports(AnnotationMetadata metadata) {AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {packageNames.add(basePackageClass.getPackage().getName());}if (packageNames.isEmpty()) {packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));}this.packageNames = Collections.unmodifiableList(packageNames);}List<String> getPackageNames() {return this.packageNames;}@Overridepublic boolean equals(Object obj) {if (obj == null || getClass() != obj.getClass()) {return false;}return this.packageNames.equals(((PackageImports) obj).packageNames);}@Overridepublic int hashCode() {return this.packageNames.hashCode();}@Overridepublic String toString() {return "Package Imports " + this.packageNames;}}}
AutoConfigurationPackages
是 Spring Boot 中的一个核心工具类,其主要作用是管理自动配置的基础包路径注册和查询。它不直接参与业务逻辑,而是为 Spring Boot 的自动配置机制(特别是 Spring Data 的实体扫描)提供基础设施支持。
也就是说真正进行包路径注册的类则是内部类Registrar 类,而这个类注册包路径的主要方法则是registerBeanDefinitions
@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));}
对于metadata是注解的元信息,用于表示一个 类(或接口)上的注解元信息。它提供了访问该类上声明的所有注解及其属性的能力。而registry则是一个BeanDefinition的存储器,一般加载后的BeanDefinition都会放在BeanDefinitionRegistry 当中。
下面我们来看看方法是如何运行的可以看到实际上调用了AutoConfigurationPackages的register方法,其中参数第一个则是用来存储BeanDefinition的BeanDefinitionRegistry,而第二个是一个string类型的可变参数,下面我们来看一下第二个参数首先将metadata当作构造方法参数传入PackageImports类当中
PackageImports(AnnotationMetadata metadata) {AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {packageNames.add(basePackageClass.getPackage().getName());}if (packageNames.isEmpty()) {packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));}this.packageNames = Collections.unmodifiableList(packageNames);}
可以看到首先则是从元信息对象metadata中取出AutoConfigurationPackage注解对应的属性信息attributes,而这个属性信息我们之前介绍过分别是basePackages和basePackageClasses从中得到一个基础包类名信息加入到packageNames 的一个集合中,也就是说PackageImports主要是从元信息数据中获取注解属性的基础包类路径。
之后通过PackageImports来将读取到的所有包路径的基本信息当作参数传入方法中去。
public static void register(BeanDefinitionRegistry registry, String... packageNames) {if (registry.containsBeanDefinition(BEAN)) {addBasePackages(registry.getBeanDefinition(BEAN), packageNames);}else {RootBeanDefinition beanDefinition = new RootBeanDefinition(BasePackages.class);beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);addBasePackages(beanDefinition, packageNames);registry.registerBeanDefinition(BEAN, beanDefinition);}}
进入register方法中查看逻辑,我们发现首先会判断registry当中是否含有这个bean,而这个bean实际上就是AutoConfigurationPackages,如果registry中含有这个bean那么则会将这个bean+这些类名加入到一个方法addBasePackages当中,如果没有含有这个bean,则证明是首次创建,则会创建一个BasePackages类的beanDefinition ,这个类的作用就是专门用来存储基础包路径的随后调用addBasePackages方法,最后将其注册到registry类中。
private static final String BEAN = AutoConfigurationPackages.class.getName();
最后总结一下,对于EnableAutoConfiguration的主要作用就是将包的基础路径进行封装为BasePackages类后存储,而这个BasePackages类则是AutoConfigurationPackages这个类的内部类,对于这个注解则是首先导入了AutoConfigurationPackages的内部类registry,通过这个内部类的register方法将注解的属性进行提取,随后调用AutoConfigurationPackages的register方法将其载入到BasePackages类中。为什么将数据封装到BasePackages类中呢,因为spring本身要适配许多第三方框架,那么哪些第三方框架则可以直接通过BasePackages来拿到本项目的根路径,近而得到项目的其他信息。