自动装配

自动装配本身是一个新的概念,相对于Spring Framework来说的话,因为 Spring Framework 本身只有一种方式,就是手动装配,Spring Boot基于Spring Framework原生组件装配进行了延伸,具体如何延伸下面会解释。

组件装配

要理解组件装配,首先要理解组件是什么,因为组件装配的核心就是对组件进行装配,组件其实就是各式各样的bean对象,组件装配就是把bean对象放入到Spring容器中管理。

手动装配

+ XML配置 + 基于注解类的手动装配 : @Configuration配合@Bean使用 + 基于组件扫描的手动装配: @ComponentScan配合@Component等等注解使用

模块装配

自动装配

SpringBoot实现自动装配是在Spring Framework原生组件装配进行了延伸:

基于Spring Framework的手动装配,通过模块装配+条件装配+SPI实现自动装配

同时Spring Boot的自动装配可以实现配置禁用,通过在@SpringBootApplication注解或者@EnableAutoConfiguration注解上标注exclude/excludeName属性,可以禁用默认的自动配置类。这种禁用方式和在Spring Boot的全局配置文件中配置spring。autoconfigure.exclude同样效果。

模块装配

+ @Import注解 + 实现ImportSelector接口 + 实现ImportBeanDefinitionRegistrar接口 + 实现DeferredImportSelector接口
// 新建TestImport类,放在了启动类的上级目录中,为了防止被扫描到
public class TestImport {
}
// 新建注解,这个注解就是为了承载 @Import注解, 先加上TestImport.class
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({TestImport.class)
public @interface ImportAnno {
}
// 新建配置类
@ImportAnno
@Configuration
public class TestImportConfigure {
}
// 新建测试输出类
@Component
@Slf4j
public class TestImportFactoryAware implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
log.info("是否存在 TestImport : {}",beanFactory.containsBean(TestImport.class.getName()));
}
}

启动程序,会发现控制台打印:

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestImport : true

//  继续在启动类的上层目录中新建TestImportSelector类
public class TestImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{TestImportSelectorTarget.class.getName()};
}
}
// 并且在TestImportSelector同级目录新建TestImportSelectorTarget类
public class TestImportSelectorTarget {
}
// 修改 ImportAnno 注解,加上TestImportSelector.class类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({TestImport.class, TestImportSelector.class})
public @interface ImportAnno {
}
// 修改测试类
@Component
@Slf4j
public class TestImportFactoryAware implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
log.info("是否存在 TestImport : {}",beanFactory.containsBean(TestImport.class.getName()));
log.info("是否存在  TestImportSelectorTarget: {}",beanFactory.containsBean(TestImportSelectorTarget.class.getName()));
log.info("是否存在  TestImportSelector: {}",beanFactory.containsBean(TestImportSelector.class.getName()));
}
}

启动程序,会发现控制台打印:

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestImport : true

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestImportSelectorTarget: true

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestImportSelector: false

可以看到通过实现ImportSelector接口的方式,其实只会创建TestImportSelectorTarget对象,不会创建TestImportSelector中间对象。spring.factories文件的读取就是利用了ImportSelector接口。

// 同样的, 在启动类上级目录新建
public class TestImportBeanDefinitionRegistryTarget {
}
// 在这个同级目录下新建
public class TestImportBeanDefinitionRegistry implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition(TestImportBeanDefinitionRegistryTarget.class.getName(), new RootBeanDefinition(TestImportBeanDefinitionRegistryTarget.class));
}
}
// 修改 ImportAnno 类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({TestImport.class, TestImportSelector.class, TestImportBeanDefinitionRegistry.class})
public @interface ImportAnno {
}
// 修改测试类
@Component
@Slf4j
public class TestImportFactoryAware implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
log.info("是否存在 TestImport : {}",beanFactory.containsBean(TestImport.class.getName()));
log.info("是否存在  TestImportSelectorTarget: {}",beanFactory.containsBean(TestImportSelectorTarget.class.getName()));
log.info("是否存在  TestImportSelector: {}",beanFactory.containsBean(TestImportSelector.class.getName()));
log.info("是否存在 TestImportBeanDefinitionRegistryTarget: {}",beanFactory.containsBean(TestImportBeanDefinitionRegistryTarget.class.getName()));
}
}

