经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
spring-boot-2.0.3不一样系列之番外篇 - @Configuration、Condition与@Conditional
来源:cnblogs  作者:youzhibing2904  时间:2019/2/22 9:34:08  对本文有异议

前言

  开心一刻

   一名劫匪慌忙中窜上了一辆车的后座,上车后发现主驾和副驾的一男一女疑惑地回头看着他,他立即拔出枪威胁到:“赶快开车,甩掉后面的警车,否则老子一枪崩了你!”,于是副驾上的男人转过脸对那女的说:“大姐,别慌,听我口令把刚才的动作再练习一遍,挂一档,轻松离合,轻踩油门,走...走,哎 走...哎,哎,对,走走... 最后,三人都躺到了医院,劫匪的手上还戴上了铐子...

劫匪的内心

  路漫漫其修远兮,吾将上下而求索!

  github:https://github.com/youzhibing

  码云(gitee):https://gitee.com/youzhibing

前情回顾

  估摸着大家已经忘记了createApplicationContext的内容,本文不做过多的回顾,只是提醒大家:在AnnotationConfigServletWebServerApplicationContext的实例化过程中,实例化了AnnotatedBeanDefinitionReader,另外也将ConfigurationClassPostProcessor定义注册到了beanFactory中,如下图所示

  看着AnnotatedBeanDefinitionReader、ConfigurationClassPostProcessor是不是隐约感觉到了什么?

概念介绍与应用

  @Configuration

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Component
  5. public @interface Configuration {
  6. /**
  7. * Explicitly specify the name of the Spring bean definition associated
  8. * with this Configuration class. If left unspecified (the common case),
  9. * a bean name will be automatically generated.
  10. * <p>The custom name applies only if the Configuration class is picked up via
  11. * component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}.
  12. * If the Configuration class is registered as a traditional XML bean definition,
  13. * the name/id of the bean element will take precedence.
  14. * @return the suggested component name, if any (or empty String otherwise)
  15. * @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
  16. */
  17. @AliasFor(annotation = Component.class)
  18. String value() default "";
  19. }
View Code

    @Configuration能够修饰Class、interface和enum,用的最多的还是标注在类上,相当于把该类作为spring的xml配置文件中的<beans>,用于配置spring容器;@Configuration往往会结合@Bean来使用,@Bean等价于spring的xml配置文件中的<bean>,用于注册bean对象。@Configuration和@Bean组成了基于java类的配置,是spring的推荐配置方式。最简单的使用如下

  1. @Configuration
  2. public class MyConfiguration {
  3. @Bean
  4. public Cat mycat() {
  5. return new Cat();
  6. }
  7. }

    如上代码就会在spring容器中注册一个名叫mycat的Cat类型的Bean

  Condition

  1. @FunctionalInterface
  2. public interface Condition {
  3. /**
  4. * Determine if the condition matches.
  5. * @param context the condition context
  6. * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
  7. * or {@link org.springframework.core.type.MethodMetadata method} being checked
  8. * @return {@code true} if the condition matches and the component can be registered,
  9. * or {@code false} to veto the annotated component's registration
  10. */
  11. boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
  12. }
View Code

    Spring的条件化配置,当我们向spring注册bean时,可以对这个bean添加一定的自定义条件,当满足这个条件时注册这个bean,否则不注册。springboot中部分实现子类如下

    springboot更多实现请查看org.springframework.boot.autoconfigure.condition包。Condition一般配合@Conditional使用,更多信息往下看

  @Conditional

  1. @Target({ElementType.TYPE, ElementType.METHOD})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface Conditional {
  5. /**
  6. * All {@link Condition}s that must {@linkplain Condition#matches match}
  7. * in order for the component to be registered.
  8. */
  9. Class<? extends Condition>[] value();
  10. }
View Code

    Spring的条件注解,其value是一个Class<? extends Condition>[],只有数组中的全部Condition全部匹配成功时,被@Conditional修饰的组件才会被注册到Spring容器中。@Conditional只是一个标志,标示需要进行条件判断,而具体的判断规则则由具体的Condition来实现。

    在SpringBoot源码中很容易看到被@Conditional注解的组合注解,例如:@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnClass、@ConditionalOnMissingClass等,具体如下

    springboot还提供了AutoConfigureAfter、AutoConfigureBefore、AutoConfigureOrder,看名字基本知道其作用,具体细节需要大家自己去跟了。

  完整应用案例

    接口都能访问通,数据返回也都正确,非常完美

    完整工程代码:spring-boot-condition

    当我们把MyConfiguration中的myCat方法注释掉(ConditionWeb中的cat相关也注释掉),再启动应用的时候,应用报错启动不起来,提示如下信息:

  1. Description:
  2. Field dog in com.lee.condition.web.ConditionWeb required a bean of type 'com.lee.condition.model.Dog' that could not be found.
  3. - Bean method 'myDog' in 'MyConfiguration' not loaded because @ConditionalOnBean (types: com.lee.condition.model.Cat; SearchStrategy: all) did not find any beans of type com.lee.condition.model.Cat
  4. Action:
  5. Consider revisiting the conditions above or defining a bean of type 'com.lee.condition.model.Dog' in your configuration.
