Spring boot 源码分析-Conditional 曾经终败给现在 2022-03-12 11:44 163阅读 0赞 在`Spring Boot`中大量的应用到`@Conditional`条件注解,Condition接口是spring4.0增加的条件判断接口,用于判断条件满足情况,主要**集中在Bean的定义和使用**。当前文章基于spring boot 1.5.4.RELEASE,spring-core 4.3.9.RELEASE。 **目录** 1. 用法演示 1.1 小结 1.2 spring Condtion拓展集 2. 源码 2.1 ConditionContext 2.2 AnnotatedBeanDefinitionReader 2.3 ClassPathScanningCandidateComponentProvider 2.4 ConfigurationClassPostProcessor 3. SpringBootCondition 总结 -------------------- //源于org.springframework.context.annotation; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { /** * All {@link Condition}s that must {@linkplain Condition#matches match} * in order for the component to be registered. */ Class<? extends Condition>[] value(); } public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); } ## 1. 用法演示 ## 在不同系统环境中创建监控服务MonitorService,还算比较简单(spring帮我们做了很多事情)。 public class LinuxCondition implements Condition{ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return context.getEnvironment().getProperty("os.name").contains("Linux"); } } public class WindowsCondition implements Condition{ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return context.getEnvironment().getProperty("os.name").contains("Windows"); } } public interface MonitorService { MonitorData ps(); } public class LinuxService implements MonitorService { public MonitorData ps(){ return ...; } } public class WindowsService implements MonitorService { public String ps(){ return ...; } } @Configuration public class MyConfiguration { @Bean(name = "monitor") @Conditional(WindowsCondition.class) public MonitorService windowsService() { return new WindowsService(); } @Bean(name = "monitor") @Conditional(LinuxCondition.class) public MonitorService linuxEmailerService() { return new LinuxService(); } } 当然如果你觉得再定义MyConfiguration太麻烦了,也可以用下面的写法,原理都是一样的。 @Component @Conditional(LinuxCondition.class) public class LinuxService implements MonitorService { public MonitorData ps(){ return ...; } } ### 1.1 小结 ### 通过`@Conditional`注解配合`Condition`接口,来决定给一个bean是否创建和注册到Spring容器中。这样做的好处是: * 当有多个同名bean时选择的问题 * 解决某些bean的创建时依赖某些条件的场景 除此之前,还有其他的场景吗,继续向下看吧(ConfigurationClassPostProcessor)! ### 1.2 spring Condtion拓展集 ### <table> <tbody> <tr> <td style="width:247px;"><span style="color:#f33b45;"><code>@ConditionalOnMissingBean</code></span></td> <td style="width:602px;">当给定的类型、类名、注解、昵称在beanFactory中不存在时返回true.各类型间是or的关系</td> </tr> <tr> <td style="width:247px;"><span style="color:#f33b45;"><code>@ConditionalOnBean</code></span></td> <td style="width:602px;">与上面相反,要求bean存在</td> </tr> <tr> <td style="width:247px;"><span style="color:#f33b45;"><code>@ConditionalOnMissingClass</code></span></td> <td style="width:602px;">当给定的类名在类路径上不存在时返回true,各类型间是and的关系</td> </tr> <tr> <td style="width:247px;"><span style="color:#f33b45;"><code>@ConditionalOnClass</code></span></td> <td style="width:602px;">与上面相反,要求类存在</td> </tr> <tr> <td style="width:247px;"><span style="color:#f33b45;"><code>@ConditionalOnProperty</code></span></td> <td style="width:602px;">要求配置属性匹配条件</td> </tr> <tr> <td style="width:247px;"><span style="color:#f33b45;"><code>@ConditionalOnJava</code></span></td> <td style="width:602px;">运行时的java版本号是否包含给定的版本号.如果包含,返回匹配,否则,返回不匹配</td> </tr> <tr> <td style="width:247px;"><span style="color:#f33b45;"><code>@ConditionalOnExpression</code></span></td> <td style="width:602px;">spel表达式执行为true</td> </tr> <tr> <td style="width:247px;"><span style="color:#f33b45;"><code>@ConditionalOnCloudPlatform</code></span></td> <td style="width:602px;">当所配置的CloudPlatform为激活时返回true</td> </tr> <tr> <td style="width:247px;"><span style="color:#f33b45;"><code>@ConditionalOnSingleCandidate</code></span></td> <td style="width:602px;">当给定类型的bean存在并且指定为Primary的给定类型存在时,返回true</td> </tr> <tr> <td style="width:247px;"><span style="color:#f33b45;"><code>@ConditionalOnJndi</code></span></td> <td style="width:602px;">给定的jndi的Location 必须存在一个.否则,返回不匹配</td> </tr> <tr> <td style="width:247px;"><span style="color:#f33b45;"><code>@ConditionalOnNotWebApplication</code></span></td> <td style="width:602px;">web环境不存在时</td> </tr> <tr> <td><span style="color:#f33b45;"><code>@ConditionalOnWebApplication</code></span></td> <td>web环境存在时</td> </tr> <tr> <td><span style="color:#f33b45;"><code>@ConditionalOnResource</code></span></td> <td>要求制定的资源存在</td> </tr> </tbody> </table> **这里提问一个问题,如何像spring boot一样进行拓展而非采用上面示例呢?** ## 2. 源码 ## ### 2.1 ConditionContext ### 在上面我们看到过ConditionContext,它可以用来获取很多系统相关的信息,来丰富条件判断: // 源于org.springframework.context.annotation; public interface ConditionContext { BeanDefinitionRegistry getRegistry(); //Bean定义 ConfigurableListableBeanFactory getBeanFactory(); //Bean容器 Environment getEnvironment(); //所有的配置信息 ResourceLoader getResourceLoader();//资源信息 ClassLoader getClassLoader(); } // 实现不展开讲 class ConditionEvaluator { private static class ConditionContextImpl implements ConditionContext { } } ### 2.2 AnnotatedBeanDefinitionReader ### AnnotatedBeanDefinitionReader管理ConditionContext,你可能不熟悉,不急!AnnotationConfigApplicationContext (这个总知道吧)是spring 3.0以后引入的类,用于处理spring注解。 public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry { public AnnotationConfigApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this);//就是它 this.scanner = new ClassPathBeanDefinitionScanner(this);//还有它 } } public class AnnotatedBeanDefinitionReader { private ConditionEvaluator conditionEvaluator; public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); Assert.notNull(environment, "Environment must not be null"); this.registry = registry; //实例化ConditionContext this.conditionEvaluator = new ConditionEvaluator(registry, environment, null); AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } } //不知道AnnotationConfigApplicationContext 是啥的看这里吧 public static void main(String[] args) { ApplicationContext context = newAnnotationConfigApplicationContext(Application.class); MessagePrinter printer =context.getBean(MessagePrinter.class); printer.printMessage(); } 到这里Condition来自哪里的问题基本上已经解决了,下一步就是看,它是如何调用的? **AnnotatedBeanDefinitionReader.registerBean** 在注册bean的时候会拿Condition判断是否这个是一个满足条件应该注册的bean,看代码 /* * Register a bean from the given bean class, deriving its metadata from class-declared annotations. */ public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass); //注解判断 true就不注册这个class了 if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { return; } ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); abd.setScope(scopeMetadata.getScopeName()); String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry)); AnnotationConfigUtils.processCommonDefinitionAnnotations(abd); if (qualifiers != null) { for (Class<? extends Annotation> qualifier : qualifiers) { if (Primary.class == qualifier) { abd.setPrimary(true); } else if (Lazy.class == qualifier) { abd.setLazyInit(true); } else { abd.addQualifier(new AutowireCandidateQualifier(qualifier)); } } } BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName); //创建ScopedProxyMode代理 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry); } ### 2.3 ClassPathScanningCandidateComponentProvider ### AnnotationConfigApplicationContext 的scan(ClassPathBeanDefinitionScanner), //从类路径中扫描component public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>(); for (String basePackage : basePackages) { //看这里,扫描 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; } } public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware { private boolean isConditionMatch(MetadataReader metadataReader) { if (this.conditionEvaluator == null) { //实例化ConditionContext this.conditionEvaluator = new ConditionEvaluator(getRegistry(), getEnvironment(), getResourceLoader()); } //注解校验 return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata()); } public Set<BeanDefinition> findCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>(); try { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath); boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); for (Resource resource : resources) { if (traceEnabled) { logger.trace("Scanning " + resource); } if (resource.isReadable()) { try { MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); //满足是一个组件的所有要求 if (isCandidateComponent(metadataReader)) { //ScannedGenericBeanDefinition其实就是一个带有注解信息,没有任何构造函数参数等等的GenericBeanDefinition ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); //判断是否给定的beandefinition是一个具体的类,不是接口或者抽象类 if (isCandidateComponent(sbd)) { if (debugEnabled) { logger.debug("Identified candidate component class: " + resource); } candidates.add(sbd); } else { if (debugEnabled) { logger.debug("Ignored because not a concrete top-level class: " + resource); } } } else { if (traceEnabled) { logger.trace("Ignored because not matching any filter: " + resource); } } } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to read candidate component class: " + resource, ex); } } else { if (traceEnabled) { logger.trace("Ignored because not readable: " + resource); } } } } catch (IOException ex) { throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); } return candidates; } protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return false; } } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return isConditionMatch(metadataReader); } } return false; } } ### 2.4 ConfigurationClassPostProcessor ### 处理上面的用法外,Spring按照Configuration类的方式进行配置的时候,会加载解析Configuration组件类,加载的时候会调用Condition的子类ConfigurationCondition去判断是否需要解析Configuration或者是否注册Bean(ConfigurationClassPostProcessor)。 public interface ConfigurationCondition extends Condition { ConfigurationPhase getConfigurationPhase(); enum ConfigurationPhase { PARSE_CONFIGURATION, REGISTER_BEAN } } 首先还是从入口开始,ConfigurationClassPostProcessor的postProcessBeanFactory,首先调用ConfigurationClassParser 的parse方法,再调用ConfigurationClassBeanDefinitionReader的loadBeanDefinitions方法完成依赖bean的判决及注入。 public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware { //就是它 private ConfigurationClassBeanDefinitionReader reader; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { if (!this.registriesPostProcessed.contains(factoryId)) { // BeanDefinitionRegistryPostProcessor hook apparently not supported... // Simply call processConfigurationClasses lazily at this point then. processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory); } } public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { // Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); parser.parse(candidates); parser.validate(); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } this.reader.loadBeanDefinitions(configClasses); } } class ConfigurationClassBeanDefinitionReader { private final ConditionEvaluator conditionEvaluator; /** * Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used * to populate the given {@link BeanDefinitionRegistry}. */ ConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry, SourceExtractor sourceExtractor, ResourceLoader resourceLoader, Environment environment, BeanNameGenerator importBeanNameGenerator, ImportRegistry importRegistry) { this.registry = registry; this.sourceExtractor = sourceExtractor; this.resourceLoader = resourceLoader; this.environment = environment; this.importBeanNameGenerator = importBeanNameGenerator; this.importRegistry = importRegistry; this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader); } /** * Read {@code configurationModel}, registering bean definitions * with the registry based on its contents. */ public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) { } } class ConfigurationClassParser { private final ConditionEvaluator conditionEvaluator; public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader, BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) { this.metadataReaderFactory = metadataReaderFactory; this.problemReporter = problemReporter; this.environment = environment; this.resourceLoader = resourceLoader; this.registry = registry; this.componentScanParser = new ComponentScanAnnotationParser( environment, resourceLoader, componentScanBeanNameGenerator, registry); this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader); } public void parse(Set<BeanDefinitionHolder> configCandidates) { this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>(); for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } } processDeferredImportSelectors(); } protected final void parse(Class<?> clazz, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(clazz, beanName)); } protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } } } ## 3. SpringBootCondition ## spring boot提供了SpringBootCondition(Spring Boot项目实现Condition的基础,它提供合理的日志记录以帮助用户诊断加载的类)注解来实现更丰富的内容。使用方式也特别简单,完全可以参考spring Condtion拓展集中的@ConditionalOnJava。 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class)//此次 public @interface ConditionalOnClass { Class<?>[] value() default {}; String[] name() default {}; } @Order(Ordered.HIGHEST_PRECEDENCE) class OnClassCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware { } **这时候你可能会问SpringBootCondition和ConfigurationCondition有什么区别?** ConfigurationCondition为spring框架特有的规约接口(与@Configuration一起使用时提供更细粒度控制的条件),SpringBootCondition是一个实现类,说实话没可比性,拓展的时候建议使用它。 ## 总结 ## spring中使用Conditional的用法及实现原理支持两种,一种是注册bean的时候拿Condition判断,另一种是需要解析Configuration的时候拿Condition判断。![76685476][] [76685476]: /images/20220312/011ac464efa24c4da08523310bc4a45d.png
还没有评论,来说两句吧...