启动程序,会发现控制台打印:

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestImport : true

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestImportSelectorTarget: true

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestImportSelector: false

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestImportBeanDefinitionRegistryTarget: true

这里需要注意的是TestImportBeanDefinitionRegistry类中的registerBeanDefinitions方法中,调用registry.registerBeanDefinition注册bean定义的时候,第一个参数不一定是 类全名,但是一定要和beanFactory.containsBean中指定的beanName保持一致。

// 同样的, 在启动类上级目录新建
public class TestDeferredImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{TestDeferredImportSelectorTarget.class.getName()};
}
}
//  同样目录
public class TestDeferredImportSelectorTarget {
}
// 修改注解类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({TestImport.class, TestImportSelector.class, TestImportBeanDefinitionRegistry.class, TestDeferredImportSelector.class})
public @interface ImportAnno {
}
// 修改测试类
@Component
@Slf4j
public class TestImportFactoryAware implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
log.info("是否存在 TestImport : {}",beanFactory.containsBean(TestImport.class.getName()));
log.info("是否存在  TestImportSelectorTarget: {}",beanFactory.containsBean(TestImportSelectorTarget.class.getName()));
log.info("是否存在  TestImportSelector: {}",beanFactory.containsBean(TestImportSelector.class.getName()));
log.info("是否存在 TestImportBeanDefinitionRegistryTarget: {}",beanFactory.containsBean(TestImportBeanDefinitionRegistryTarget.class.getName()));
log.info("是否存在 TestImportBeanDefinitionRegistry: {}",beanFactory.containsBean(TestImportBeanDefinitionRegistry.class.getName()));
log.info("是否存在 TestDeferredImportSelectorTarget: {}",beanFactory.containsBean(TestDeferredImportSelectorTarget.class.getName()));
log.info("是否存在 TestDeferredImportSelector: {}",beanFactory.containsBean(TestDeferredImportSelector.class.getName()));
}

启动程序,会发现控制台打印:

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestImport : true

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestImportSelectorTarget: true

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestImportSelector: false

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestImportBeanDefinitionRegistryTarget: true

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestImportBeanDefinitionRegistry: false

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestDeferredImportSelectorTarget: true

c.e.d.y.m.d.TestImportFactoryAware : 是否存在 TestDeferredImportSelector: false

DeferredImportSelector接口从实现方式上来看,其实和ImportSelector一样,也是实现接口,返回全限定类名数组,两者唯一的区别就是** 执行时机。**


ImportSelector执行时机是在注解配置类的解析期间,此时配置类中的@Bean方法还没有开始解析;而DeferredImportSelector则是在注解配置类完全解析后,才开始执行的,这样的目的就是为了配置条件装配。

执行时机,由早到晚:

ImportSelector ➡️ DeferredImportSelector ➡️ ImportBeanDefinitionRegistrar


条件装配

SpringFrameWork提供了两种条件装配的方式: @Profile和@Conditional注解

@Profile注解

@Profile("dev")
@Slf4j
@Component
public class TestProfileAnno implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
log.info("TestProfileAnno afterPropertiesSet");
}
}
在applicaiton.yaml中指定生效的配置文件为test
spring:
profiles:
active:
- test

此时控制台不会打印输出语句;将生效配置文件修改为 dev,控制台就会打印出输出语句,根据配置文件中配置属性值来作为条件装配,不是很灵活,可以将@profile注解结合命令行参数来使用,在命令行参数中来指定生效文件会更加灵活。但是和@Conditional注解相比,就显得相形见绌了,功能就没有@Conditional注解强大。

@Conditional注解