View Code

    ConditionWeb中需要Dog类型的bean,而Dog实例化又依赖Cat实例,而我们没有实例化Cat,所以应用启动报错,提示如上信息 

源码探究

  我们要探究什么了?不探究太细,就探究@Configuration修饰的配置类是何时解析的,@Conditional是何时生效、如何生效的

  @Configuration修饰的配置类是何时解析的

    ConfigurationClassPostProcessor是一个BeanFactoryPostProcessor(可以查看ConfigurationClassPostProcessor的类继承结构图),那么我们从AbstractApplicationContext的refresh方法调用的invokeBeanFactoryPostProcessors(beanFactory)方法开始

    来到了processConfigurationClass方法,其详细代码如下

  1. protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
  2. // ConfigurationClass是否应该被skip
  3. if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
  4. return;
  5. }
  6. ConfigurationClass existingClass = this.configurationClasses.get(configClass);
  7. if (existingClass != null) {
  8. if (configClass.isImported()) {
  9. if (existingClass.isImported()) {
  10. existingClass.mergeImportedBy(configClass);
  11. }
  12. // Otherwise ignore new imported config class; existing non-imported class overrides it.
  13. return;
  14. }
  15. else {
  16. // Explicit bean definition found, probably replacing an import.
  17. // Let's remove the old one and go with the new one.
  18. this.configurationClasses.remove(configClass);
  19. this.knownSuperclasses.values().removeIf(configClass::equals);
  20. }
  21. }
  22. // Recursively process the configuration class and its superclass hierarchy. 递归处理configuration class和它的父级类
  23. // 也就说会递归处理我们的应用入口类:ConditionApplication.class,以及ConditionApplication.class的父级类
  24. SourceClass sourceClass = asSourceClass(configClass);
  25. do {
  26. sourceClass = doProcessConfigurationClass(configClass, sourceClass);
  27. }
  28. while (sourceClass != null);
  29. // 将满足条件的ConfigurationClass都放入configurationClasses集合中
  30. // 后续会加载configurationClasses集合中所有的ConfigurationClass中配置的bean定义
  31. this.configurationClasses.put(configClass, configClass);
  32. }
View Code

    其中shouldSkip方法如下

  1. /**
  2. * Determine if an item should be skipped based on {@code @Conditional} annotations.
  3. * @param metadata the meta data
  4. * @param phase the phase of the call
  5. * @return if the item should be skipped
  6. */
  7. public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
  8. // 如果这个类没有注解修饰,或者没有被@Conditional注解(包括Conditional系列)所修饰,不会skip
  9. if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
  10. return false;
  11. }
  12. // 如果参数中沒有设置条件注解的生效阶段
  13. if (phase == null) {
  14. if (metadata instanceof AnnotationMetadata &&
  15. ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
  16. return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
  17. }
  18. return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
  19. }
  20. // 要解析的配置类的条件集合,即@Conditional的value
  21. List<Condition> conditions = new ArrayList<>();
  22. for (String[] conditionClasses : getConditionClasses(metadata)) {
  23. for (String conditionClass : conditionClasses) {
  24. Condition condition = getCondition(conditionClass, this.context.getClassLoader());
  25. conditions.add(condition);
  26. }
  27. }
  28. // 对条件进行排序
  29. AnnotationAwareOrderComparator.sort(conditions);
  30. // 遍历条件,逐个匹配
  31. for (Condition condition : conditions) {
  32. ConfigurationPhase requiredPhase = null;
  33. if (condition instanceof ConfigurationCondition) {
  34. requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
  35. }
  36. // 条件注解的生效阶段满足,一旦有条件匹配不成功,则返回true,skip此类
  37. if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
  38. return true;
  39. }
  40. }
  41. return false;
  42. }
