Springboot自动装配

红太狼 2024-03-16 10:39 133阅读 0赞

一.自动装配

自动装配是springboot的核心,一般提到自动装配就会和springboot联系在一起。实际上 Spring Framework 早就实现了这个功能。Spring Boot 只是在其基础上,通过 SPI 的方式,做了进一步优化。

二.自动装配接口规范

SpringBoot 在启动时会扫描外部引用 jar 包中的 META-INF/spring.factories 文件,将文件中配置的类型信息加载到 Spring 容器,并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。

没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦。但是,Spring Boot 中,我们直接引入一个 starter 即可。引入 starter 之后,我们通过少量注解和一些简单的配置就能使用第三方组件提供的功能了。

所以说,其实自动装配可以简单的理解为:**通过注解或者一些简单的配置就能在spring boot的帮助下实现某款功能。**

三. 自动装配的实现

我们看一下 @SpringBootApplication 注解

d343b2d36a764cb7bd1810d2002e6f9a.png

@SpringBootApplication是一个复合注解,可以看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan 注解的集合。

1. @Target

@Target用于指定其他注解可以应用的元素类型。它可以用来限制注解可以放置的目标元素,例如类、方法、字段等。

ElementType.TYPE:类、接口(包括注解类型)、枚举等类型。

ElementType.FIELD:字段(包括枚举常量)。

ElementType.METHOD:方法。

ElementType.PARAMETER:方法或构造函数的参数。

ElementType.CONSTRUCTOR:构造函数。

ElementType.LOCAL_VARIABLE:局部变量。

ElementType.ANNOTATION_TYPE:注解类型。

ElementType.PACKAGE:包。

ElementType.TYPE_PARAMETER:类型参数(Java 8 新增),可以应用于类的泛型声明之处。

ElementType.TYPE_USE:用于指定注解可以应用于类型使用的地方(Java 8 新增)。

2. @Retention

@Retention 是一个元注解(meta-annotation), 用于指定注解的保留策略,即注解在编译后的类文件中的保留方式。它可以应用于其他自定义注解的声明上。

RetentionPolicy.SOURCE:注解仅保留在源代码中,编译后的类文件中不包含该注解。这意味着该注解在运行时不可见,仅用于编译时的静态检查。

RetentionPolicy.CLASS:注解保留在编译后的类文件中,但在运行时不可访问。这是默认的保留策略,如果注解未指定保留策略,则默认为 RetentionPolicy.CLASS。

RetentionPolicy.RUNTIME:注解保留在编译后的类文件中,并在运行时可以通过反射机制访问。这意味着可以在运行时获取注解的信息,并根据注解执行相应的逻辑。

3. @Documented

@Documented 是一个元注解(meta-annotation),用于指示被它注解的注解应该包含在生成的文档中。

4. @Inherited

@Inherited 是一个元注解(meta-annotation),当一个注解使用了 @Inherited,并且被该注解注解的类被作为父类继承时,子类将自动继承父类的该注解。换句话说,子类会自动拥有父类上被 @Inherited 注解标记的注解。

5. @SpringBootConfiguration

@SpringBootConfiguration 注解是 Spring Boot 特有的注解,它是对 Spring Framework 中的 @Configuration 注解的特化,专门用于 Spring Boot 应用程序。在大多数情况下,我们可以直接使用 @Configuration 注解来代替 @SpringBootConfiguration 注解,它们具有相同的效果。

6. @Configuration

允许在上下文中注册额外的 bean 或导入其他配置类,作用与 ApplicationContext.xml 的功能相同。

7. @ComponentScan

扫描包下的类中添加了@Component (@Service,@Controller,@Repostory,@RestController)注解的类 ,并添加的到spring的容器中。

TypeExcludeFilter.class: 是Spring框架中用于排除特定类型组件的过滤器类,当组件扫描过程中遇到被TypeExcludeFilter排除的类型时,这些类型的组件将不会被注册为bean,从而被忽略。。

AutoConfigurationExcludeFilter.class: 使用一些规则和条件来判断哪些自动配置类应该被排除。这些规则和条件通常是基于应用程序的配置、已经显式声明的bean以及其他相关因素。通过排除某些自动配置类,可以控制应用程序上下文中的bean创建和配置,以满足特定需求或避免冲突。