@Conditional注解的作用就是,符合@Conditional注解中指定条件之后才会装配bean对象。@Conditional注解有多种:
  • <font style="color:rgb(5, 7, 59);background-color:rgb(253, 253, 254);">@ConditionalOnBean</font>:当容器中存在特定bean时。
  • <font style="color:rgb(5, 7, 59);background-color:rgb(253, 253, 254);">@ConditionalOnMissingBean</font>:当容器中不存在特定bean时。
  • <font style="color:rgb(5, 7, 59);background-color:rgb(253, 253, 254);">@ConditionalOnClass</font>:当类路径中存在特定类时。
  • <font style="color:rgb(5, 7, 59);background-color:rgb(253, 253, 254);">@ConditionalOnMissingClass</font>:当类路径中不存在特定类时。
  • <font style="color:rgb(5, 7, 59);background-color:rgb(253, 253, 254);">@ConditionalOnProperty</font>:当指定的属性有特定值时。
  • 自定义条件,实现Condition接口(不是Conditional注解,是接口!!!)

测试@`ConditionalOnBean` 注解
```sql @Component public class TestConditionalFirst { }

@Component
@ConditionalOnBean(TestConditionalFirst.class)
public class TestConditionalSecond implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println(“是否已经存在 first bean” + beanFactory.getBean(TestConditionalFirst.class));
}
}

启动项目之后会发现打印如下:
是否已经存在 first beancom.example.demo.practise.conditionAnno.TestConditionalFirst@688d411b

测试自定义注解
```sql
public class SelfConditional implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 既有first 也有 second
        if (context.getBeanFactory().containsBeanDefinition(TestConditionalSecond.class.getName())
                && context.getBeanFactory().containsBeanDefinition(TestConditionalFirst.class.getName())) {
            return true;
        }
        return false;
    }
}
@Conditional(SelfConditional.class)
@Component
public class SelfConditionBean implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("这是自定义条件配置bean   SelfConditionBean");
    }
}

本来想着应该可以顺利输出打印语句的,但是一直没有,然后debug的时候看context对象的属性,看见beanfactory里面的beanDefinitionMap里面,只有spring内部的几个beanDefinition,就好奇@ConditionalOnBean 注解是怎么实现的,进去之后发现其实是SpringBootCondition(也是实现了Condition接口)抽象类的实现类OnBeanCondition兜兜转转用了一大堆逻辑去实现的,并不是简单的直接用match方法传入的参数对象来实现的:

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
MergedAnnotations annotations = metadata.getAnnotations();
if (annotations.isPresent(ConditionalOnBean.class)) {
Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
  MatchResult matchResult = getMatchingBeans(context, spec);
  if (!matchResult.isAllMatched()) {
  String reason = createOnBeanNoMatchReason(matchResult);
  return ConditionOutcome.noMatch(spec.message().because(reason));
  }
  matchMessage = spec.message(matchMessage)
  .found("bean", "beans")
  .items(Style.QUOTE, matchResult.getNamesOfAllMatches());
  }

SPI机制

JDK原生的SPI

