自动装配
自动装配本身是一个新的概念,相对于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
浙公网安备 33010602011771号