postProcessBeanDefinitionRegistry()处理过程总结
1.使用工具ConfigurationClassParser尝试发现所有的配置(@Configuration)类
2.使用工具ConfigurationClassBeanDefinitionReader注册所发现的配置类中所有的bean定义
3.结束执行的条件是所有配置类都被发现和处理,相应的bean定义注册到容器(内部有递归)
ConfigurationClassPostProcessor#postProcessBeanFactory处理逻辑
这里就是增强配置类,添加一个 ImportAwareBeanPostProcessor,这个类用来处理 ImportAware 接口实现
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { int factoryId = System.identityHashCode(beanFactory); if (this.factoriesPostProcessed.contains(factoryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + beanFactory); } this.factoriesPostProcessed.add(factoryId);\ // 这一步的意思是:如果processConfigBeanDefinitions没被执行过(不支持hook的时候不会执行) // 这里会补充去执行processConfigBeanDefinitions这个方法 if (!this.registriesPostProcessed.contains(factoryId)) { // BeanDefinitionRegistryPostProcessor hook apparently not supported... // Simply call processConfigurationClasses lazily at this point then. processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory); } // 这个是核心方法~~~~~~~~~~~~~~ enhanceConfigurationClasses(beanFactory); // 添加一个后置处理器。ImportAwareBeanPostProcessor它是一个静态内部类 // 记住:它实现了SmartInstantiationAwareBeanPostProcessor这个接口就可以了。后面会有作用的 //InstantiationAwareBeanPostProcessor是BeanPostProcessor的子接口,可以在Bean生命周期的另外两个时期提供扩展的回调接口 //即实例化Bean之前(调用postProcessBeforeInstantiation方法)和实例化Bean之后(调用postProcessAfterInstantiation方法) beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory)); }
enhanceConfigurationClasses如下:
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) { Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>(); // 拿到所有的Bean名称,当前环境一共9个(6个基础Bean+rootConfig+helloServiceImpl+parent视图类) for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); // 显然,能进来这个条件的,就只剩rootConfig这个类定义了 // 从此处也可以看出,如果是lite版的@Configuration,是不会增强的 if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) { if (!(beanDef instanceof AbstractBeanDefinition)) { throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" + beanName + "' since it is not stored in an AbstractBeanDefinition subclass"); } else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) { logger.warn("Cannot enhance @Configuration bean definition '" + beanName + "' since its singleton instance has been created too early. The typical cause " + "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " + "return type: Consider declaring such methods as 'static'."); } configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef); } } // 装载着等待被加强的一些配置类们 if (configBeanDefs.isEmpty()) { // nothing to enhance -> return immediately return; } ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer(); for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) { AbstractBeanDefinition beanDef = entry.getValue(); // If a @Configuration class gets proxied, always proxy the target class beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); try { // Set enhanced subclass of the user-specified bean class // 拿到原始的rootConfig类 Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader); if (configClass != null) { // 对此类进行cglib增强 // 注意增强后的类为:class com.fsx.config.RootConfig$$EnhancerBySpringCGLIB$$13993c97 Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader); if (configClass != enhancedClass) { if (logger.isDebugEnabled()) { logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " + "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName())); } // 把增强后的类配置类set进去。 // 所以我们getBean()配置类,拿出来的是配置类的代理对象 是被CGLIB代理过的 beanDef.setBeanClass(enhancedClass); } } } catch (Throwable ex) { throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex); } } }
可能有人会问,Spring为何要用cglib增强配置文件@Configuration呢?
其实上面提到的Full和Lite的区别已经能够有所答案了:
我们通过源码发现:只有full模式的才会去增强,然后增强带来的好处是:Spring可以更好的管理Bean的依赖关系了。比如@Bean之间方法之间的调用,我们发现,其实是去Spring容器里去找Bean了,而并不是再生成了一个实例。(它的缺点是使用了代理,带来的性能影响完全可以忽略)
其实这些可以通过我们自己书写代码来避免,但是Spring为了让他的自动化识别来得更加强大,所以采用代理技术来接管这些配置Bean的依赖,可谓对开发者十分的友好
上面源码也指出了:Liter模式是不会采用代理的,因此它的Bean依赖关系程序员自己去把控吧。建议:不要使用Lite模式,会带来不少莫名其妙的坑
@Configuration注解的配置类有如下要求
1.@Configuration不可以是final类型
2.@Configuration不可以是匿名类
3.嵌套的@Configuration必须是静态类
ConfigurationClassBeanDefinitionReader使用详解
功能:读取一组已经被完整解析的配置类ConfigurationClass,基于它们所携带的信息向给定bean容器BeanDefinitionRegistry注册其中所有的bean定义。
该内部工具如上,由BeanDefinitionRegistryPostProcessor来使用。Spring中的责任分工是非常明确的:
ConfigurationClassParser负责去找到所有的配置类。(包括做加强操作)
然后交给ConfigurationClassBeanDefinitionReader将这些配置类中的bean定义注册到容器
该类只提供了一个public方法供外面调用:
这个方法是根据传的配置们,去解析每个配置文件所标注的@Bean们,一起其余细节~
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) { //TrackedConditionEvaluator是个内部类:是去解析@Conditional相关注解的。借助了conditionEvaluator去计算处理 主要是看看要不要shouldSkip() TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator(); // 遍历处理参数configurationModel中的每个配置类 // 这里需要特别注意的是,此环境下,这里size不是1,而是2(rootConfig和helloServiceImpl) // 因为对于parser来说,只要是@Component都是一个组件(配置文件),只是是Lite模式而已 // 因此我们也是可以在任意一个@Component标注的类上使用@Bean向里面注册Bean的,相当于采用的Lite模式。只是,只是我们一般不会去这么干而已,毕竟要职责单一 for (ConfigurationClass configClass : configurationModel) { loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } } //从指定的一个配置类ConfigurationClass中提取bean定义信息并注册bean定义到bean容器 : //1. 配置类本身要注册为bean定义 2. 配置类中的@Bean注解方法要注册为配置类 private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { // 判断是否需要跳过,与之前解析@Configuration判断是否跳过的逻辑是相同的 借助了conditionEvaluator。如果需要 // 显然这里,哪怕是helloServiceImpl都不会被跳过 if (trackedConditionEvaluator.shouldSkip(configClass)) { String beanName = configClass.getBeanName(); if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { this.registry.removeBeanDefinition(beanName); } this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName()); return; } // 如果这个类是@Import进来的 那就注册为一个BeanDefinition 比如这种@Import(Child.class) 这里就会是true if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } // 这里处理的是所有标注有@Bean注解的方法们,然后注册成BeanDefinition // 同时会解析一些列的@Bean内的属性,以及可以标注的其余注解 // 备注:方法访问权限无所谓,private都行。然后static的也行 for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } //加载@ImportResource注解配置的资源需要生成的BeanDefinition loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); // 调用自定义的ImportBeanDefinitionRegistrar的registerBeanDefinitions方法注册BeanDefinition loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }
就这样通过这个Reader,把所有的Bean定义都加进容器了,后面就可以很方便的获取到了
ConfigurationClassParser 总结
Spring的工具类ConfigurationClassParser用于分析@Configuration注解的配置类,产生一组ConfigurationClass对象。
分析过程主要是递归分析配置类的注解@Import(比如我们的@EnableWebMvc注解,就@Import(DelegatingWebMvcConfiguration.class),然后它就是一个@Configuration)
`,配置类内部嵌套类,找出其中所有的配置类,然后返回这组配置类
该工具主要由ConfigurationClassPostProcessor使用,而ConfigurationClassPostProcessor是一个BeanDefinitionRegistryPostProcessor/BeanFactoryPostProcessor,它会在容器启动过程中,应用上下文上执行各个BeanFactoryPostProcessor时被执行。
ConfigurationClassParser 所在包 : org.springframework.context.annotation。由此可知,Spring给这个处理器的定位,就是去处理解析相关注解的
关于此Parserd的详细讲解,我找到了一篇讲得非常全面的文章供以参考,这里我就不再班门弄斧了:
Spring 工具类 ConfigurationClassParser 分析得到配置类
getImports、collectImports
这里面特别的说一下比较有意思的getImports()(collectImports):这个方法目的是递归去搜集到所有的@Import注解
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException { // 装载所有的搜集到的import Set<SourceClass> imports = new LinkedHashSet<>(); // 这个集合很有意思:就是去看看所有的内嵌类、以及注解是否有@Import注解 // 比如看下面这个截图,会把所有的注解都给翻出来,哪怕是注解的注解 Set<SourceClass> visited = new LinkedHashSet<>(); collectImports(sourceClass, imports, visited); return imports; } private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited) throws IOException { // 此处什么时候返回true,什么时候返回false,请操作HashMap的put方法的返回值,看什么时候返回null // 答案:put一个新key,返回null。put一个已经存在的key,返回老的value值 // 因此此处把add放在if条件里,是比较有技巧性的(若放置的是新的,返回null,若已经存在,就返回的false,不需要用contains()进一步判断了) if (visited.add(sourceClass)) { for (SourceClass annotation : sourceClass.getAnnotations()) { String annName = annotation.getMetadata().getClassName(); // 此处不能以java打头,是为了过滤源注解:比如java.lang.annotation.Target这种 // 并且这个注解如果已经是Import注解了,那也就停止递归了 if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) { collectImports(annotation, imports, visited); } } imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value")); } }
我们可以看到在解析到@EnableWebMvc
的时候,拿到了它的@Import,拿到DelegatingWebMvcConfiguration
,但是我们发现它也还是个@Configuration
@Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { ... }
需要注意的是:它的父类WebMvcConfigurationSupport,里面有非常多的@Bean注解的方法,比如RequestMappingHandlerMapping、BeanNameUrlHandlerMapping等等共18个类都会被注册到容器里(Spring非常强大,配置文件都会解析父类的@Bean标签),理解了这里,到时候后面讲解为何SpringBoot环境下,若我们写了@EnableWebMvc这个注解,就脱离Spring的管理了 就非常好理解其中的原因了~~~~~~~~~~~~~~
然后吧这些@Import交给processImports()去处理。进而又会递归式的处理@Configuration文件一样处理(内部也就可以写@Bean之类隐式的给容器注册Bean)。
总结
Spring设计了很多的后置处理器,让调用者可以在Bean定义时、Bean生成前后等等时机参与进来。而我们此处的ConfigurationClassPostProcessor就是Spring自己为我们实现的,来解析@Confiuration以及相关配置注解的处理器
了解了此处理器的解析过程,在我们自己去处理配置文件的时候,也能够更加的得心应手。比如知其然,知其所以然后,我们就能更加熟练的运用@Import、@ImportSelect这种高级注解实现特定的设计模式。这些在Spring Boot的整体框架设计中,得到了大量的运用