概念
>
在Java中,SPI(Service Provider Interface)的概念可以通俗地理解为一种服务发现与加载的机制。它允许Java应用程序在运行时动态地查找并加载服务实现,而无需在编译时静态地绑定到特定的实现上。这种方式大大增强了Java应用程序的灵活性和可扩展性。
>
具体来说,SPI的工作原理可以归纳为以下几点:
>
**接口定义**:首先,定义一个服务接口(Service Interface),这个接口定义了服务提供者(Service Provider)必须实现的方法。服务接口相当于一个“契约”,规定了服务提供者的行为。
>
**实现服务**:第三方开发者或不同的服务提供者根据这个接口实现具体的服务。这些实现类可以位于不同的jar包中,只要它们遵循相同的接口规范。
>
**注册服务**:服务提供者需要在自己的jar包中,通过特定的方式注册自己的服务实现。在Java SPI中,这通常是通过在jar包的META-INF/services目录下创建一个以接口全限定名命名的文件,并在该文件中列出所有实现类的全限定名来实现的。
>
**加载服务**:当Java应用程序需要使用某个服务时,它可以通过SPI机制动态地查找并加载服务实现。这通常是通过java.util.ServiceLoader类来完成的,该类能够读取META-INF/services目录下的配置文件,并加载文件中指定的服务实现类。
>
**使用服务**:一旦服务实现被加载,Java应用程序就可以像使用普通类一样使用这些服务了。由于SPI机制的存在,应用程序无需关心具体是哪个服务提供者提供的实现,只需通过接口调用即可。
>
SPI的好处在于它实现了接口与实现类的解耦,使得Java应用程序更加灵活和可扩展。开发者可以轻松地替换服务实现,而无需修改应用程序的代码。同时,SPI也为插件化开发提供了便利,开发者可以开发符合SPI规范的插件,并在运行时动态地加载和使用这些插件。
>
总的来说,Java中的SPI是一种非常有用的服务发现与加载机制,它极大地增强了Java应用程序的灵活性和可扩展性。通过SPI,开发者可以更加轻松地构建模块化、插件化的应用程序架构。
我理解的SPI:
和在代码中实现类直接实现接口不同,SPI不是根据实现关系来找实现类,而是根据其他的方式比如 固定文件中读取类的全路径去找到实现类,只要在固定文件中添加自定义实现类,就可以灵活的去替换原有的功能。
简单测试
public interface TestJDKSpi {
void spi();
}
public class TestJDKSpiOne implements TestJDKSpi{
@Override
public void spi() {
System.out.println("我是TestJDKSpiOne");
}
}
@Component
public class TestJDKSpiTwo implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
ServiceLoader<TestJDKSpi> sl = ServiceLoader.load(TestJDKSpi.class);
  sl.forEach(TestJDKSpi::spi);
  }
  }

在resource目录下新建META-INF目录,在这个目录下再新建services目录,在services目录下新建com.example.demo.practise.testJavaSPIcom.example.demo.practise.testJavaSPI.TestJDKSpi文件(也就是接口的全限定名),在这个文件的第一行加上 com.example.demo.practise.testJavaSPI.TestJDKSpiOne;

启动程序,控制台会打印我是TestJDKSpiOne,说明调用成功。

JDP的SPI机制需要遵循以下规范:

所有定义的SPI文件都必须放在项目的META-INF/services目录下,且文件名必须命名为接口或抽象类的全限定名,文件内容为接口或抽象类的具体实现类的全限定名;如果出现多个具体实现类,则每行声明一个实现类的全限定名,多个类之间没有分隔符。

Spring中的SPI

spring中实现的方式也类似,只不过文件不一样,文件内容不一样,使用的的META-INF目录下的spring.factories文件,格式如下:
# ApplicationContext Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.autoconfigure.integration.IntegrationPropertiesEnvironmentPostProcessor
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer,\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jooq.NoDslContextBeanFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.MissingR2dbcPoolDependencyFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.MultipleConnectionPoolConfigurationsFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.NoConnectionFactoryBeanFailureAnalyzer,\
org.springframework.boot.autoconfigure.ssl.BundleContentNotWatchableFailureAnalyzer
# Template Availability Providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
# DataSource Initializer Detectors
org.springframework.boot.sql.init.dependency.DatabaseInitializerDetector=\
org.springframework.boot.autoconfigure.flyway.FlywayMigrationInitializerDatabaseInitializerDetector
# Depends on Database Initialization Detectors
org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\
org.springframework.boot.autoconfigure.batch.JobRepositoryDependsOnDatabaseInitializationDetector,\
org.springframework.boot.autoconfigure.quartz.SchedulerDependsOnDatabaseInitializationDetector,\
org.springframework.boot.autoconfigure.session.JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector

底层使用SpringFactoriesLoader.forDefaultResourceLocation(getClassLoader()).load来读取文件内容,SpringFactoriesLoader.loadFactoryNames方法在spring6.0被标注为弃用了。

SpringBoot的装配机制

