Spring Beans实例化

来源:互联网 发布:思维脑图软件 编辑:程序博客网 时间:2024/05/01 15:15

        如果说Spring最核心的东西是什么,那就非Beans组件莫属了,Bean对于Spring的意义就象OOP对于Java的意义一样。本文基于Spring Boot1.4x来分析下Spring是如何实例化Bean。我们将会从Bean的定义、创建以及解析几个方面来分析。
        首先来说说Spring是如何找到并解析Bean属性的。如果使用注解的方式则Spring在启动初始化的时候会扫描项目classpath下符合要求的class,解析并注册到BeanFactory中,如果是xml的方式则是直接通过XmlBeanDefinitionReader解析Bean。现如今基于注解的配置是主流,Spring Boot更是把注解用到了极致。
        Spring是通过调用所有实现了BeanDefinitionRegistryPostProcessor接口类的postProcessBeanDefinitionRegistry方法来向BeanFactory中注册Bean定义的,如ConfigurationClassPostProcessor实现<context:component-scan/>功能、MapperScannerConfigurer注册Mybatis相关对象。
        调用所有实现了BeanFactoryPostProcessor接口类的postProcessBeanFactory方法,在实例化之前做一些修改,用户可以自行实现该接口,Spring提供了不少实现,常用的比如:PropertyPlaceholerConfigurer、CustomerEditorConfigurer、PropertyOverrideConfigurer等。
        ConfigurationClassPostProcessor做的东西很多,我们来讲其中一个,它会使用ComponentScanAnnotationParser解析注解@ComponentScan,并调用ClassPathBeanDefinitionScanner来扫描classpath下的目录及Jar中的类,扫描的路径是由@ComponentScan指定,扫描类ClassPathBeanDefinitionScanner的定义如下图所示:

        ResourcePatternResolver是扫描classpath下所有候选class文件路径并封装成Resource资源,这其中是基于ClassLoader.getResources获取目录并递归查找目录下class文件。MetadataReaderFactory的工作是通过ASM来解析前面扫描到的Resource文件对应的class字节码读取到类相关信息并封装成MetadataReader,这里没有使用反射来获取类信息也是为了避免JVM加载过多不必要的class。BeanNameGenerator一看就知道是用来生成Bean名称,如果注解未指定名称则默认使用类名并首字母小写。ScopeMetadataResolver是用来设置scope属性。最后会经过滤筛选出符合条件的类封装成BeanDefinition并使用BeanDefinitionRegistry注册到Spring容器中。这其中的过程看起来像是下图这样:

        上面有一个很重要的接口:BeanDefinition,它的定义如下图所示:

        通过ClassPathBeanDefinitionScanner扫描出来的BeanDefinition实现类为ScannedGenericBeanDefinition,它包含了注解信息AnnotationMetadata,类定义以及资源信息Resource。除了通过ClassPathBeanDefinitionScanner来扫描类之外,SpringBoot还会通过@EnableAutoConfiguration导入EnableAutoConfigurationImportSelector,并调用其selectImports方法从spring.factories文件中加载EnableAutoConfiguration对应的xxxAutoConfiguration配置类。这些导入的AutoConfiguration主要提供一些默认配置,比如:TransactionAutoConfiguration提供事务配置、DispatcherServletAutoConfiguration提供SpringMvc的DispatcherServlet配置、AopAutoConfiguration提供AOP配置等等,这些Spring高级组件以后会一一道来,现在主要目的是先打好基础。
        Spring会把载入的Bean定义调用registerBeanDefinition注册到BeanFactory中,默认实现为DefaultListableBeanFactory,它的体系结构如下图所示:

        Spring的DI(依赖注入)正是通过BeanFactory来实现的。里面最重要的方法是getBean,该方法提供获取对象实例的入口,无论bean到底是Prototype产生的独立的bean,还是Singleton产生的共享的bean,都是通过getBean来获取对象。Bean的定义、实例、依赖关系都会缓存至集合当中。
        缓存这些依赖关系的实现是DefaultSingletonBeanRegistry,而DefaultListableBeanFactory则继承了此类。在实例化Bean的过程中(调用getBean),会调用DefaultSingletonBeanRegistry.registerDependentBean向BeanFactory中缓存对象依赖关系,调用DefaultSingletonBeanRegistry.registerSingleton缓存共享实例。
        InstantiationStrategy负责实例化。实例化仅仅是调用构造函数,相当于new了一个对象而已,bean的具体的属性在此时并未赋值。InstantiationStrategy负责由Bean类的默认构造函数、带参构造函数或者工厂方法等来实例化Bean,InstantiationStrategy的默认实现为CglibSubclassingInstantiationStrategy。
        getBean的执行流程如下图所示,我们以getBean(String)来说明,其实getBean(Class)也是会转化成通过name来获取,里面怎么转换可以看看DefaultListableBeanFactory.getBeanNamesForType。

        上图中当实例是prototype时,会去检查是否存在循环依赖,基于ThreadLocal来实现,实例化过程中会把Bean Name保存到线程变量中,实例完再删除ThreadLocal中的Bean Name,如果A依赖B,B又依赖A,则直接抛出异常。其实单例中也会处理循环依赖问题,单例实例化过程中,在属性注入之前会把当前实例的引用放到缓存当中。如:A->B->A,在A创建过程中属性注入时需要B,于是实例化B,在实例化B时发现需要A,因此通过getBean(A)来实例化A,此时A已经将其实例的引用放到缓存中了,因此直接返回,于是B可以顺利完成实例化,A在得到B的实例化,即可进行下面的实例化过程。这里需要注意的是,spring中只能解决单例bean且为setter方式的循环依赖,对于原型bean或是单例bean构造函数方式的循环依赖,spring是无法解决的,只能抛出异常了。因为原型bean,在spring容器中不存在缓存。单例bean构造函数方式依赖时,由于bean没有构造好,无法缓存实例bean的引用。
        Bean实例的初始化主要在initializeBean方法中,主要实现的功能就是调用自定扩展,如InitializingBean接口、指定的init-method方法、实例化后的回调函数等。属性的注入实现封装在populateBean方法中,@Autowired依赖的Bean,以及List,Map,Set等特容器属性,然后将得到值采用TypeConvertor进行转换得到最终的属性值,最后通过BeanWrapper.setPropertyValues方法将得到的值注入到bean实例中。BeanWrapper是对实例的一种包装,Spring正是通过BeanWrapper对实例进行设置或获取属性值。

0 0
原创粉丝点击