Spring-beans架构设计原理

IOC

IOC,大名鼎鼎,如雷贯耳。官方给的定义是依赖注入(Dependency Injection)或者控制反转(Inversion of Control),都相当术语化,不太容易懂。

想象下日常中的生产过程,在生产之前是客户下单,单子上会详细注明需要的产品,包括产品的各方面规格属性,然后工厂据此生产。

IOC就是一个类似的过程,我们声明需要什么,工厂据此给我们生产出来。在这个过程中我们只是给出了需求清单,而不用再去辛苦new,以及不停设置各种属性。

Spring ioc

Spring-beans是ioc最著名的实现,而且没有之一,在业界几乎也就是ioc的代名词。Beans是整个spring的基石组件,可以说所有的spring组件都是基于它。

spring-beans整体架构

Spring-beans提供的优秀的可扩展能力使spring几乎能包容一切,用户只需遵循spring beans的相关规范--spring.schema定义配置文档的文法规范,spring.handler定义客户化配置的解析工具--就可以将bean接入到spring容器里。Bean接入后还可以通过实现BeanPostProcessor或者init-method对bean做后处理甚至替换bean。

Spring-beans的优秀设计使spring越来越像是一个生态圈,基于beans,aop、context、mvc、annotation等强大的组件都被接入进来,除此还有一些优秀的第三方组件,例如dubbo等。

以aop为例,先在parse阶段对aop的配置生成Advised bean(Advisor),然后在所有bean的后处理阶段get Advised bean,并通过filter判断是否适应于当前bean,适用则会对bean织入advice。后面如果有时间会专门做篇aop,这里不再继续深入。

Dubbo和aop略有区别,dubbo的扩展选择了init method中的afterPropertiesSet,所有严格意义上dubbo是无法去spring的,虽然dubbo声明是可以,但其实只能不适用spring功能,而不能去spring jar,dubbo bean在init阶段生成ref,其实也是个代理--封装了远程访问的细节。

Bean定义

Spring-beans的核心实体是BeanDefinition和BeanFactory。前者映射我们的定义,后者则是依据定义生产bean的工厂。

上图是spring beans的静态结构图,更多是偏重于bean解析,因为1. 理解了bean解析也就理解了一半spring扩展能力;2.BeanFactory的复杂不在于类之间的组织结构,而在于复杂的调用链路,也就没必要是静态结构方面做过多说明。需要说明的是,这只是概念模型,并不完全映射到类,因为spring的抽象层次太高,一个概念实体功能往往由多个类协同完成,画起来比较费劲,就类似BeanFactory,光搞清楚各个BeanFactory之间的关系就理得头痛,所以都尽可能从概念层面说明。

重要实体说明

  • DefaultListableBeanFactory是BeanDefintionRegistry的默认实现,它是个适配器,用于适配BeanFactory和BeanDefintionRegistry,工厂和定义通过它统一。它由ApplicationContext初始化,并被作为BeanDefinitionReader的registry。Reader对配置文档加载解析,生成definition并注册到registry--其实就是DefaultListableBeanFactory,这样工厂就拥有了类定义,bean初始化时也可以通过内部方法轻松获取到定义。
  • NamespaceHandlerResolver用于获取配置解析实体--NamespaceHandler。它和registry均内聚在上下文实体--ReaderContext中,parser内聚上下文从而可以间接访问handler和registry获得解析和注册的能力。

其他几个实体都比较直观便于理解,不再一一赘述。

整体交互过程

BeanDefinitionReader是整个bean解析的聚合根,它由ApplicationContext创建,并将DefaultListableBeanFactory作为registry传递给它。

BeanDefinitionReader创建文档读取实体--DocuemntReader用于加载解析,并在step3加载文档时创建上下文--ReaderContext传递给文档读取实体。上下文贯穿于整个解析过程始终,它在文档读取实体使用parser解析时也会被传入parser中。

Parse过程是整个加载过程的核心,默认parser通过间接关联的识别器可以依据不同配置节点进行parser切换,当读到非默认配置时,则切换到对应客户化parser解析。解析完成后再通过间接关联的registry进行注册,从而配置定义进入spring管理,待getBean时使用。

客户化配置节点解析

客户化配置是spring非常重要的扩展点,spring强大的扩展能力有一半功能要归功于它,另一半中的80%就是后面要介绍的大名鼎鼎的BeanPostProcessor。不仅仅一些第三方扩展(例如开篇提到的dubbo)基于它,spring本身的很多模块也是基于它,例如spring-aop,spring-context等等,spring体系内除了默认的beans命名空间其余都基于它扩展的。

NamespaceHandlerResolver由BeanDefinitionReader初始化,后者在第一次被访问时读取spring.handlers文件。.handlers文件定义namespace uri和对应处理类的映射关系。例如:

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler

上面的这行配置就是配置声明的解析类。

NamespaceHandlerResolver依据节点namespace获得NamespaceHandler,然后使用handler处理自定义配置节点。

public interface NamespaceHandler {
    void init();

