从 Spring 进入到 SpringBoot 有一个最直观的感受 —— 省去了大量 xml 的配置,一切看起来都是十分简洁高效的
 
什么是自动装配 现在提到自动装配都默认是 SpringBoot,其实 Spring 很早就实现了这个功能,SpringBoot 只是在其基础上,通过 SPI 的方式,做了进一步的优化
SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时 会扫描外部引用 jar 包中的 META-INF/spring.factories 文件,按照文件中配置的类信息加载到 Spring 容器中,并执行类中定义的各种操作。对于外部 jar 来说,只需按照 SpringBoot 定义的标准,就能将自己的功能装配进 SpringBoot
SpringBoot3.0 之后不再使用 SpringFactoriesLoader,而是 Spring 重新从 META-INFO/spring/ 目录下的 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中读取,其本质还是不变的,只是文件路径和文件名的改变
 
如何自动装配 既然是启动时自动装配的,先看启动时的核心注解 @SpringBootApplication
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 @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 {    @AliasFor(          annotation = EnableAutoConfiguration.class     )     Class<?>[] exclude() default  {};     @AliasFor(          annotation = EnableAutoConfiguration.class     )     String[] excludeName() default  {};     @AliasFor(          annotation = ComponentScan.class,         attribute = "basePackages"     )     String[] scanBasePackages() default  {};     @AliasFor(          annotation = ComponentScan.class,         attribute = "basePackageClasses"     )     Class<?>[] scanBasePackageClasses() default  {};     @AliasFor(          annotation = ComponentScan.class,         attribute = "nameGenerator"     )     Class<? extends  BeanNameGenerator > nameGenerator() default  BeanNameGenerator.class;     @AliasFor(          annotation = Configuration.class     )     boolean  proxyBeanMethods ()  default  true ; } 
 
@SpringBootApplication 由三个注解组成:@SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan,其中 @SpringBootConfiguration 相当于是 @Configuration
1 2 3 4 5 6 7 8 9 10 11 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration @Indexed public  @interface  SpringBootConfiguration {    @AliasFor(          annotation = Configuration.class     )     boolean  proxyBeanMethods ()  default  true ; } 
 
三个关键注解的作用:
@Configuration :允许上下文中注册额外的 Bean 或导入其他配置类 
@EnableAutoConfiguration :启用 SpringBoot 的自动装配 
@ComponentScan :指定包扫描路径(默认为启动类所有包下),可指定基础包以及排除某些类 
 
@EnableAutoConfiguration 工作原理 @EnableAutoConfiguration 是靠 AutoConfigurationImportSelector 进行解析加载的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public  class  AutoConfigurationImportSelector  implements  DeferredImportSelector , BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {    private  static  final  String[] NO_IMPORTS = new  String [0 ]; public  String[] selectImports(AnnotationMetadata annotationMetadata) {                 if  (!this .isEnabled(annotationMetadata)) {             return  NO_IMPORTS;         } else  {           	             AutoConfigurationMetadata  autoConfigurationMetadata  =  AutoConfigurationMetadataLoader.loadMetadata(this .beanClassLoader);             AutoConfigurationImportSelector.AutoConfigurationEntry  autoConfigurationEntry  =  this .getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);             return  StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());         }     } } public  interface  DeferredImportSelector  extends  ImportSelector  {} public  interface  ImportSelector  {    String[] selectImports(AnnotationMetadata var1); } 
 
AutoConfigurationImportSelector 实现了 ImportSelector 接口的 selectImports 方法,方法里的关键在于 getAutoConfigurationEntry
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private  static  final  AutoConfigurationEntry  EMPTY_ENTRY  =  new  AutoConfigurationEntry ();AutoConfigurationEntry getAutoConfigurationEntry (AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata)  {          if  (!this .isEnabled(annotationMetadata)) {         return  EMPTY_ENTRY;     } else  {                  AnnotationAttributes  attributes  =  this .getAttributes(annotationMetadata);                  List<String> configurations = this .getCandidateConfigurations(annotationMetadata, attributes);                  configurations = this .removeDuplicates(configurations);                  Set<String> exclusions = this .getExclusions(annotationMetadata, attributes);                  this .checkExcludedClasses(configurations, exclusions);                  configurations.removeAll(exclusions);                  configurations = this .filter(configurations, autoConfigurationMetadata);                  this .fireAutoConfigurationImportEvents(configurations, exclusions);                  return  new  AutoConfigurationImportSelector .AutoConfigurationEntry(configurations, exclusions);     } } 
 