只要在启动类上加上@SpringBootApplication注解,就会触发组件的自动装配,@SpringBootApplication注解中主要包括三个注解,自动装配(@Enable@@EnableAutoConfiguration )和组件扫描(@ComponentScan),以及自动配置(@SpringBootConfiguration)

@ComponentScan注解

这个注解是放在启动类上的目的就是扫描启动类所在包及其子包下的所有组件,这也解释了为什么Spring Boot的启动类要放到所有类的外在包的最外层。

@SpringBootConfiguration注解

这个并不是一个特殊的注解,他本身仅组合了一个@Configuration注解

@EnableAutoConfiguration

这个是自动装配的核心,标注了@EnableAutoConfiguration注解之后,会启用Spring应用上下文的(即ApplicationContext)的自动配置,并且SpringBoot会尝试猜测和配置当前项目中可能需要的bean。

被@EnableAutoConfiguration注解标注的类其所在的包有特殊含义,通常被称为默认值,这个默认值体现在组件扫描,JDP实体扫描,Mapper接口扫描等。SpringBoot默认扫描被@EnableAutoConfiguration标注的类所在包及其子包的所有组件。

@EnableAutoConfiguration注解

@EnableAutoConfiguration注解中包含了两个主要注解:
- @AutoConfigurationPackage
- @Import(AutoConfigurationImportSelector.class)

@AutoConfigurationPackage注解

```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class[] basePackageClasses() default {};

}

可以看到 在AutoConfigurationPackage注解中再次使用了@Import注解, 查看AutoConfigurationPackages.Registrar.class
```java
/**
	 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
	 * configuration.
	 */
	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}
		@Override
		public Set determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}
	}

这个静态内部类的注解翻译之后的意思是:

用以存储导入配置中的基础包。

registerBeanDefinitions方法

这里先关注registerBeanDefinitions方法,进行debug之后查看metadata方法参数的值就是和启动类有关的。

PackageImports(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
// 先获取到	basePackages和basePackageClasses 配置的值
List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
  for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
    packageNames.add(basePackageClass.getPackage().getName());
    }
    // 如果没有配置,就直接取传过来的启动类的calssName
    if (packageNames.isEmpty()) {
    packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
    }
    this.packageNames = Collections.unmodifiableList(packageNames);
    }
//  在看了new PackageImports(metadata)中构造函数中,进行debug之后可以知道,
// 这里的packageNames其实就是启动类的路径(因为我没有配置 basePackages,basePackageClasses属性)
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
//  先判断当前BeanDefinitionRegistry中是否有AutoConfigurationPackages
//  没有 就先new 一个RootBeanDefinition对象,然后注册到beanDefinition注册表中
if (registry.containsBeanDefinition(BEAN)) {
addBasePackages(registry.getBeanDefinition(BEAN), packageNames);
}
else {
RootBeanDefinition beanDefinition = new RootBeanDefinition(BasePackages.class);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
//  addBasePackages方法会将 启动类路径赋值给beanDefinition,然后再注册到IOC容器中
addBasePackages(beanDefinition, packageNames);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}

在整合其他第三方框架的时候,比如mybatis的时候就会获取 AutoConfigurationPackages对象的实例,获取其中的根包类路径。

determineImports方法
在determineImports方法中,和上面一样也是先调用了new PackageImports(metadata)的构造方法,也就是存储了启动类路径的信息,不再赘述。

@Import(AutoConfigurationImportSelector.class)

在AutoConfigurationImportSelector类中进行debug,会发现会走process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector)方法
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取 EnableAutoConfiguration注解的属性(exclude(),excludeName())
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports  读取配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  // 使用 LinkedHashSet 去重
  configurations = removeDuplicates(configurations);
  //  解析  exclude(),excludeName() 属性的值
  Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // 检查 exclude(),excludeName() 配置的排除类是否存在 (ClassUtils.isPresent方法)
    checkExcludedClasses(configurations, exclusions);
    // 去掉  exclude(),excludeName() 指定的配置类
    configurations.removeAll(exclusions);
    // 先到spring.factories文件中获取到AutoConfigurationImportFilter的配置类// 
    //  筛选出符合条件的自动配置类(具体如何过滤的没看懂暂时)
    configurations = getConfigurationClassFilter().filter(configurations);
    //   广播AutoConfigurationImportEvent 事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
    }