    BeanDefinition parse(Element element, ParserContext parserContext);

    BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);

}

init方法注册localName和自定义parser的关系,parser和localName的关系由handler的提供者自己注册。例如:

public void init() {
        // In 2.0 XSD as well as in 2.1 XSD.
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

        // Only in 2.0 XSD: moved to context namespace as of 2.1
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }

上面就是aop注册parser的代码片段。config,aspectj-autoproxy这些就是localName,parse过程中不同的localName会切换到不同的parser解析。

spring先通过命名空间定位到handler,handler处理时再基于localName取相应的parser解析当前结点。比如这个配置,aop是命名空间,aspectj-autoproxy是localName。整个读取解析过程中先通过aop找到AopNamespaceHandler,再在解析到aspectj-autoproxy节点时使用AspectJAutoProxyBeanDefinitionParser来解析。如果要研究spring源码,一定要先找到对应parser,知道每个配置项对应到运行时的bean结构才能更好理解spring;而且parser可能会生成一些默认的BeanPostProcessor,如果意识不到这些后处理器,那么对代码的读取将会断片,陷入完全无法理解的境地。比如spring-aop就是由parser默认生成AopAutoProxyCreator这个BeanPostProcessor,在bean初始化后由这个processor对bean生成代理。

Bean获取


上图是getBean过程,整个过程很简洁,实际深入代码会发现非常繁琐。

BeanFactory和BeanDefinitionRegistry在spring里是统一的,参见第一节,图上为了方便理解,拆成两个概念实体。

需要注意的是第4步和第6步,bean配置时可以指定parent属性,如果有parent,则beanFactory会对local和parent做merge,merge的策略是对parent做覆盖,也可以理解为是对parent做继承。这和parent bean factory完全是两个概念,一定要区分开。

在beans的实体静态结构里,分别注明了parent bean definition和parent bean factory。两者都是被关联的,而不是被继承。后者有点像jvm的双亲委托模型,parent和child有各自的上下文,类似于jvm的命名空间。parent bean factory由applicationContext设置,无法配置。比如spring mvc就是两个父子两个容器,在容器refresh时相应的也会把父容器的BeanFactory设置成子容器BeanFactory的parentBeanFactory。

spring bean状态


Bean主要经过instantiate,populate,initializeBean和registerDisposableBean4个状态,在状态流转中会调用很多spring预留的扩展接口。

  1. awareMethod
    如果bean继承了BeanFactoryAware,BeanNameAware,BeanClassLoaderAware,则会在initialize阶段将BeanFactory, BeanName和bean ClassLoader设置给Bean。
    注意它和ApplicationContextAware是不一样的,后者是由BeanPostProcessor做后处理set的。
  2. init method
    init method不仅仅包括配置的init-method方法还包括InitializedBean的afterPropertiesSet回调接口,这两者均是无参的,完全可以互相替代,两者中afterPropertiesSet调用在前。

BeanPostProcessor

上文提到过,spring另一半的扩展能力是由BeanPostProcessor提供的。先看下其接口定义

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

两个方法分别在init-method调用前后,对bean做后处理。需要特别注意的就是postProcessAfterInitialization,大部分的spring扩展就是由它来完成的,比如上文提到的aop就是在这个阶段对bean做后处理生成代理。相应的也可以使用postProcessBeforeInitialization,但是此时init-method并未执行,后处理需要保证init-method带来的影响,@PostConstruct的方法执行就是在这个阶段。

实例化后处理器

InstantiationAwareBeanPostProcessor继承了BeanPostProcessor,主要用于在bean实例化前后做处理。

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
    Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;

    boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;

    PropertyValues postProcessPropertyValues(
            PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
            throws BeansException;
}
  • 实例化前处理,这个方法的参数是beanClass和beanName,在bean实例化前调用,如果这个方法的返回值不为空则getBean结束,用户收到的就是这个该方法的返回值。这个扩展点主要是留给预处理的,用户可以直接生成一个同类型的bean,替换实际bean,此时实际bean不会被实例化。
  • 实例化后处理连同属性后处理是spring内部非常重要的扩展点,annotation的field注入就是在这两个阶段完成。主要有两个关键实现:
    CommonAnnotationBeanPostProcessor,该类在实例化后处理阶段做属性注入,主要用于@Resource
    AutowiredAnnotationBeanPostProcessor,该类在属性后处理阶段做属性注入,主要用于@Autowired
    前者还继承了InitDestroyAnnotationBeanPostProcessor类,该类会在init-method被执行前调用@PostConstruct方法。
时间: 2024-09-20 00:01:26

Spring-beans架构设计原理的相关文章

Spring beans架构设计原理--set注入

