前言
在Spring3.0以后,官方推荐我们使用注解去驱动Spring应用。那么很多人就一下子懵了,不需要xml配置文件了,那我的那些配置项怎么办呢?
@Configuration是Spring3.0推出来的注解,用来代替xml配置文件。
若一个Class类被标注了这个注解,我们就认为这个类就是一个配置类,然后在这个类里面就可以写相应的其它配置了,比如@Bean等等
既然@Configuration这么重要,它也作为管理其它配置、读取其它配置的入口,大多数小伙伴却并不知道它加载的时机以及解析的方式,这就造成了遇到一些稍微复杂点的问题时,无法入手去定位问题
本文旨在介绍一下Spring是怎么解析@Configuration注解驱动的配置文件的,这里ConfigurationClassPostProcessor这个处理器就闪亮登场了~
附:@Configuration源码:
@Target(ElementType.TYPE) // 它只能标注在类上 @Retention(RetentionPolicy.RUNTIME) @Documented @Component // 它也是个Spring的组件,会被扫描 public @interface Configuration { @AliasFor(annotation = Component.class) String value() default ""; //也可以自定义Bean的名称 }
Spring源码基于的Spring版本为:5.0.6.RELEASE(下同)
Spring源码基于的Spring版本为:5.0.6.RELEASE(下同)
Spring源码基于的Spring版本为:5.0.6.RELEASE(下同)
ConfigurationClassPostProcessor初识
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware { ... }
可以看出它是一个BeanFactoryPostProcessor,并且它是个功能更强大些的BeanDefinitionRegistryPostProcessor,有能力去处理一些Bean的定义信息~
需要知道的是,ConfigurationClassPostProcessor是Spring内部对BeanDefinitionRegistryPostProcessor接口的唯一实现。BeanFactoryPostProcessor顶级接口的实现类如下:
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry处理逻辑
为了更好的理解它的运行过程,我们需要知道它在什么时候调用:AbstractApplicationContext#refresh 中的第5步时进行调用postProcessBeanDefinitionRegistry:
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { //根据BeanDefinitionRegistry,生成registryId 是全局唯一的。 int registryId = System.identityHashCode(registry); // 判断,如果这个registryId 已经被执行过了,就不能够再执行了,否则抛出异常 if (this.registriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry); } if (this.factoriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + registry); } // 已经执行过的registry 防止重复执行 this.registriesPostProcessed.add(registryId); // 调用processConfigBeanDefinitions 进行Bean定义的加载.代码如下 processConfigBeanDefinitions(registry); } // 解释一下:我们配置类是什么时候注册进去的呢???(此处只讲注解驱动的Spring和SpringBoot) // 注解驱动的Spring为:自己在`MyWebAppInitializer`里面自己手动指定进去的 // SpringBoot为它自己的main引导类里去加载进来的,后面详说SpringBoot部分 public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); // 获取已经注册的bean名称(此处有7个 6个Bean+rootConfig) String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); // 这个判断很有意思~~~ 如果你的beanDef现在就已经确定了是full或者lite,说明你肯定已经被解析过了,,所以再来的话输出个debug即可(其实我觉得输出warn也行~~~) if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } // 检查是否是@Configuration的Class,如果是就标记下属性:full 或者lite。beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL) // 加入到configCandidates里保存配置文件类的定义 // 显然此处,仅仅只有rootConfig一个类符合条件 else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // Return immediately if no @Configuration classes were found // 如果一个配置文件类都没找到,现在就不需要再继续下去了 if (configCandidates.isEmpty()) { return; } // Sort by previously determined @Order value, if applicable // 把配置文件们:按照@Order注解进行排序 这个意思是,我们@Configuration注解的配置文件是支持order排序的。(备注:普通bean不行的~~~) configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); // Detect any custom bean name generation strategy supplied through the enclosing application context // 此处registry是DefaultListableBeanFactory 这里会进去 // 尝试着给Bean扫描方式,以及import方法的BeanNameGenerator赋值(若我们都没指定,那就是默认的AnnotationBeanNameGenerator:扫描为首字母小写,import为全类名) SingletonBeanRegistry sbr = null; if (registry instanceof SingletonBeanRegistry) { sbr = (SingletonBeanRegistry) registry; if (!this.localBeanNameGeneratorSet) { BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR); if (generator != null) { this.componentScanBeanNameGenerator = generator; this.importBeanNameGenerator = generator; } } } // web环境,这里都设置了StandardServletEnvironment // 一般来说到此处,env环境不可能为null了~~~ 此处做一个容错处理~~~ if (this.environment == null) { this.environment = new StandardEnvironment(); } // Parse each @Configuration class // 这是重点:真正解析@Configuration类的,其实是ConfigurationClassParser 这个解析器来做的 // parser 后面用于解析每一个配置类~~~~ ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); // 装载已经处理过的配置类,最大长度为:configCandidates.size() Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); do { // 核心方法:具体详解如下 parser.parse(candidates); // 校验 配置类不能使final的,因为需要使用CGLIB生成代理对象,见postProcessBeanFactory方法 parser.validate(); Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content // 如果Reader为null,那就实例化ConfigurationClassBeanDefinitionReader来加载Bean,并加入到alreadyParsed中,用于去重(避免譬如@ComponentScan直接互扫) if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } // 此处注意:调用了ConfigurationClassBeanDefinitionReader的loadBeanDefinitionsd的加载配置文件里面的@Bean/@Import们,具体讲解请参见下面 // 这个方法是非常重要的,因为它决定了向容器注册Bean定义信息的顺序问题~~~ this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); candidates.clear(); // 如果registry中注册的bean的数量 大于 之前获得的数量,则意味着在解析过程中又新加入了很多,那么就需要对其进行解继续析 if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames)); Set<String> alreadyParsedClasses = new HashSet<>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } for (String candidateName : newCandidateNames) { // 这一步挺有意思:若老的oldCandidateNames不包含。也就是说你是新进来的候选的Bean定义们,那就进一步的进行一个处理 // 比如这里的DelegatingWebMvcConfiguration,他就是新进的,因此它继续往下走 // 这个@Import进来的配置类最终会被ConfigurationClassPostProcessor这个后置处理器的postProcessBeanFactory 方法,进行处理和cglib增强 if (!oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes //如果SingletonBeanRegistry 不包含org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry, //则注册一个,bean 为 ImportRegistry. 一般都会进行注册的 if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry()); } //清楚缓存 元数据缓存 if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { // Clear cache in externally provided MetadataReaderFactory; this is a no-op // for a shared cache since it'll be cleared by the ApplicationContext. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); } }
需要注意下面两个核心方法,是如何判断某个类是否为配置类的(判断是full模式,还是lite模式的配置文件):
//如果类上有@Configuration注解说明是一个完全(Full)的配置类 public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) { return metadata.isAnnotated(Configuration.class.getName()); } public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) { // Do not consider an interface or an annotation... if (metadata.isInterface()) { return false; } // Any of the typical annotations found? for (String indicator : candidateIndicators) { if (metadata.isAnnotated(indicator)) { return true; } } // Finally, let's look for @Bean methods... try { return metadata.hasAnnotatedMethods(Bean.class.getName()); } catch (Throwable ex) { return false; } } candidateIndicators内容如下: private static final Set<String> candidateIndicators = new HashSet<>(8); static { candidateIndicators.add(Component.class.getName()); candidateIndicators.add(ComponentScan.class.getName()); candidateIndicators.add(Import.class.getName()); candidateIndicators.add(ImportResource.class.getName()); }
1.如果类上有@Configuration注解说明是一个完全(Full)的配置类
2.如果如果类上面有@Component,@ComponentScan,@Import,@ImportResource这些注解,那么就是一个简化配置类。如果不是上面两种情况,那么有@Bean注解修饰的方法也是简化配置类
完整@Configuration和Lite @Bean模式(Full模式和Lite模式的区别)
首先看看Spring对此的定义:在ConfigurationClassUtils里
// 只要这个类标注了:@Configuration注解就行 哪怕是接口、抽象类都木有问题 public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) { return metadata.isAnnotated(Configuration.class.getName()); } // 判断是Lite模式:(首先肯定没有@Configuration注解) // 1、不能是接口 // 2、但凡只有标注了一个下面注解,都算lite模式:@Component @ComponentScan @Import @ImportResource // 3、只有存在有一个方法标注了@Bean注解,那就是lite模式 public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) { // 不能是接口 if (metadata.isInterface()) { return false; } // 但凡只有标注了一个下面注解,都算lite模式:@Component @ComponentScan @Import @ImportResource for (String indicator : candidateIndicators) { if (metadata.isAnnotated(indicator)) { return true; } } // 只有存在有一个方法标注了@Bean注解,那就是lite模式 try { return metadata.hasAnnotatedMethods(Bean.class.getName()); } } // 不管是Full模式还是Lite模式,都被认为是候选的配置类 是上面两个方法的结合 public static boolean isConfigurationCandidate(AnnotationMetadata metadata) { return (isFullConfigurationCandidate(metadata) || isLiteConfigurationCandidate(metadata)); } // 下面两个方法是直接判断Bean定义信息,是否是配置类,至于Bean定义里这个属性啥时候放进去的,请参考 //ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)方法,它会对每个Bean定义信息进行检测(毕竟刚开始Bean定义信息是非常少的,所以速度也很快) public static boolean isFullConfigurationClass(BeanDefinition beanDef) { return CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE)); } public static boolean isLiteConfigurationClass(BeanDefinition beanDef) { return CONFIGURATION_CLASS_LITE.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE)); }
结论先说在前头:Full模式和Lite模式的唯一区别:Full模式的配置组件会被enhance(加强/代理),而Liter模式不会。其余使用方式都一样,比如@Bean、@Import等等
和full模式不同的是,Lite模式不能声明Bean之间的依赖关系。也就是说入参、Java方法调用,都不能达到直接注入的效果。特别是Java方法调用,就直接进方法体了。
在常见的场景中,@Bean方法将在@Configuration类中声明,确保始终使用“完整”模式,并因此将交叉方法引用重定向到容器的生命周期管理。这可以防止@Bean通过常规Java调用意外地调用相同的方法(这也就是为什么我们用方法调用,其实还是去容器里找Bean的原因,并不是新建了一个Bean),这有助于减少在“精简”模式下操作时难以跟踪的细微错误。
例子:
@Configuration public class AppConfig { @Bean public Foo foo() { return new Foo(bar()); // 这里调用的bar()方法 } @Bean public Bar bar() { return new Bar(); } }
Foo 接受一个bar的引用来进行构造器注入:
这种方法声明的bean的依赖关系只有在@Configuration类的@Bean方法中有
效。
但是,如果换成@Component(Lite模式),则foo()方法中new Foo(bar())传入的bar()方法会每次产生一个新的Bar对象
结论:
在@Component或其他组建中使用@Bean好处是不会启动CGLIB这种重量级工具(不过在Spring中即使这里不使用,其他很多地方也在使用)。并且@Component及其相关的Stereotype组件自身就有摸框级别的功能,在这里使用@Bean注解能很好的表明一个Bean的从属和结构关系,但是需要注意直接调用方法的“副作用”。
个人建议如果没什么特别的要求就使用@Configuration,引入CGLIB并不会影响多少性能,然而坑会少很多。
在spring官网将用@Configuration创建的@Bean称呼为"Full"模式、将@Component创建的@Bean称呼为"'lite"模式,从字面上也能略知他们的差异。
只有类上标注@Configuration才是full模式。标注有@Component、@ComponentScan、@Import、@ImportResource或者啥注解都没标注但是有被标注了@Bean的方法这种也是lite模式