经过上述的代码之后,就会收集所有需要加载的自动配置类,并在最后返回这些自动配置类的全限定名,存入到AutoConfigurationGroup类中的Map<String, AnnotationMetadata>属缓存中,后续IOC容器会提取出这些这些自动配置类并且解析,完成自动装配的解析。

小结

+ @SpringBootApplication注解包含@CompentScan注解,可以默认扫描当前启动类所在包的下面的所有组件。 + @EnableAutoConfiguration注解中包含@AutoConfigurationPackage注解,@AutoConfigurationPackage注解的作用是记录启动类的位置,以便第三方框架整合使用。 + @EnableAutoConfiguration注解中的引入的AutoConfigurationImportSelector类利用SpringFramework的SPI机制(META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件)加载所有自动配置类。

自动装配实际案例:WebMvc场景下的自动装配

在引入spring-boot-starter-web 依赖之后,SpringBoot会进行WebMvc环境的自动装配,处理的核心是一个叫做 **WebMvcAutoConfiguration** 的类:
// 在这个类之后加载
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
//  当前环境需要是 Servlet
@ConditionalOnWebApplication(type = Type.SERVLET)
// 需要存在 这三个类                            
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
//  不存在 WebMvcConfigurationSupport 类的情况下才会使用本类的配置     
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration

满足要使WebMvcAutoConfiguration 生效的配置类省考,需要满足以下几个条件:

- 当前环境必须要是WebMvc(也就是Servlet环境)
- 当前类路径下需要含有Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class 这三个类(这三个类是构造WebMvc环境的重要成员,缺一不可)
- 只有项目中没有注册WebMvcConfigurationSupport的类或者子类,才会对**WebMvcAutoConfiguration** 类进行自动装配。

对于最后一个条件,**WebMvcAutoConfiguration **之所以需要容器进行检查是否包含WebMvcConfigurationSupport的类或者子类,是为了避免和@EnableWebMvc注解冲突。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
在源码中可以看见,@EnableWebMvc注解导入了DelegatingWebMvcConfiguration类,
DelegatingWebMvcConfiguration类又继承了WebMvcConfigurationSupport类,就和上面的必要条件冲突。

WebMvcAutoConfiguration 类可以看作是约定的配置,如果没有进行手动配置@EnableWebMvc注解,那就采取默认的配置,也就是 约定大于配置。

Web容器装配

```java @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @AutoConfiguration(after = ServletWebServerFactoryAutoConfiguration.class) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass(DispatcherServlet.class) public class DispatcherServletAutoConfiguration { ```
@AutoConfiguration(after = SslAutoConfiguration.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
  ObjectProvider<TomcatContextCustomizer> contextCustomizers,
    ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
      TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
      factory.getTomcatConnectorCustomizers().addAll(connectorCustomizers.orderedStream().toList());
      factory.getTomcatContextCustomizers().addAll(contextCustomizers.orderedStream().toList());
      factory.getTomcatProtocolHandlerCustomizers().addAll(protocolHandlerCustomizers.orderedStream().toList());
      return factory;
      }
      }

DispatcherServlet的装配

基于SpringBoot的项目,都会使用Servlet3.0及以上的规范,在Servlet3.0规范不再使用web.xml,而是使用注解的方式配合Servlet容器完成原生组件的扫描(servlet,filter,listener),Spring Boot中并不默认支持扫描servlet原生组件,所以提供了另外两种注册方式:
- Servlet原生组件扫描 @ServletComponentScan:
    * 在主启动类中加上ServletComponentScan注解
    *
    * 在主启动类所有的包下需要注册为servlet组件的类上加上@WebServlet,@WebFilter,@WebListener注解
- 借助辅助注册器RegistrationBean
    * 这种方式适用于引入的第三方包中存在Servlet原生组件
