@ConditionalOn…注解 参考文章 https://zhuanlan.zhihu.com/p/78251301
源码分析 在Spring中,如果想要满足一定条件才加载某个bean到IoC容器,只能使用@Conditional注解
1 2 3 4 5 6 7 8 public @interface Conditional { Class<? extends Condition >[] value(); }
可以看到@Conditional接收的参数是实现了Condition接口的类,所以只需要重写matches()方法即可
1 2 3 4 5 6 7 8 9 @FunctionalInterface public interface Condition { boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata) ; }
而在SpringBoot中已经为我们封装好了@ConditionalOn...接口,这些接口上都有@Conditional注解
比如@ConditionalOnClass上有@Conditional(OnClassCondition.class),下面是OnClassCondition的继承关系
.
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 @EnableAutoConfiguration @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 @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration" ; Class<?>[] exclude() default {}; 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 { ... }
.
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) { if (!this .isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { 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) { return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true ); } 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 protected AutoConfigurationEntry getAutoConfigurationEntry (AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry (configurations, exclusions); }
.
.
可以看到,这些全部都是以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 @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的过滤也可以深入,也先摆了