Java Beans Spring管理对象是以bean为颗粒度,在最初设计时其实是特指Java beans,因此之前的注入也几乎是清一色的set注入,直到聪明的大脑们引入了Annotation后两者才有了明显差异,慢慢进化出Spring特有的bean规范. 本篇先从设计者的初衷Java Beans开始,理清楚set的注入原理,然后再(如)往(果)下(有)探(时)寻(间)Annotation注入. Java beans规范主要有三点: 有一个公有的无参构造器 属性可以通过get.set.is(可以

restful 架构设计 原理 运用

问题描述 restful 架构设计 原理 运用 在网络应用中有restful架构已经很热很久了,但是我现在还不是很清楚到底什么是restful思想,原理,然后需要在项目中怎样去运用,希望大家能给我相关资料学习参考下,谢谢 解决方案 http://lwe.iteye.com/blog/1484781

jQuery技术内幕:深入解析jQuery架构设计与实现原理1

jQuery技术内幕:深入解析jQuery架构设计与实现原理 高 云 著 图书在版编目(CIP)数据 jQuery技术内幕:深入解析jQuery架构设计与实现原理 / 高云著. -北京:机械工业出版社,2013.11 ISBN 978-7-111-44082-6 I. j- II. 高- III. JAVA语言-程序设计 IV. TP312 中国版本图书馆CIP数据核字(2013)第221662号 版权所有·侵权必究 封底无防伪标均为盗版 本书法律顾问 北京市展达律师事务所     本书由阿里巴

基于微服务和Docker容器技术的PaaS云平台架构设计

本文讲的是基于微服务和Docker容器技术的PaaS云平台架构设计[编者的话]在系统架构上,PaaS云平台主要分为微服务架构.Docker容器技术.DveOps三部分,这篇文章重点介绍微服务架构的实施. [3 天烧脑式容器存储网络训练营 | 深圳站]本次培训以容器存储和网络为主题,包括:Docker Plugin.Docker storage driver.Docker Volume Pulgin.Kubernetes Storage机制.容器网络实现原理和模型.Docker网络实现.网络插件.

spring context架构--静态结构

概念 Context也就是我们常说的spring容器,打个比方,context就像是一家公司,beans则是公司的工厂,除了工厂,公司还有翻译,仓库以及办公场所等等. 下面就看看context的主要构成部件. Context构成部件 上图是ApplicationContext的实体静态结构,它继承了六个实体.虽然是继承,但其实context和他们的关系更像是聚合.Spring使用继承主要是为了在context上也同时体现这6个实体的特征.在实现层面,context事实上是个包装类,最终通过聚合的

写自己的缓存框架,JAD-CACHE架构设计篇

在之前一篇<写一个自己的通用缓存框架,以同时支持ehcache.mecache以及springcache注解等等>博文中,列出了自己的通用缓存框架需要实现在的大致功能总结如下: 1.提供统一的缓存操作api: 2.支持同时使用多种缓存实现: 3.提供灵活的配置: 4.需要防止缓存穿透: 5.需要可以灵活指定缓存存活时间: 6.需要任意控制缓存的停用或启用. 目前这个框架的编码部分已完成,并取名为JAD-CACHE,取这个名字的原因是因为它是我的个人的JAD项目的一部分,JAD项目是本人用业余时

如何实现高容量大并发数据库服务 | 数据库分布式架构设计

袋鼠学院和优云.阿里云联合举办的沙龙结束之后,总是有小伙伴们来问PPT内容,想要进一步了解Topic内容.(哦,对了对了,竟然还有小伙伴专门冲着袋鼠云去听沙龙,感动cry~~) 千呼万唤,忙成狗的袋鼠小妹终于把沙龙总结整理了出来(⊙o⊙) 本次沙龙的主题是"云时代下的运维管理实践",受邀请的演讲嘉宾,花名宏翊(经常关注袋鼠云的同学,肯定已经对这个名字很熟悉了),是袋鼠云首席数据库架构师,袋鼠学院数据库讲师. 呼应沙龙运维实践的主题,结合自己的专长领域,宏翊主要是从数据库领域来谈云时代下

《企业迁云实战》——3.3 应用架构设计

3.3 应用架构设计 上面已经介绍了用户业务上云时如何进行网络设计.运维管理环境规划,本章将重点介绍如何基于阿里云产品和服务设计应用系统架构.3.3.1 负载均衡 阿里云负载均衡(Server Load Balancer,SLB)是将访问流量根据转发策略分发到后端多台ECS的流量分发控制服务.用户可以通过负载均衡的流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性. 阿里云负载均衡主要功能: 负载均衡服务通过设置虚拟服务地址(IP),将多台云服务器ECS实例虚拟成一个高性能

利用Docker轻松实现云原生应用 - 高可用架构设计

本文为利用Docker和容器服务轻松实现云原生应用系列的第一篇 高可用架构设计(本文) Spring Boot + Redis分布式会话 最近对应用迁云的讨论很多,很多用户对云环境中的应用架构和运维方式还不了解.直接利用云服务器替换自有物理机并不是使用云的正确姿势. Cloud Native Application(云原生应用)是当下一个热门名词,简单而言就是针对云计算的特性,来设计应用架构,并优化应用的交付.运维流程.Linux基金会旗下的云原生计算基金会 CNCF(Cloud Native