public class ThirdServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
public class ThirdServletRegistrationBean extends ServletRegistrationBean<ThirdServlet> {
  public ThirdServletRegistrationBean(ThirdServlet servlet, String... urlMappings) {
  super(servlet, urlMappings);
  }
  }
@Configuration
public class ThirdServletConfiguration {
@Bean
public ThirdServletRegistrationBean thirdServletRegistrationBean()
{
return new ThirdServletRegistrationBean(new ThirdServlet(),"/third");
}
}

上述代码就是使用RegistrationBean的案例。

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@AutoConfiguration(after = ServletWebServerFactoryAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
public class DispatcherServletAutoConfiguration {
/**
* The bean name for a DispatcherServlet that will be mapped to the root URL "/".
*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
/**
* The bean name for a ServletRegistrationBean for the DispatcherServlet "/".
*/
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
configureThrowExceptionIfNoHandlerFound(webMvcProperties, dispatcherServlet);
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}

装配DispatcherServlet的大概过程:

        1. **WebMvcAutoConfiguration自动配置类**
        2. **@AutoConfiguration(after = { DispatcherServletAutoConfiguration**
        3. **@Bean和@AutoConfiguration注解配合**

SpringWebMvc的装配

在嵌入式Web容器和DispatcherServlet都装配完成之后,最后是装配WebMvc。

在WebMvcAutoConfiguration中,有两个主要的类,WebMvcAutoConfigurationAdapter 和EnableWebMvcConfiguration。

WebMvcAutoConfigurationAdapter

- 配置HttpMessageConverter * 重写configureMessageConverters方法是为了配置默认的HttpMessageConverter,HttpMessageConverter是一个消息转换器,她的作用对象是@RequestBody和@ResponseBody注解标注的Controller方法,分别完成请求体到参数对象的转换以及响应对象到响应体的转换,默认情况下SpringBoot整合WebMvc,底层会自动依赖Jackson作为JSON支持,所以这里会配置一个MappingJackson2HttpMessageConverter作为消息体转换器的实现。 - 异步支持 * configureAsyncSupport方法的作用就是配置异步请求的支持,在**WebMvcAutoConfiguration** 类中配置的TaskExecutionAutoConfiguration类会创建一个异步线程池。 - 注册视图解析器 * InternalResourceViewResolver + 通过路径前后缀拼接的方式解析逻辑视图名称,这个通常用于处理JSP页面,所以不必理会 * ContentNegotiatingViewResolver + 是顶层级的视图解析器,负责将视图解析的工作分配给不同的带俩ViewResolver来实现,核心工作就是负责转发。 * RequestContextHolder + RequestContextHolder用来全局获取HttpServletRequest和HttpServletResponse,而支撑RequestContextHolder完成这样的功能是RequestContextFilter。
@Bean
@ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class })
@ConditionalOnMissingFilterBean(RequestContextFilter.class)
public static RequestContextFilter requestContextFilter() {
return new OrderedRequestContextFilter();
}
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
ServletRequestAttributes attributes = new ServletRequestAttributes(request, response);
initContextHolders(request, attributes);
try {
filterChain.doFilter(request, response);
}
finally {
resetContextHolders();
if (logger.isTraceEnabled()) {
logger.trace("Cleared thread-bound request context: " + request);
}
attributes.requestCompleted();
}
}
private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) {
LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
if (logger.isTraceEnabled()) {
logger.trace("Bound request context to thread: " + request);
}
}

EnableWebMvcConfiguration

- 注册HandlerMapping * HandlerMapping处理器映射器的作用就是根据请求的URI去匹配对应处理的Handler,目前主流的WebMvc使用的都是@RequestMapping注解定义的Handler请求处理器,所以这里默认直接注册了一个RequestMappingHandlerAdapter - 注册HandlerAdapter * 处理器适配器HandlerAdapter会拿到HandlerMapping匹配成功的Handler,并用合适的方式执行Handler逻辑,使用@RequestMapping注解定义的Handler,其底层负责执行的适配器就是RequestMappingHandlerAdapter。