与 SPI 不同的是,SpringBoot 并不会将所有的配置都装载进来,装载的策略会按条件进行过滤,过滤方法在第 21 行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 private  List<String> filter (List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata)  {	long  startTime  =  System.nanoTime(); 	 	String[] candidates = StringUtils.toStringArray(configurations); 	 	boolean [] skip = new  boolean [candidates.length]; 	boolean  skipped  =  false ; 	 	Iterator  var6  =  this .filters.iterator(); 	 	int  i;          while (var6.hasNext()) { 		         AutoConfigurationImportFilter  filter  =  (AutoConfigurationImportFilter)var6.next();          		 		boolean [] match = filter.match(candidates, this .autoConfigurationMetadata); 		 		for  (int  i  =  0 ; i < match.length; i++) { 			 			if  (!match[i]) { 				 				skip[i] = true ; 				 				candidates[i] = null ; 				 				skipped = true ;  			} 		} 	}  	 	if  (!skipped) { 		return  configurations; 	} else  {                  List<String> result = new  ArrayList <>(candidates.length);          for  (int  i  =  0 ; i < candidates.length; i++) {                          if  (!skip[i]) {                  result.add(candidates[i]);             }         }                  if  (logger.isTraceEnabled()) {             int  numberFiltered  =  configurations.size() - result.size();             logger.trace("Filtered "  + numberFiltered + " auto configuration class in "                      + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)                     + " ms" );         }                  return  result;     } } 
 
这里的 filter 方法主要就是 通过 AutoConfigurationImportFilter 接口的 match 方法来判断每个自动配置类上面的条件注解(@ConditionOnClass、@ConditionOnBean 和 @ConditionOnWebApplication)是否满足
整体流程 SpringBoot 自动装配的整体流程:
利用 SPI 机制,从配置文件中读取需要自动配置的类 
过滤 @EnableAutoConfiguration 中 exclude 属性所要排除的类 
再判断 @ConditionOnClass、@ConditionOnBean 和 @ConditionOnWebApplication 这三个条件注解是否满足条件 
然后触发AutoConfigurationImportEvent事件,告诉ConditionEvaluationReport条件评估报告器对象来分别记录符合条件和exclude的自动配置类 
最后将筛选后的自动配置类导入 IOC 容器中 
 
创建一个自己的自动装配 Starter 
基于 SpringBoot 3.X
 
模块 如果项目具有多种风格、选项或者可选特性,通常会定义两个模块 —— autoconfigure 和 starter
如果自动配置相对简单,没有可选特性,那么可以合并为一个 starter
命名 自动配置模块通常命名为:xxx-spring-boot-autoconfigure
starter 模块通常命名为:xxx-spring-boot-starter
配置参数 项目自定义的一些配置参数为确保命名空间的唯一,通常需要以项目名为开头
1 2 3 4 @ConfigurationProperties("xxx") public  class  XxxProperties  {    private  String  name  =  "xxx" ; } 
 
SpringBoot 内部针对配置的相关规则:
不要使用「The」或者「A」开头 
对于布尔类型,用「Whether」或者「Enable」 
对于集合类型,使用逗号分隔的列表 
使用 java.time.Duration 而不是 long,并描述与毫秒不同的默认单位 
为确保配置的正确生成,需要检查生成的元数据 META-INF/spring-configuration-metadata.json 
 
