springBoot是如何实现自动装配的?
SpringBootApplication注解
SpringBoot
的诞生就是为了简化 Spring
中繁琐的 XML
配置
springBoot
一切来源于SpringBoot
的启动类,main方法上会有一个注解@SpringBootApplication
/** * Indicates a {@link Configuration configuration} class that declares one or more * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience * annotation that is equivalent to declaring {@code @Configuration}, * {@code @EnableAutoConfiguration} and {@code @ComponentScan}. * * @author Phillip Webb * @author Stephane Nicoll * @author Andy Wilkinson * @since 1.2.0 */ @Target(ElementType.TYPE) //SpringBootApplication注解可以在类、接口(包括注释类型)或枚举声明 @Retention(RetentionPolicy.RUNTIME) //指定该注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在 @Documented //这个注解只是用来标注生成javadoc的时候是否会被记录 @Inherited //指定该注解在类上使用时,可以被子类继承 @SpringBootConfiguration //套壳的Configuration,类似Service和Component的区别 @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) //ComponentScan默认会扫描当前包和所有子包,和xml配置自动扫描效果一样,@Filter是排除了两个系统类 public @interface SpringBootApplication {}
@SpringBootApplication是一个复合注解,主要包含三个重要元注解
-
@SpringBootConfiguration
: 里面就是@Configuration,标注当前类为配置类,其实只是做了一层封装改了个名字而已-
@Configuration
: 标注在某个类上,表示这是一个 springboot的配置类
。可以向容器中注入组件。
-
-
@ComponentScan
: 配置用于 Configuration 类的组件扫描指令。 -
@EnableAutoConfiguration
: springBoot自动配置原理依赖于该注解,引入了AutoConfigurationImportSelector,该类中 的方法会扫描所有存在META-INF/spring.factories的jar包。
如上就是springBoot基本的自动配置原理。
SpringBootApplication注意事项
@SpringBootApplication 注解应该放在主类上,也就是启动类上,通常位于项目的根包下,这样可以保证 @ComponentScan 注解能够扫描到所有的组件和配置类。
@SpringBootApplication 注解可以通过 exclude 或 excludeName 属性来排除一些不需要的自动配置类,从而减少启动时间和内存占用
@SpringBootApplication 注解可以通过 scanBasePackages 或 scanBasePackageClasses 属性来指定需要扫描的包或类,从而覆盖默认的扫描规则。
导入自动配置类
深入@EnableAutoConfiguration
之前我们了解了spirngBoot依赖于@EnableAutoConfiguration注解进行自动配置的基本信息,接下来我们对该注解注解继续深入
/** * Enable auto-configuration of the Spring Application Context, attempting to guess and * configure beans that you are likely to need. Auto-configuration classes are usually * applied based on your classpath and what beans you have defined. For example, if you * have {@code tomcat-embedded.jar} on your classpath you are likely to want a * {@link TomcatServletWebServerFactory} (unless you have defined your own * {@link ServletWebServerFactory} bean). * <p> * When using {@link SpringBootApplication @SpringBootApplication}, the auto-configuration * of the context is automatically enabled and adding this annotation has therefore no * additional effect. * <p> * Auto-configuration tries to be as intelligent as possible and will back-away as you * define more of your own configuration. You can always manually {@link #exclude()} any * configuration that you never want to apply (use {@link #excludeName()} if you don't * have access to them). You can also exclude them via the * {@code spring.autoconfigure.exclude} property. Auto-configuration is always applied * after user-defined beans have been registered. * <p> * The package of the class that is annotated with {@code @EnableAutoConfiguration}, * usually via {@code @SpringBootApplication}, has specific significance and is often used * as a 'default'. For example, it will be used when scanning for {@code @Entity} classes. * It is generally recommended that you place {@code @EnableAutoConfiguration} (if you're * not using {@code @SpringBootApplication}) in a root package so that all sub-packages * and classes can be searched. * <p> * Auto-configuration classes are regular Spring {@link Configuration @Configuration} * beans. They are located using the {@link SpringFactoriesLoader} mechanism (keyed * against this class). Generally auto-configuration beans are * {@link Conditional @Conditional} beans (most often using * {@link ConditionalOnClass @ConditionalOnClass} and * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations). * * @author Phillip Webb * @author Stephane Nicoll * @since 1.0.0 * @see ConditionalOnBean * @see ConditionalOnMissingBean * @see ConditionalOnClass * @see AutoConfigureAfter * @see SpringBootApplication */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage //2.向springBoot自动注入配置类 @Import(AutoConfigurationImportSelector.class) //1.扫描需要被自动配置的类 public @interface EnableAutoConfiguration {}
@EnableAutoConfiguration的作用是什么?
查看顺序:AutoConfigurationImportSelector类 > DeferredImportSelector接口 > ImportSelector接口 > selectImports方法 >
AutoConfigurationImportSelector类的selectImport具体实现方法
该注解主要用于扫描和配置可能需要的 bean。先提一嘴,这个可能的bean在spring.factories中配置。
-
可以看到,该注解使用@Import导入了一个类文件AutoConfigurationImportSelector,该类实现了DeferredImportSelector接口
-
而DeferredImportSelector接口继承于ImportSelector类
selectImports方法
ImportSelector接口定义了一个重要方法selectImports
public interface ImportSelector { /** * Select and return the names of which class(es) should be imported based on * the {@link AnnotationMetadata} of the importing @{@link Configuration} class. * @return the class names, or an empty array if none */ String[] selectImports(AnnotationMetadata importingClassMetadata); }
我们主要观看ImportSelector接口的下级DeferredImportSelector接口,但由于DeferredImportSelector为接口,所以真正的逻辑代码在DeferredImportSelector的下级AutoConfigurationImportSelector的selectImports方法中实现。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { private static final String[] NO_IMPORTS = {}; /** * 如果在springBoot配置文件中开启了自动配置 * 那么就获取需要被引入的的自动配置 */ @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } }
什么是如果在springBoot配置文件中开启了自动配置?
这个就是我们常用的application.yml
server: port: 9091 spring: autoconfigure: exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration # datasource: # url: jdbc:mysql://localhost:3306/mall_ams?useUnicode=true&characterEncoding # username: root # password: root
这样在导入jdbc后不需要配置datasource,但mybatis依赖于datasource,仍然报错。
开启DataSourceAutoConfiguration后报错为
-
Error creating bean with name 'adminMapper' defined in
关闭DataSourceAutoConfiguration后报错为
-
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured
同理也可以在@SpringBootApplication上关闭
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
当我们没有配置的时候,默认就是开启自动配置的
查看顺序:this.getAutoConfigurationEntry >>> this.getCandidateConfigurations >>> SpringFactoriesLoader.loadFactoryNames >>> this.loadFactoryNames >>> this.loadSpringFactories
核心方法getAutoConfigurationEntry
接下里继续解析selectImports方法里面的this.getAutoConfigurationEntry,这个是核心方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { // 判断是否开启自动配置 if (!isEnabled(annotationMetadata)) { //如果没有返回一个空的AutoConfigurationEntry return EMPTY_ENTRY; } // 获取@EnableAutoConfiguration注解的属性 AnnotationAttributes attributes = getAttributes(annotationMetadata); // 重点:使用SpringFactoriesLoader从所有的spring.factories文件中获取需要配置的bean全限定名列表, List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 去重 configurations = removeDuplicates(configurations); // 获取注解中exclude或excludeName排除的类集合 Set<String> exclusions = getExclusions(annotationMetadata, attributes); // 检查被排除类是否可以实例化,是否被自动配置所使用,否则抛出异常 checkExcludedClasses(configurations, exclusions); // 去除被排除的类 configurations.removeAll(exclusions); // 使用spring.factories配置文件中配置的过滤器对自动配置类进行过滤 configurations = getConfigurationClassFilter().filter(configurations); // 抛出事件 fireAutoConfigurationImportEvents(configurations, exclusions); //将需要的自动配置 和 被排除的自动配置,一同封装到AutoConfigurationEntry对象 return new AutoConfigurationEntry(configurations, exclusions); }
List<String> configurations 包含所有需要被自动配置的类的,是一个全限定路径名称。
configurations 包含的类在随后都将被注入到IOC容器,完成自动配置。
List<String> configurations结构 如图:
继续深入,很明显,该方法getAutoConfigurationEntry的重点方法是this.getCandidateConfigurations方法,我们来分析一下
/** * Return the auto-configuration class names that should be considered. By default * this method will load candidates using {@link SpringFactoriesLoader} with * {@link #getSpringFactoriesLoaderFactoryClass()}. * @param metadata the source metadata * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation * attributes} * @return a list of candidate configurations */ protected List<String> getCandidateConfigurations( AnnotationMetadata metadata,AnnotationAttributes attributes) { //调用SpringFactoriesLoader.loadFactoryNames,获得自动配置的信息 List<String> configurations = SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader()); //如果自动配置的信息是否为空,则抛出异常 Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
什么是AnnotationMetadata?
AnnotationMetadata用来访问指定类上的注解。
AnnotationMetadata
存在两个实现类分别为 StandardAnnotationMetadata
与 AnnotationMetadataReadingVisitor
。前者主要使用 Java 反射原理获取元数据,而 后者 使用 ASM 框架获取元数据。
FactoryNames
继续深入SpringFactoriesLoaderload类的FactoryNames方法
/** * Load the fully qualified class names of factory implementations of the * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given * class loader. * <p>As of Spring Framework 5.3, if a particular implementation class name * is discovered more than once for the given factory type, duplicates will * be ignored. * @param factoryType the interface or abstract class representing the factory * @param classLoader the ClassLoader to use for loading resources; can be * {@code null} to use the default * @throws IllegalArgumentException if an error occurs while loading factory names * @see #loadFactories */ public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); }
该方法大概是:使用指定的类加载器从所有 “META-INF/spring.factory”路径下把EnableAutoConfiguration对应的的Bean值添加到容器中
具体的实现在下方的this.loadSpringFactories。
总之AutoConfigurationEntry类用于返回导入@Configuration类的AnnotationMetadata。
从 Spring Framework 5.3 开始,如果多次发现给定工厂类型的特定实现类名,则重复项将被忽略。
什么是spring.factories?
spring.factories是一个Spring Boot提供的SPI机制,用于在类路径中的多个JAR文件中加载和实例化特定类型的工厂类。spring.factories文件必须是Properties格式,其中键是接口或抽象类的完全限定名,值是实现类名的逗号分隔列表。
=后面的路径都作为容器中的一个组件,被添加到IoC 容器中,从而实现Spring Boot 的自动配置。
列如:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration是一个类的全限定名称,可直接查看
MybatisAutoConfiguration这个相当于我们以前手动配置的mybatisConf配置类
// spring配置mybatis简单示例 public class MyBatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource source){ SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); sessionFactoryBean.setTypeAliasesPackage("SpringQuickStart.MybatiesBean.domain"); //扫描路径 sessionFactoryBean.setDataSource(source); return sessionFactoryBean; }
也就是说我们以前在spring中需要编写的mybatis配置类,在我们导入mybatis依赖的时候,mybatis已经编写好,不用我们重复编写。
-
mybatis将自己编写的配置类的一个路径放入到spring.factories
-
springBoot扫描spring.factories文件,就能找到mybatis编写的配置类具体路径
-
然后springBoot自动装配这些具体的配置类
但业务千变万化,配置不是完全一样的,如数据源是不确定的,还需要手动配置。
这就是Spring Boot 的约定优于配置,通俗的说,SpringBoot提供了一套默认的配置,可以直接使用,这个默认的配置不合适就自己覆盖
自动配文件一般在spring-boot-autoconfigure包下
如何创建spring.factories?
创建spring.factories的方法是:
-
在你的项目中创建一个类,用@Configuration注解标记,并定义你想要自动配置的bean。例如:
@Configuration public class MyAutoConfiguration { @Bean public MyService myService() { return new MyService(); } }
-
在你的项目的资源目录下,创建一个META-INF/spring.factories文件,用Properties格式指定你的配置类的完全限定名,以EnableAutoConfiguration为键。例如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.MyAutoConfiguration
-
将你的项目打包成一个jar文件,并发布到Maven仓库或者其他地方。
-
在其他需要使用你的自动配置的项目中,添加你的jar文件作为依赖。Spring Boot会在启动时扫描spring.factories文件,并加载其中配置的类
如上,springBoot就扫描完了并且使用类加载器加载了需要被自动配置的配置类,接下里需要注入到springIOC容器
小总结:
AutoConfigurationImportSelector
类是ImportSelector
的实现类,实现了selectImports()
方法。selectImports()
方法又调用getAutoConfigurationEntry()
方法从spring.factories文件中读取配置类的全限定名列表,并进行过滤,最终得到需要自动配置的类全限定名列表。随后spring根据需要自动配置的类全限定名列表,将这些自动注入到容器,完成自动配置
注册自动配置包
而@EnableAutoConfiguration注解的核心是@AutoConfigurationPackage,也就是将之前扫描的类注入到容器
/** * Registers packages with {@link AutoConfigurationPackages}. When no {@link #basePackages * base packages} or {@link #basePackageClasses base package classes} are specified, the * package of the annotated class is registered. * * @author Phillip Webb * @since 1.3.0 * @see AutoConfigurationPackages */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage {}
该AutoConfigurationPackage注解上的前四个都是java的基本注解,不在过多解释
该注解的重点是@Import(AutoConfigurationPackages.Registrar.class)
自动注册包就在这里实现
AutoConfigurationImportSelector和AutoConfigurationPackages的区别
AutoConfigurationImportSelector 和 AutoConfigurationPackages 是 Spring Boot 自动配置的两个重要的类,它们分别用于导入自动配置类和注册自动配置包。
-
AutoConfigurationImportSelector 是一个实现了 ImportSelector 接口的类,它会根据类路径中的依赖和条件注解(如 @ConditionalOnClass、@ConditionalOnMissingBean 等)来选择合适的配置类,并将它们返回给 @Import 注解,从而导入到容器中。这些配置类都是以 AutoConfiguration 结尾的,位于 org.springframework.boot.autoconfigure 包下,或者在 META-INF/spring.factories 文件中声明。
-
AutoConfigurationPackages 是一个抽象类,它有一个内部类 Registrar,实现了 ImportBeanDefinitionRegistrar 接口,它会根据 @EnableAutoConfiguration 注解所在的类的包名,来注册一个名为 org.springframework.boot.autoconfigure.AutoConfigurationPackages 的 BeanDefinition,该 BeanDefinition 的属性值就是包名的集合。这样,其他的自动配置类就可以通过这个 BeanDefinition 来获取需要扫描的包名。
总结1:
spirngBoot依赖于@SpringBootApplication注解完成自动配置
@SpringBootApplication依赖于@EnableAutoConfiguration
@EnableAutoConfiguration依赖于AutoConfigurationImportSelector类,该类需要扫描所有需要被自动配置的类
扫描完成后自然需要注入到ioc容器
@EnableAutoConfiguration依赖于@AutoConfigurationPackage
@AutoConfigurationPackage依赖于AutoConfigurationPackages.Registrar内部内
总结2:
springBoot使用AutoConfigurationImportSelector类扫描在spring.factories文件中的配置类
springBoot使用AutoConfigurationPackages类将之前扫描的类注入到ioc容器