0%

SpringBoot自动装配原理

@ConditionalOn…注解

参考文章 https://zhuanlan.zhihu.com/p/78251301

源码分析

在Spring中,如果想要满足一定条件才加载某个bean到IoC容器,只能使用@Conditional注解

1
2
3
4
5
6
7
8
public @interface Conditional {

/**
* All {@link Condition Conditions} that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}

可以看到@Conditional接收的参数是实现了Condition接口的类,所以只需要重写matches()方法即可

1
2
3
4
5
6
7
8
9
@FunctionalInterface
public interface Condition {

/**
* Determine if the condition matches
* ...
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

而在SpringBoot中已经为我们封装好了@ConditionalOn...接口,这些接口上都有@Conditional注解

比如@ConditionalOnClass上有@Conditional(OnClassCondition.class),下面是OnClassCondition的继承关系

.image-20220710103116452

1
2
3
4
5
6
...
@Target({ ElementType.TYPE, ElementType.METHOD })
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
...
}
  • ElementType.TYPE表明Spring 自动扫描的一切类@Component (@Configuration, @Service, @Repository, @Controller) 都可以通过添加相应的 @ConditionalOnClass 来判断是否加载
  • ElementType.METHOD表明@ConditionalOnClass还可以应用在具有@Bean的方法上,实现粒度更小的控制

使用方法

具体各注解如下:

  • @ConditionalOnProperty:在application.yml中是否有对应属性,如果无对应属性则默认不加载,因为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public @interface ConditionalOnProperty {
    ...
    boolean matchIfMissing() default false;
    }

    //使用方法
    @Component
    @ConditionOnProperty(value="mybean.enabled",havingValue="true",matchIfMissing=true)
    public class MyBean{
    ...
    }
  • @ConditionalOnExpression:使用SpEL表达式用于多个配置属性联合判断

    1
    @ConditionOnExpression("${mybean.enabled:true} and ${otherbean.enabled:true}")

    这里的:true意思是如果没有该属性,默认值为true

  • @ConditionalOnResource:要加载的Bean依赖于某个资源

    1
    @ConditionalOnResource(resources="/logback.xml")
  • @ConditionalOnBean:当容器里有指定 Bean 的条件下

  • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下

  • @ConditionalOnClass:当类路径下有指定类的条件下

  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下

使用多个@ConditionalOn...默认是的关系,如果要实现的关系,需要继承AnyNestedCondition

的关系也有NoneNestedCondition

自动装配原理★

参考文章:https://zhuanlan.zhihu.com/p/345895748

SpringBoot 定义了一套接口规范,规定:SpringBoot 在启动时会扫描外部 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器,一般在SpringBoot Starter中,并执行类中定义的各种操作

首先来看@SpringBootApplication注解

1
2
3
4
5
6
7
8
@SpringBootConfiguration //可以看成@Configuration,允许在上下文中注册额外的 bean 或导入其他配置类
@EnableAutoConfiguration
//扫描启动类下的包的@Component(@Controller, @Service), 并排除TypeExcludeFilter和AutoConfigurationExcludeFilter
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}

其中,@EnableAutoConfiguration是实现自动装配的核心注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@AutoConfigurationPackage //将main包下的所有组件注册到容器中
@Import({AutoConfigurationImportSelector.class}) //加载自动装配类...Autoconfiguration
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; //在isEnabled()中被使用

/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};

/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
*/
String[] excludeName() default {};
}

可以看到,自动装配的功能实际上是由AutoConfigurationImportSelector实现的

1
2
3
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
}

这里只需要关心DeferredImportSelector接口

1
2
3
public interface DeferredImportSelector **extends** ImportSelector {
...
}

.image-20220709214038323

selectImports方法用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中

AutoConfigurationImportSelector的实现方式如下

1
2
3
4
5
6
7
8
9
10
11
12
private static final String[] NO_IMPORTS = new String[0];

public String[] selectImports(AnnotationMetadata annotationMetadata) {
//1.判断自动装配开关是否打开
if(!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//2.获取所有需要装配的bean
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}

首先看一眼isEnabled方法

1
2
3
4
5
6
7
8
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
//这里获取的就是application.yml中配置的属性
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
//<T> T getProperty(String key, Class<T> targetType, T defaultValue);
}
return true;
}

然后重点关注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
28
/**
* Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
* of the importing {@link Configuration @Configuration} class.
* @param annotationMetadata the annotation metadata of the configuration class
* @return the auto-configurations that should be imported
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
//判断是否打开自动装配
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
//1.获取@EnableAutoConfiguration注解中的exclude和excludeName,见下方
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//2.读取META-INF/spring.factories,获取候选的自动配置类,见下方
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//去重,一般没有重复
configurations = removeDuplicates(configurations);
//需要排除的类,一般为空
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//3.**因为configuration太多,不可能全部加载,所以要根据@ConditionalOn...注解过滤**
configurations = getConfigurationClassFilter().filter(configurations);
//使用AutoConfigurationImportListener触发event
fireAutoConfigurationImportEvents(configurations, exclusions);
//封装AutoConfigurationEntry
return new AutoConfigurationEntry(configurations, exclusions);
}
  1. .image-20220709221222470

  2. .image-20220709221810691

    可以看到,这些全部都是以AutoConfiguration为后缀的类名,这些类中配置了要注入到IoC容器中的bean,例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
    * @author lihengming [89921218@qq.com]
    */
    @Configuration
    @ConditionalOnClass(DruidDataSource.class)
    @AutoConfigureBefore(DataSourceAutoConfiguration.class)
    @EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
    @Import({DruidSpringAopConfiguration.class,
    DruidStatViewServletConfiguration.class,
    DruidWebStatFilterConfiguration.class,
    DruidFilterConfiguration.class})
    public class DruidDataSourceAutoConfigure {

    private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);

    @Bean(initMethod = "init")
    @ConditionalOnMissingBean
    public DataSource dataSource() {
    LOGGER.info("Init DruidDataSource");
    return new DruidDataSourceWrapper();
    }
    }

大致流程过完之后,继续深入,查看Step2的getCandidateConfigurations()方法

1
2
3
4
5
6
7
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
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.");
return configurations;
}

显然,核心是SpringFactoriesLoader.loadFactoryNames()方法,参数中使用到了ClassLoader

1
2
3
4
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

可以继续深入,但先摆了

另外,Step3的过滤也可以深入,也先摆了