autoconfigure模块包含starter模块库所需的所有内容。它还可能包含配置键定义(例如@ConfigurationProperties)和任何回调接口,这些接口可以用于进一步定制组件的初始化方式
SpringBoot 使用一个注释处理器来收集元数据文件中自动配置的条件( META-INF/spring-autoconfiguration-metadata.properties)。如果该文件存在,它将用于主动过滤不匹配的自动配置,这将提高启动时间。因此建议在包含自动配置的模块中添加以下依赖项:
1 2 3 4 5 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-autoconfigure-processor</artifactId >      <optional > true</optional >  </dependency > 
 
如果在应用程序中直接定义了自动配置,确保配置了 spring-boot-maven-plugin,以防止重新打包目标将依赖项添加到 fat.jar 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <project >     <build >          <plugins >              <plugin >                  <groupId > org.springframework.boot</groupId >                  <artifactId > spring-boot-maven-plugin</artifactId >                  <configuration >                      <excludes >                          <exclude >                              <groupId > org.springframework.boot</groupId >                              <artifactId > spring-boot-autoconfigure-processor</artifactId >                          </exclude >                      </excludes >                  </configuration >              </plugin >          </plugins >      </build >  </project > 
 
starter 模块 starter 模块其实是一个空的 jar,它的唯一目的是提供使用库所需的依赖项
示例 
 开发一个模拟发送邮件的自定义 starter,最终的效果任何其它的项目只需引入 starter 配置基本的邮件配置,就可以使用 SendMailService 发送邮件。项目的结构 autoconfigure 和 starter 模块分开
 
1. 新建项目 email-spring-boot 设置顶级 pom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <groupId > net.venom24</groupId > <artifactId > email-spring-boot</artifactId > <version > 0.0.1-SNAPSHOT</version > <name > email-spring-boot</name > <description > 模拟 email 发送 starter</description > <modules >     <module > email-spring-boot-autoconfigure</module >      <module > email-spring-boot-starter</module >  </modules > <packaging > pom</packaging > <dependencyManagement >      <dependencies >           <dependency >                             <groupId > org.springframework.boot</groupId >               <artifactId > spring-boot-dependencies</artifactId >               <version > 3.0.0-M2</version >               <type > pom</type >               <scope > import</scope >           </dependency >       </dependencies >  </dependencyManagement > 
 
添加依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <parent >     <groupId > net.venom24</groupId >      <artifactId > email-spring-boot</artifactId >      <version > 0.0.1-SNAPSHOT</version >  </parent > <artifactId > email-spring-boot-autoconfigure</artifactId > <packaging > jar</packaging > <description >     autoconfigure </description > <dependencies >          <dependency >          <groupId > org.springframework.boot</groupId >          <artifactId > spring-boot-autoconfigure</artifactId >      </dependency >           <dependency >          <groupId > org.springframework.boot</groupId >          <artifactId > spring-boot-configuration-processor</artifactId >          <optional > true</optional >      </dependency >  </dependencies > <build >     <plugins >          <plugin >              <groupId > org.springframework.boot</groupId >              <artifactId > spring-boot-maven-plugin</artifactId >              <configuration >                  <excludes >                      <exclude >                          <groupId > org.springframework.boot</groupId >                          <artifactId > spring-boot-autoconfigure-processor</artifactId >                      </exclude >                  </excludes >              </configuration >          </plugin >      </plugins >  </build > 
 
添加配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Getter @Setter @ConfigurationProperties("email.service") public  class  EmailProperties  {         private   boolean  enable=true ;          private  String host;          private  Integer port;          private  String name;          private  String password; } 
 
添加模拟邮件发送功能 EmailService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  class  EmailService  {    private  EmailProperties mailProperties;     public  EmailService (EmailProperties mailProperties)  {         this .mailProperties = mailProperties;     }          public  void  send (String content)  {         System.out.println("开始发送邮件:" );         String  info  =  "host:%s,port:%s" ;         System.out.println(String.format(info, mailProperties.getHost(), mailProperties.getPort()));         System.out.println("发送内容:"  + content);         System.out.println("发送成功!" );     } } 
 