8. @EnableAutoConfiguration

实现自动装配的核心注解, 通常被放置在应用程序的主配置类上(通常是带有 @SpringBootApplication 注解的类),以启用自动配置。当应用程序启动时,Spring Boot 会自动扫描类路径上的各种配置类和依赖,根据约定和条件进行自动配置。

f7f253e7bb0d4a4baec98dbfc8f939fb.png

8.1 @AutoConfigurationPackage

用于指示 Spring Boot 自动配置的基础包。在 Spring Boot 应用程序中,自动配置是通过扫描特定包及其子包下的组件来实现的。**@AutoConfigurationPackage 注解用于指定这些组件扫描的基础包。它通常被放置在应用程序的主配置类上,以指示 Spring Boot 自动配置的基础包路径**。当使用 @AutoConfigurationPackage 注解时,它会将注解所在类所在的包路径作为自动配置的基础包。这样,Spring Boot 将会扫描这个基础包及其子包下的组件,并根据约定和条件进行自动配置。

8.2 @Import

通过使用 @Import 注解,可以将其他的配置类或组件引入到当前的配置类中,以扩展配置或添加额外的组件。被引入的配置类或组件将会被 Spring 容器管理,并参与应用程序的装配和协作。被引入的配置类或组件类通常需要使用合适的注解进行标记,例如 @Configuration、@Component 等。这样,它们才能被正确地识别和管理。

8.3 AutoConfigurationImportSelector.class

自动装配核心功能的实现实际是通过 AutoConfigurationImportSelector类, AutoConfigurationImportSelector 类实现了 ImportSelector接口,也就实现了这个接口中的 selectImports 方法,该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中。

a. selectImports()方法

接收一个 AnnotationMetadata 参数,用于获取当前被注解标记的类的元数据信息。根据元数据信息,该方法会返回一个字符串数组,表示要导入的自动配置类的全限定类名。

  1. @Override
  2. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  3. // 判断自动装配开关是否打开, 可在 application.properties 或 application.yml 中设置
  4. if (!this.isEnabled(annotationMetadata)) {
  5. return NO_IMPORTS;
  6. } else {
  7. // 加载自动配置元数据,返回一个 AutoConfigurationMetadata 对象。该对象包含了自动配置类的条件和属性信息。这些信息来自spring-boot-autoconfigure.jar包下
  8. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
  9. // 传入自动配置元数据和当前被注解标记的类的元数据信息,获取一个 AutoConfigurationEntry 对象。AutoConfigurationEntry 对象包含了根据条件筛选后的自动配置类的信息。
  10. AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
  11. // 获取筛选后的自动配置类的全限定类名
  12. return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  13. }
  14. }

b.getAutoConfigurationEntry()方法

  1. protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
  2. // 判断自动装配开关是否打开, 可在 application.properties 或 application.yml 中设置
  3. if (!this.isEnabled(annotationMetadata)) {
  4. return EMPTY_ENTRY;
  5. } else {
  6. // 获取注解元数据的属性信息,并保存到 attributes 变量中。
  7. AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
  8. // 根据注解元数据和属性信息获取候选的自动配置类的全限定类名列表,并保存到 configurations 变量中。从 META-INF/spring.factories 下找
  9. List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
  10. // 去除重复的自动配置类
  11. configurations = this.removeDuplicates(configurations);
  12. // 获取需要排除的自动配置类的全限定类名列表
  13. Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
  14. // 检查排除的自动配置类是否存在于候选的自动配置类列表中, 如果排除类存在且不在候选的自动配置类列表中, 则为无效的排除类, 抛出异常
  15. this.checkExcludedClasses(configurations, exclusions);
  16. // 从 configurations 列表中移除 exclusions 列表中的自动配置类。
  17. configurations.removeAll(exclusions);
  18. // 根据自动配置元数据对 configurations 列表进行过滤,保留满足条件的自动配置类。
  19. configurations = this.filter(configurations, autoConfigurationMetadata);
  20. // 触发自动配置导入的事件
  21. this.fireAutoConfigurationImportEvents(configurations, exclusions);
  22. // 返回最终的自动配置类列表 configurations 和排除的自动配置类列表 exclusions。
  23. return new AutoConfigurationEntry(configurations, exclusions);
  24. }
  25. }

