前言
自动装配是SpringBoot的核心,从Spring的起源来说,一开始是通过XML文件对Bean进行装载,后来使用JavaConfig的方式实现无配置化的解决方案,但是本质问题并没有得到解决,直到SpringBoot的问世,帮开发者省略了繁琐的基础性的工作,是开发者更加关注于逻辑本身,SpringBoot将繁琐的基础性工作交给了Starter组件和自动装配去做。
自动装配的原理
自动装配的实现
当我们新建一个SpringBoot项目时,启动类上只有一个注解(如下),我们点进去这个注解
代码语言:javascript复制@SpringBootApplication
public class ElmSpringApplication {
public static void main(String[] args) {
SpringApplication.run(ElmSpringApplication.class, args);
}
}
当我们点进来后(如下),SpringBoot官方的开发人员也对这个注解进行了说明,简单来说@SpringBootApplication
就是@Configuration
, @EnableAutoConfiguration
和 @ComponentScan
的集合,下面我们分别说下这几个注解的作用:
@Target
:用来表示注解的作用范围@Retention
:指定它所修饰注解的保留策略,也就是该注解的生命周期@Documented
:标记注解,可以被例如javadoc此类的工具文档化@Inherited
:用于标注一个父类的注解是否可以被子类继承@SpringBootConfiguration
:继承自@Configuration
,允许在上下文中注册额外的 bean 或导入其他配置类@EnableAutoConfiguration
:开启SpringBoot的自动装载机制@Enable
:Spring3.1就已经支持@Enable
注解了,他的作用是把相关组件的Bean装配到IoC容器中,如果基于JavaConfig的形式完成Bean的装载就必须使用@Configuration
和@Bean
,而@Enable
本质上就是针对这两个注解的封装
@ComponentScan
:扫描包下的类中添加了@Component
(@Service
,@Controller
,@Repostory
,@RestController
)注解的类 ,并添加的到spring的容器中,可以自定义不扫描某些 bean
/**
* 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)
@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;
}
EnableAutoConfiguration
进入@EnableAutoConfiguration
注解(如下),可以看到除了我们在上面所说的注解外还多出了两个注解:@AutoConfigurationPackage
和@Import(AutoConfigurationImportSelector.class)
,这两个注解在这的作用是:
@AutoConfigurationPackage
:把使用了该注解所在包及子包下的所有组件扫描到Spring IoC容器中@Import(AutoConfigurationImportSelector.class)
:一般情况下,@Import
导入的应该是一个Configuration配置类,但在这里导入的是一个AutoConfigurationImportSelector
类,我会在下文中详细介绍此类,但知道的是,无论此类是什么,它一定会实现配置类的导入
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
AutoConfigurationImportSelector
AutoConfigurationImportSelector
类实现了ImportSelector
接口(关系如下),它只有一个selectImports
抽象方法,并且返回一个String数组,在这个数组中可以指定装配到IoC容器的类,当@Import
导入ImportSelector
的实现类后,就会把该方法返回的String数组里的类名的类都装载到IoC容器里。和@Configuration
不同的是,这种方法可以实现自动装配,也就是根据上下文来决定哪些类能够被IoC容器初始化。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
.............
.............
}
代码语言:javascript复制public interface DeferredImportSelector extends ImportSelector {
.............
.............
}
代码语言:javascript复制public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
简单使用ImportSelector的例子
创建两个类Codeone.class
和Codetwo.class
public class Codeone {
}
public class Codetwo {
}
创建ImportSelector
的实现类ImportSelectorImpl.class
public class ImportSelectorImpl implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{Codeone.class.getName(),Codetwo.class.getName()};
}
}
自定义注解EnableAutoImport
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(ImportSelectorImpl.class)
public @interface EnableAutoImport {
}
在启动类上添加注解并进行测试
代码语言:javascript复制@SpringBootApplication
@EnableAutoImport
public class ElmSpringApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(ElmSpringApplication.class, args);
Codeone codeone=run.getBean(Codeone.class);
}
}
也可以在ImportSelector
的实现类中把Codeone.class
和Codetwo.class
变为xxxConfiguration.class
,就是把具体的类变为Configuration
类,实现批量导入
自动装配的原理分析
根据上面的分析我们已经知道了是ImportSelector
通过导入Configuration
类从而完成Bean的装载过程,那么这些Configuration
是从哪里获取的呢?是怎样获取的呢?这是下面我们要分析的问题,上文中我们提到了AutoConfigurationImportSelector
类,当时我们只分析了selectImports
方法的作用,没分析具体的代码,而要想知道以上的两个问题的答案就要继续分析其代码,我把代码贴在下面:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
.......
.......
.......
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
.......
.......
.......
}
我们可以看到以上selectImports
方法中getAutoConfigurationEntry
方法,这个方法也是在该类中,下面我们找到getAutoConfigurationEntry
方法的具体实现
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
.......
.......
.......
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
//检测自动装配是否开启 可在yml中通过spring.boot.enableautoconfiguration=true开启,默认开启
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
//获取@EnableAutoConfiguration注解中exclude和excludeName
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//获得所有自动装配的配置类,后面会继续分析
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//去除重复的配置项,防止多个项目引入同样的配置类
configurations = removeDuplicates(configurations);
//根据@EnableAutoConfiguration注解中exclude等属性,把不需要自动装配的配置类移除
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//检查被排除类是否可实例化,是否被自动注册配置所使用,不符合条件则抛出异常
checkExcludedClasses(configurations, exclusions);
//从自动配置类集合中取出被排除的类
configurations.removeAll(exclusions);
//检查配置类的注解是否符合 spring.factories 文件中 AutoConfigurationImportFilter 指定的注解检查条件
configurations = getConfigurationClassFilter().filter(configurations);
//广播事件,所有监听 AutoConfigurationImportEvent 事件的都会做出相关的反应
fireAutoConfigurationImportEvents(configurations, exclusions);
//创建自动配置的对象
return new AutoConfigurationEntry(configurations, exclusions);
}
.......
.......
.......
}
以上源码都进行了注释,我们只看其中的核心,也就是getCandidateConfigurations
这个方法,他是获得配置类最核心的方法,下面我们看他的代码
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
.......
.......
.......
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)
{
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-
INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
"are using a custom packaging, make sure that file is correct.");
return configurations;
}
.......
.......
.......
}
通过上面的代码,我们看到代码中用了SpringFactoriesLoader
,它是Spring内部提供的一种约定俗成的加载方式,类似于JAVA中的SPI,简单来说,他会扫描classpath下的META-INF/spring.factories
文件,spring.factories
文件中的数据以key=value的形式存储,而SpringFactoriesLoader.loadFactoryNames
会根据key得到对应的value值,因此在这个场景中key对应为EnableAutoConfiguration
,value是多个配置类,也就是getCandidateConfigurations
的返回值,我们通过debug获取它的返回值,如下图所示:
打开RabbitAutoConfiguration
,代码如下
@AutoConfiguration
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import({ RabbitAnnotationDrivenConfiguration.class, RabbitStreamConfiguration.class })
public class RabbitAutoConfiguration {
....................
....................
....................
}
除了基本的@Configuration
注解,还有一个@ConditionalOnClass
注解,这个条件控制机制在这里的用途是,判断classpath下是否存在着RabbitTemplate
和Channel
这两个类,如果是,则把当前配置类注册到IoC容器中。另外,@EnableConfigurationProperties
是属性配置,也就是说我们可以按照约定在application.yml
中配置RabbitMQ的参数,而这些配置会加载到RabbitProperties中。
@Conditional条件装配
@Conditional
是Spring提供的一个核心注解,这个注解的作用是提供自动装配的条件约束,一般与@Configuration
和@Bean
配合使用
简单来说,Spring在解析@Configuration
配置类时,如果该配置类增加了@Conditional
注解,那么就会根据该注解配置的条件来决定是否要实现Bean的装配
@Conditional
的注解类声明代码如下,该注解可以接收一个Condition
类型数组
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
Condition
是一个函数式接口,提供了matchs
方法,它主要提供一个匹配规则,返回true则装载Bean反之不装载
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
基于@Conditional实现一个条件装配案例
自定义一个Condition,规则是如果当前是windows系统则返回true,反之则返回false
代码语言:javascript复制public class ConditionImpl implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String os=context.getEnvironment().getProperty("os.name");
if(os.contains("Windows")){
return true;
}
return false;
}
}
创建一个配置类,装载一个自定义类Codeone
,在Codeone
的Bean声明方法中增加@Conditional(ConditionImpl.class)
注解,如果ConditionImpl.class
中的matchs
方法返回true则Codeone装载到Spring IoC容器中,反之则不装载
@Configurable
public class ConditionConfig {
@Bean
@Conditional(ConditionImpl.class)
public Codeone codeone(){
return new Codeone();
}
}
运行测试,输出结果
代码语言:javascript复制@SpringBootTest
class ElmSpringApplicationTests {
@Test
void contextLoads() {
AnnotationConfigApplicationContext annotationConfigApplicationContext=new AnnotationConfigApplicationContext(ConditionConfig.class);
Codeone codeone=annotationConfigApplicationContext.getBean(Codeone.class);
System.out.println(codeone);
}
}
SpringBoot中的@Conditional
在SpringBoot中,针对@Conditional
做了扩展,提供了更简单的使用形式,扩展注解如下:
@ConditionOnBean
/@ConditionOnMissingBean
:容器中存在某个类或者不存在某个Bean时进行Bean的装载@ConditionOnClass
/@ConditionOnMissingClass
:classpath下存在指定类或者不存在指定类时进行Bean的装载@ConditionOnCloudPlatform
:只用运行在指定的云平台上才加载指定的Bean@ConditionOnExpression
:基于SpEI表达式的条件判断@ConditionOnJava
:只用运行指定版本的Java才会加载Bean@ConditionOnJndi
:只有指定的资源通过JNDI加载后才加载Bean@ConditionOnWebApplication
/@ConditionOnNotWebApplication
:如果是Web应用或者不是Web应用,才加载指定的Bean@ConditionOnProperty
:系统中指定的对应的属性是否有对应的值@ConditionOnResource
:classpath中是否存在要加载的Bean依赖的资源@ConditionOnSingleCandidate
:只有在确定了给定Bean类的单个候选项时才会加载Bean
spring-autoconfigure-metadata
它的作用和@Conditional
是一样的,只是将这些条件配置放进了配置文件里
org.springframework.boot.autoconfigure.AutoConfiguration=
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration=
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration=
org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.annotation.EnableRabbit
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration=
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration$MessagingTemplateConfiguration=
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration$MessagingTemplateConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.core.RabbitMessagingTemplate
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.ConditionalOnClass=com.rabbitmq.client.Channel,org.springframework.amqp.rabbit.core.RabbitTemplate
org.springframework.boot.autoconfigure.amqp.RabbitStreamConfiguration=
org.springframework.boot.autoconfigure.amqp.RabbitStreamConfiguration.ConditionalOnClass=org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration=
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration=
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration.ConditionalOnClass=org.aspectj.weaver.Advice
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration=
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration=
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration$DataSourceInitializerConfiguration=
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration$DataSourceInitializerConfiguration.ConditionalOnBean=javax.sql.DataSource
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration$DataSourceInitializerConfiguration.ConditionalOnClass=org.springframework.jdbc.datasource.init.DatabasePopulator
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration.ConditionalOnBean=org.springframework.batch.core.launch.JobLauncher
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.batch.core.launch.JobLauncher
org.springframework.boot.autoconfigure.batch.BatchConfigurerConfiguration=
这种形式也是约定大于配置的体现,通过这种配置实现条件过滤必须要遵循两个条件:
- 配置文件的路径和名称必须是
META-INFspring-autoconfigure-metadata.properties
- 配置文件中key的配置格式:自动配置类的类全路径名.条件=值
这种配置方式有效的降低了SpringBoot的启动时间,减少配置类的加载数量
温馨提示:本文最后更新于2023-03-14
,若文件或内容有错误或已失效,请在下方留言。