添加自动配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Configuration @ConditionalOnClass(EmailService.class) @EnableConfigurationProperties(value = EmailProperties.class) public  class  EmailAutoConfiguration  {    private  final  EmailProperties mailProperties;     public  EmailAutoConfiguration (EmailProperties mailProperties)  {         this .mailProperties = mailProperties;     }     @Bean      @ConditionalOnMissingBean(EmailService.class)      @ConditionalOnProperty(prefix = "email.service", value = "enable", havingValue = "true")      public  EmailService mailService (EmailProperties mailProperties)  {         return  new  EmailService (mailProperties);     } } 
 
简单解释下各个注解:
@Configuration :声明配置类 
**@ConditionalOnClass(EmailService.class)**:只有在 classpath 中找到 EmailService 类的情况下才会解析这个自动配置类 
**@EnableConfigurationProperties(value = EmailProperties.class)**:启用自动装配 
@Bean :实例化一个 Bean 
**@ConditionalOnMissingBean(EmailService.class)**:与 @Bean 配合使用,只有当 Spring 上下文中不存在 EmailService 时才会执行该实例化方法 
**@ConditionalOnProperty(prefix = “email.service”, value = “enable”, havingValue = “true”)**: 当 classpath 中存在 EmailService 类时解析此配置类,并且在 Spring 上下文中没有 EmailService 的 bean 实例的情况下,new 一个实例出来,然后将应用配置中的相关配置值传入 
 
添加自动装配文件
META-INF/spring/ 下新建 org.springframework.boot.autoconfigure.AutoConfiguration.imports
1 net.venom24.email.config.EmailAutoConfiguration 
 
3. 新建模块 email-spring-boot-starter 添加依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <parent >     <groupId > net.venom24</groupId >      <artifactId > email-spring-boot</artifactId >      <version > 0.0.1-SNAPSHOT</version >  </parent > <artifactId > email-spring-boot-starter</artifactId > <packaging > jar</packaging > <description >     starter </description > <dependencies >     <dependency >          <groupId > net.venom24</groupId >          <artifactId > email-spring-boot-autoconfigure</artifactId >          <version > 0.0.1-SNAPSHOT</version >      </dependency >  </dependencies > 
 
email-spring-boot-starter 模块主要是集成 email-spring-boot-autoconfigure,以及一些其它的依赖项
4. 测试 新建一个测试项目,引入 email-spring-boot-starter
1 2 3 4 5 6 7 8 9 10 <dependency >     <groupId > org.example</groupId >      <artifactId > email-spring-boot-starter</artifactId >      <version > 1.0-SNAPSHOT</version >  </dependency > <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-test</artifactId >      <scope > test</scope >  </dependency > 
 
配置参数
1 2 3 4 5 email.service.enable =true email.service.host =mail.qq.com email.service.port =123 email.service.name =admin email.service.password =admin 
 
新建测试类测试
1 2 3 4 5 6 7 8 9 @SpringBootTest class  EmailStarterTestApplicationTests  {    @Autowired      private  EmailService emailService;     @Test      void  contextLoads ()  {         emailService.send("我是自定义starter发送的邮件" );     } } 
 
Output:
1 2 3 4 开始发送邮件 host:mail.qq.com,port:123 发送内容:我是自定义starter发送的邮件 发送成功! 
 
附 SpringBoot 提供的条件注解
@ConditionalOnBean :当容器里有指定 Bean 的条件下 
@ConditionalOnMissingBean :当容器里没有指定 Bean 的情况下 
@ConditionalOnSingleCandidate :当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean 
@ConditionalOnClass :当类路径下有指定类的条件下 
@ConditionalOnMissingClass :当类路径下没有指定类的条件下 
@ConditionalOnProperty :指定的属性是否有指定的值 
@ConditionalOnResource :类路径是否有指定的值 
@ConditionalOnExpression :基于 SpEL 表达式作为判断条件 
@ConditionalOnJava :基于 Java 版本作为判断条件 
@ConditionalOnJndi :在 JNDI 存在的条件下差在指定的位置 
@ConditionalOnNotWebApplication :当前项目不是 Web 项目的条件下 
@ConditionalOnWebApplication :当前项目是 Web 项 目的条件下