c. getCandidateConfigurations()方法

用于获取候选的自动配置类列表,这些类是通过读取 META-INF/spring.factories 文件中的配置获取的。

  1. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  2. // 使用 SpringFactoriesLoader 加载指定类加载器下 META-INF/spring.factories 文件中的自动配置类配置。
  3. // getSpringFactoriesLoaderFactoryClass() 方法用于获取 SpringFactoriesLoader 的工厂类。该工厂类是一个用于加载 META-INF/spring.factories 文件中配置的实现类。
  4. // this.getBeanClassLoader() 方法用于获取当前使用的类加载器,以便在加载自动配置类时使用。
  5. List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
  6. 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.");
  7. return configurations;
  8. }

d.loadFactoryNames() 方法

用于加载指定工厂类的工厂名称列表,这些工厂名称是通过读取 META-INF/spring.factories 文件中的配置获取的。

  1. public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
  2. // 获取指定工厂类的完全限定类名
  3. String factoryClassName = factoryClass.getName();
  4. // 方法加载 META-INF/spring.factories 文件中的配置,并返回一个 Map<String, List<String>> 类型的结果。其中键是工厂类的完全限定类名,值是工厂类的名称列表。
  5. return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
  6. }

e.loadSpringFactories() 方法

该方法用于加载 META-INF/spring.factories 文件中的配置,并返回一个映射,其中键是工厂类的完全限定类名,值是工厂类的名称列表。

  1. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  2. // 从缓存中获取与给定类加载器相关联的配置映射 result。
  3. MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
  4. // 如果缓存中存在配置映射,则直接返回该映射
  5. if (result != null) {
  6. return result;
  7. } else {
  8. try {
  9. // 根据给定的类加载器 classLoader 获取 META-INF/spring.factories 文件的 URL 枚举,如果类加载器为 null,则使用系统类加载器获取 URL 枚举。
  10. Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
  11. // 保存加载到的配置映射,其中键是工厂类的完全限定类名,值是工厂类的名称列表。
  12. MultiValueMap<String, String> result = new LinkedMultiValueMap();
  13. // 遍历 URL 枚举中的每个 URL, 每个URL为一个 spring.factories 文件
  14. while(urls.hasMoreElements()) {
  15. URL url = (URL)urls.nextElement();
  16. // 读取该 URL 对应的资源文件。
  17. UrlResource resource = new UrlResource(url);
  18. // 加载资源文件中的属性
  19. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  20. // 遍历 properties 文件中的每一对键值对
  21. Iterator var6 = properties.entrySet().iterator();
  22. while(var6.hasNext()) {
  23. Map.Entry<?, ?> entry = (Map.Entry)var6.next();
  24. // 对于每个键值对,将键转换为工厂类的完全限定类名,并将值转换为工厂类的名称列表。
  25. // (因为多个 properties文件中可能含有相同的key, 所以要采用 MultiValueMap 的数据结构)
  26. String factoryClassName = ((String)entry.getKey()).trim();
  27. // 将每个文件中的 key 值转换为字符串数组
  28. String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
  29. int var10 = var9.length;
  30. for(int var11 = 0; var11 < var10; ++var11) {
  31. String factoryName = var9[var11];
  32. // 以列表的形式向 key 的 value 中添加值
  33. result.add(factoryClassName, factoryName.trim());
  34. }
  35. }
  36. }
  37. // 将 result 映射添加到缓存中,与给定的类加载器关联。
  38. cache.put(classLoader, result);
  39. return result;
  40. } catch (IOException var13) {
  41. throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
  42. }
  43. }
  44. }

注: MultiValueMap 是 springframework 提供的一个 key value 结构体, 可以实现一键多值的数据结构, 比如key为 “name”, 添加值时不会覆盖, 而且以列表的形式追加, 如[“小明”, “小红”]

总结: Spring Boot 通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过@Conditional按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖。

发表评论

表情:
评论列表 (有 0 条评论,133人围观)

还没有评论,来说两句吧...

相关阅读