为什么用这个名字来定位今天的标题呢?其实是因为我们的bean在实例化并初始化之前,spring已经对我们的class文件做了提前一步的准备,那就是我们的BeanDefinition,从字面的意思上可以理解为Bean描述,Spring中最开始是通过扫描我们的class文件然后注册到一个BeanDefinition Map中,我们的部分Bean信息实例化过程是依赖于我们的BeanDefinitionMap来完成的,所以了解BeanDefinitionMap的创建过程,可以对扩展Spring有很大的帮助。 spring BeanDefinition是如何完成资源定位?这里我们仅仅讲解集合Spring Boot使用的场景。相信看过Spring源码的老铁,是知道AbstractApplicationContext.refresh()方法的,spring BeanDefinition的资源定位就是在这个方法中的invokeBeanFactoryPostProcessor()方法中的.
首先我们来看看,我们是如何定位到这个class文件要交给我们spring容器管理的?我们在使用spring boot的过程中,经常会使用到@ComponentScan这个注解,指定我们要扫描的clas文件所在的包目录,这里我们不讨论是怎么拿到这个包目录的,这里读者自行查找,当我们拿到包目录之后,我们就可以使用ClassPathBeanDefinitionScanner类里面的doScan(String... basePackages)方法类定位到我们的需要管理的类,这里面可以找到那些类是标记了@Component等等注解的,您以为这样就完了吗?那你就错了,Spring在这个扫描的过程中还提供了一个很大的扩展点,就是Condition,在扫描的过程中,他会查看这个类,有没有标记@Conditional的注解,如果标记了,表示这个类需要执行过滤逻辑,也就是有可能即使这个类有@Component注解的话,也有可能不会注入到Spring容器中,在现实的开发中,笔者也曾经使用到了这个功能来做一些业务逻辑,笔者是这样使用的,对于开发环境或者测试环境不同的场景可能由于外围条件的限制,代码可能是不一样的,所以笔者就借助了@Profile注解来实现了各个环境代码的剥离,有兴趣的读者可以看下@Profile的原理是啥样的,这里就不做过多的赘述了。
基于java配置的形式将我们的class交给spring管理的方式 1)@Component @Service等等注解 2)@Bean 3) @Import && @ImportResource 4) 基于java api的方式,实现BeanFactoryPostProcessor,然后获取到spring容器,往spring容器中注册BeanDefinition
这里我们主要介绍第三种方式和第四种方式 @Import方式的话 使用Import的话,可以分为三种形式的使用方式 1)普通的类,直接@Import(XXXX.class)即可 2)ImportSelector实现类,一般是自定义一个类,实现ImportSelector接口,然后通过@Import(MyImportSelector.class)即可使用 3)ImportBeanDefinitionRegistrar实现类,一般是自定义一个类,实现ImportBeanDefinitionRegistrar接口,然后通过@Import(MyImportBeanDefinitionRegistrar.class)即可
其主要的原理解析的代码位于 org.springframework.context.annotation.ConfigurationClassParser#processImports 这里代码逻辑还是比较简单的,大体这边给读者们简单介绍一下, 引用的是普通的类,就是通过反射获取配置的class信息,然后注册到spring容器中,下面的这两种实现方式的话,都是扩展的方式实现的,读者可以自定义自己的逻辑,然后代码的逻辑主要就是最后调用自己的逻辑实现注册,当然ImportSelector和ImportBeanDefinitionRegistrar还是有很大的区别的,
基于java api的方式 BeanDefinitionRegistry#registerBeanDefinition 如果读者希望使用这种方式来将我们的java类交给我们的spring容器管理的话,我们通常需要自己写一个类来实现BeanFactoryPostProcessor接口或者BeanDefinitionRegistryPostProcessor接口,当然实现的逻辑主要也是简单的做一个注册的功能即可,当然在上面的ImportBeanDefinitionRegistrar也是通过BeanDefinitionRegistry#registerBeanDefinition这个api来向容器中注册BeanDefinition的,所以这是两种不同的方式都可以实现一样的功能,只是相当于这是多个扩展点,所以笔者在这里还是把两种方式都介绍给大家了,条条大路通罗马,多知道一条总是没毛病的。Spring就是通过这些扩展点来支持各个java开发可以来动态的参与BeanDefinition的注册过程,下面介绍一下使用了这些扩展点的一些比较知名的java类吧
实现了ImportBeanDefinitionRegistrar的类有 场景一:mybatis中@MapperScan中有使用@Import注解来导入MapperScannerRegistrar这个类 场景二:spring aop中的AspectJAutoProxyRegistrar类 场景三: 这里场景太多,笔者没有这个实力去列举出所有的场景,或者大家可以进入到ImportBeanDeifnitionRegistrar里面去查看这个类的实现类也可以的,大家在开发一个jar包,需要对接spring的框架的时候,ImportBeanDefinitionRegistrar还是很有用的
实现BeanFactoryPostProcessor的经典场景有: 场景一:ConfigurationClassPostProcessor,这个类就是Spring中一个特别核心的类了,可以根据名字看到就是配置类的后置处理器,通常是用来处理我们标注了@Configuration类的一个处理器,而@Configuration中一般又会和@ComponentScan一起来使用这样的话,spring就知道我们要扫描的包目录结构了,然后就可以通过一些过滤条件(是否是spring的注解以及Condition条件去完成一些过滤的工作),然后将剩下来的一些黄金圣斗士保存起来,这些人就是我们需要关注的bean了! 其他的场景笔者也正在摸索的过程中,需要大家一起摸索哈!笔者第一次写博客,纯理论也没附加图片,也没实例,不喜勿喷!
总是,我们总结一些BeanDefinition的查找吧,我们首先可以拿到一个扫描包目录,然后扫描到这个包目录下面所有的class文件,然后一个一个的查找是否是申请者(ClassPathScanningCandidateComponentProvider#scanCandidateComponents)方法实现,scanCandidateComponents()这个方法中,又会通过condition去过滤掉一些,Spring Boot根据配置自动注入大量场景就是使用到了@Conditional注解,例如假如用户添加了Redis的配置,我们就直接能够使用RedisTemplate了,这个就是基于@Conditional的原理来实现的,读者可以自己好好的去感受一下,我们拿到所有的BeanDefinion集合之后,就可以通过BeanDefinitionRegistry#registerBeanDefinition来将对应的BeanDefinition注册到beanDefinitionMap中,后面的Spring的实例化以及生命周期是依赖于beanDefinitionNames来看需要实例化那些类的,BeanDefinition的查找和注册笔者就介绍到这里了,还是说一句,第一次写博客,不喜勿喷哈,笔者也会持续的提高组织能力,下次争取多一点例子,谢谢!