ImportBeanDefinitionRegistrar
该接口功能非常强大,能够实现快速的、批量的、扫描式的注册。比如我们熟悉的ServletComponentScanRegistrar就是去解析注解@ServletComponentScan实现批量注册Bean定义
MapperScannerRegistrar就是MyBatis用来解析@MapperScan注解,来扫描的 等等还有很多类似的设计方式
先看一个最简单的效果吧:
@Configuration @Import({Parent.class, AntPathMatcher.class, MyImportSelector.class, MyDeferredImportSelector.class, MyImportBeanDefinitionRegistrar.class}) public class RootConfig { } public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { // 同样的,这种注入都是不好使的(相同的,那些感知接口是可以实现的,从而注入对应组件) @Autowired private HelloService helloService; /** * 实现了该接口让我们的这个类成为了拥有注册bean的能力 * 也可以让我们实现动态注入(根据条件、逻辑进行动态注入) * * @param importingClassMetadata 注解信息和本类信息 * @param registry 注册器,我们可以向容器里面注册[Bean定义信息] */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { System.out.println("this MyImportBeanDefinitionRegistrar"); RootBeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClass(GenericBean.class); registry.registerBeanDefinition("genericBean", beanDefinition); } }
输出:(genericBean成功被注入容器了)
rootConfig helloServiceImpl com.fsx.bean.Parent org.springframework.util.AntPathMatcher com.fsx.bean.Child genericBean
从另外一个日志的打印来看:MyImportBeanDefinitionRegistrar
是更加滞后执行的。那么下面我们就要看看它到底啥时候执行的呢?
this MyImportSelector... this MyDeferredImportSelector... this MyImportBeanDefinitionRegistrar
有了上面的源码分析我们知道了,实现了ImportBeanDefinitionRegistrar
接口的,最后是这么一句话给缓存下来了(还木有执行):
// registrar是已经被实例化了的当前类 configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
下面重点看看,该接口到底什么时候执行的呢?根据断点,跟踪到它的加载时机是这句代码里:this.reader.loadBeanDefinitions(configClasses);
:
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) { TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator(); // 依次解析每一个配置类,这里面注意了configClass 比如我们的RootConfig这个对象里有个字段 //importBeanDefinitionRegistrars是记录着了我们前面add进去的ImportBeanDefinitionRegistrar的,因此它会在此处开始执行了 for (ConfigurationClass configClass : configurationModel) { loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } } // 这个方法很重要:它处理了多种方式(@Bean、实现接口类注册等等)完成向容器里注册Bean定义信息 private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { // 如果这咯Config不需要被解析,做一些清理、移除的操作~~~~ 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; } ///稍微注意一下Spring处理这些Bean定义的顺序,在某些判断逻辑中或许能用到/// // 如果是被单纯@Import进来的,这个值是true的,默认值是false哦 if (configClass.isImported()) { // 这个处理源码这里不分析了,比较简单。支持@Scope、@Lazy、@Primary、@DependsOn、@Role、@Description等等一些通用的基本属性 registerBeanDefinitionForImportedConfigurationClass(configClass); } //处理方法上的@Bean 解析@Bean上面各种属性值。也处理上面提到的那些通用注解@Lazy等等吧 //这里面只说一个内部比较重要的方法isOverriddenByExistingDefinition(beanMethod, beanName) // 该方法目的主要是去重。其实如果是@Configuration里面Bean重名了,IDEA类似工具发现,但是无法判断xml是否也存在(注意,发现归发现,但并不是编译报错哦~~~) // 它的处理策略为:若来自同一个@Configuration配置类,那就保留之前的。若来自不同配置类,那就覆盖 for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } //处理@ImportResource,里面解析xml就是上面说到的解析xml的XmlBeanDefinitionReader //所以,咱们@Configuration和xml是可以并行使用的 loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); // 最后,这里就是咱们今天的主菜了:解析咱们的ImportBeanDefinitionRegistrars // configClass.getImportBeanDefinitionRegistrars():就是我们上面异步add进去的那些注册器们 loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }
loadBeanDefinitionsFromRegistrars完整解析:
// 没什么多余的代码 所有的注册逻辑(哪些Bean需要注册,哪些不需要之类的,全部交给子类去实现) // 用处:上面有提到,比如@MapperScan这种批量扫描的=== private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) { registrars.forEach((registrar, metadata) -> registrar.registerBeanDefinitions(metadata, this.registry)); }
应用场景分析
根据各个接口的特点,有各自的应用场景。因为直接@Import普通类的场景相对较少,这里主要说说实现接口的方式的场景:
ImportSelector接口应用场景
AdviceModeImportSelector:它是个抽象类。(实现类有出名的AsyncConfigurationSelector、CachingConfigurationSelector等,因为都是基于代理来做的,所以都继承了此抽象)。
它拿到泛型类型(比如@EnableAsync或者@EnableCaching),然后解析注解为AnnotationAttributes,最后由子类去实现select逻辑(具体要向容器注入的Class全类名),比如注入ProxyAsyncConfiguration,或者其它的。。。
总之,向这种还不能决定去注入哪个处理器(如果你能决定,那就直接@Import那个类好了,没必要实现接口了嘛),然后可以实现此接口,写出一些判断逻辑,不同的配置情况注入不同的处理类。
DeferredImportSelector接口应用场景
它和上面只是执行的时机不同。在Spring内部没有应用,但是在Spring Boot中却有大量的应用,比如:
AutoConfigurationImportSelector、EnableCircuitBreakerImportSelector等等(在SpirngBoot章节中还会重点分析)
实现这个接口的基本思想是:默认处理(以用户配置的为准,若用户没管,那就执行我的默认配置呗)。执行生效的一个先后顺序的简单控制
ImportBeanDefinitionRegistrar接口应用场景
它的应用场景特别的有用,因此也是最常使用的。因为她直接可以向工厂里注册Bean的定义信息(当然也可以拿出来Bean定义信息,做出对应的修改)~
下面两个实现,都喝@EnableAspectJAutoProxy注解相关:
- AspectJAutoProxyRegistrar:它能解析注解的时候,从BeanFactory拿出指定的Bean,设置一些参数值等等
- AutoProxyRegistrar:自动代理的注册器。它和上面的区别在于它和代理的类型无关(它可以指定mode类型),而上面是表示就是用AspectJ来做切面代理。
实现它的基本思想是:当自己需要操作BeanFactory里面的Bean的时候,那就必须只有它才能做到了。而且它还有个方便的地方,那就是做包扫描的时候,比如@MapperScan类似这种的时候,用它处理更为方便(因为扫描到了直接注册即可)
备注:@Mapper的扫描依赖于ClassPathMapperScanner,它由mybatis-spring提供。它继承与ClassPathBeanDefinitionScanner由Spring底层提供
总结
注意,完成了所有的这些注解、配置文件的解析,Bean们都还只是被加载了,还没有加入到Bean的定义信息里面,更别谈实例化了。要加入到Bean的定义信息里面存储好,还得这一步:
//configClasses就是parser.parse(candidates);上面解析完成了的配置类 //根据这些已经解析好了的配置类,由这个ConfigurationClassBeanDefinitionReader去加载Bean定义信息 this.reader.loadBeanDefinitions(configClasses);
然后,上面已经从源码上分析了Spring处理的一个流程,希望各位小伙伴能更宏观的明晰这个执行的时序图(流程图),最好是能画出来(我这里就不画了哈,若哪位小伙伴有心,欢迎留言贡献一份,不胜感激~)