View Code

    我们再回过头去看processConfigBeanDefinitions方法

  1. /**
  2. * Build and validate a configuration model based on the registry of
  3. * {@link Configuration} classes.
  4. * 验证@Configuration修饰的类,满足条件则构建成configuration model
  5. */
  6. public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
  7. List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
  8. String[] candidateNames = registry.getBeanDefinitionNames();
  9. for (String beanName : candidateNames) {
  10. BeanDefinition beanDef = registry.getBeanDefinition(beanName);
  11. if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
  12. ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
  13. if (logger.isDebugEnabled()) {
  14. logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
  15. }
  16. }
  17. else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
  18. configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
  19. }
  20. }
  21. // Return immediately if no @Configuration classes were found
  22. if (configCandidates.isEmpty()) {
  23. return;
  24. }
  25. // Sort by previously determined @Order value, if applicable
  26. configCandidates.sort((bd1, bd2) -> {
  27. int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
  28. int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
  29. return Integer.compare(i1, i2);
  30. });
  31. // Detect any custom bean name generation strategy supplied through the enclosing application context
  32. // 检测自定义的bean生成策略
  33. SingletonBeanRegistry sbr = null;
  34. if (registry instanceof SingletonBeanRegistry) {
  35. sbr = (SingletonBeanRegistry) registry;
  36. if (!this.localBeanNameGeneratorSet) {
  37. BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
  38. if (generator != null) {
  39. this.componentScanBeanNameGenerator = generator;
  40. this.importBeanNameGenerator = generator;
  41. }
  42. }
  43. }
  44. if (this.environment == null) {
  45. this.environment = new StandardEnvironment();
  46. }
  47. // Parse each @Configuration class
  48. // 解析每一个被@Configuration修饰的class
  49. ConfigurationClassParser parser = new ConfigurationClassParser(
  50. this.metadataReaderFactory, this.problemReporter, this.environment,
  51. this.resourceLoader, this.componentScanBeanNameGenerator, registry);
  52. Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
  53. Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
  54. do {
  55. parser.parse(candidates); // 解析过程中会将满足条件的@Configuration class存放到configurationClasses中
  56. parser.validate();
  57. // 满足条件的@Configuration class 都存放在了parser的configurationClasses中
  58. Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
  59. configClasses.removeAll(alreadyParsed);
  60. // Read the model and create bean definitions based on its content
  61. // 读取@Configuration class中的配置(各个@Bean),并创建对应的bean definition(后续创建bean实例会用到bean定义)
  62. if (this.reader == null) {
  63. this.reader = new ConfigurationClassBeanDefinitionReader(
  64. registry, this.sourceExtractor, this.resourceLoader, this.environment,
  65. this.importBeanNameGenerator, parser.getImportRegistry());
  66. }
  67. this.reader.loadBeanDefinitions(configClasses); // 加载全部@Configuration class中的配置
  68. alreadyParsed.addAll(configClasses);
  69. candidates.clear();
  70. if (registry.getBeanDefinitionCount() > candidateNames.length) {
  71. String[] newCandidateNames = registry.getBeanDefinitionNames();
  72. Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
  73. Set<String> alreadyParsedClasses = new HashSet<>();
  74. for (ConfigurationClass configurationClass : alreadyParsed) {
  75. alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
  76. }
  77. for (String candidateName : newCandidateNames) {
  78. if (!oldCandidateNames.contains(candidateName)) {
  79. BeanDefinition bd = registry.getBeanDefinition(candidateName);
  80. if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
  81. !alreadyParsedClasses.contains(bd.getBeanClassName())) {
  82. candidates.add(new BeanDefinitionHolder(bd, candidateName));
  83. }
  84. }
  85. }
  86. candidateNames = newCandidateNames;
  87. }
  88. }
  89. while (!candidates.isEmpty());
  90. // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
  91. if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
  92. sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
  93. }
  94. if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
  95. // Clear cache in externally provided MetadataReaderFactory; this is a no-op
  96. // for a shared cache since it'll be cleared by the ApplicationContext.
  97. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
  98. }
  99. }
View Code

  @Conditional是何时生效、如何生效的

    这个问题再上面已经全部得到体现,Spring不会无脑的加载所有的@Configuration class,只会加载满足条件的@Configuration class,而@Conditional就是条件标志,至于条件匹配规则这有Condition提供;shouldSkip方法中用到Conditional和Condition,完成条件的匹配处理。

总结

  1、@Configuration和@Bean组成了基于java类的配置,与xml中的<Beans>、<Bean>功能一致,Spring推荐java类的配置;

  2、Condition与@Conditional实现了条件配置,只有满足了条件的@Configuration class和@Bean才会被注册到Spring容器;

  3、Spring以我们的应用启动类为基础来递归扫描配置类,包括我们应用中的配置类、Spring自己的以及第三方的配置类(springboot集成的各种配置类(spring-boot-autoconfigure-xxx.RELEASE.jar下的spring.factories文件中的Auto Configure),还有pageHelper的自动配置,等等);前提是需要开启自动配置(@EnableAutoConfiguration)。

参考

  SpringBoot源码分析之条件注解的底层实现

  Spring 工具类 ConfigurationClassParser 分析得到

原文链接:http://www.cnblogs.com/youzhibing/p/9692091.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号