Jack Frost

Spring应用、原理以及粗读源码系列(一)–框架总述、以Bean为核心的机制(IoC容器初始化以及依赖注入)

总述:spring框架是如今J2EE开发最重要框架之一,为企业级应用提供一系列轻量级解决方案,比如:基于依赖注入的核心机制、基于AOP的声明式事务管理、与多种持久层技术整合、整合后端各种组件等等。贯穿了表现层、业务层、持久层,实现无缝整合。

文章结构:(1) 框架总述;(2)以Bean为核心的IOC/DI机制;


一、框架总述:

这里写图片描述

图选自疯狂Java讲义。文字参考:此博主此文章。本博主在此摘抄并补充下

(1)Core Container(核心容器):

包含有Core,Beans,Context,Expression Language模块。是框架的基础部分,提供IOC(控制反转)和依赖注入特性。这里的基础概念 是BeanFactory,它提供Factory模式的经典实现来消除对程序单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置。

  • Core模块主要包含Spring框架基本的核心工具类,Spring的其他组件都要使用到这个包里的类,Core模块是其他组件的基本核心。当然你也可以在自己的应用系统中使用这些工具类。
  • Beans模块是所有应用都要用到的,它包含访问配置文件,创建和管理bean以及进行Inversion of Control/Dependency Injection(依赖注入)操作相关的所有类。
  • Context模块构建于Core和Beans模块基础之上,提供了一种类似于JNDI注册器的框架式的对象访问方法。Context模块集成了Beans的特性,为Spring核心提供了大量的扩展,添加了对国际化(例如资源绑定),事件传播,资源加载和对Context的透明创建的支持。Context模块同时也支持J2EE的一些特性,例如EJB(java企业Bean),JMX(Java Management Extensions,即Java管理扩展是一个为应用程序、设备、系统等植入管理功能的框架)和基础的远程处理。ApplicationContext接口是Context模块的关键。
  • Expression Language 模块提供了一个强大的表达式语言用于在运行时查询和操纵对象。它是JSP2.1规范中定义的unifed expression language的一个扩展。该语言支持设置/获取属性的值,属性的分配,方法的调用,访问数组上下文(accession the context of arrays),容器和索引器,逻辑和算数运算符,命名变量以及从Spring的IOC容器中根据名称检索对象。它也支持list投影,选择和一般的list聚合。

    (2)WEB层:

    Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。所以Spring框架支持与Jakarta Struts的集成。Web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。Web层包含了Web,Web-Servlet,Web-Struts和Web-Porlet模块,具体说明如下。

  • Web-Servlet模块web.servlet.jar:该模块包含Spring的model-view-controller(MVC)的实现。Spring的MVC框架使得模型范围内的代码和web forms之间能够清楚地分离开来,并与Spring框架的其他特性集成在一起。
  • Web模块:提供了基础的面向Web的集成特性。例如,多文件上传,使用servlet listeners初始化IOC容器以及一个面向Web的应用上下文。它还包含Spring远程支持中Web的相关部分。
  • Web-Porlet模块:提供了用于Portlet环境和Web-Servlet模块的MVC的实现。
  • spring4.0以后加入了对websocket技术的支持.目的是浏览器与服务端建立全双工的通信方式,解决http请求-响应带来过多的资源消耗,同时对特殊场景应用提供了全新的实现方式,比如聊天、股票交易、游戏等对对实时性要求较高的行业领域。

    (3)Data Access/Integration.持久层:

    Data Access/Integration层包含有JDBC,ORM,OXM,JMS和Transaction模块,其中:

  • JDBC模块提供了一个JDBC抽象层,它可以消除冗长的JDBC编码和解析数据库厂商特有的错误代码。这个模块包含了Spring对JDBC数据访问进行封装的所有类。
  • ORM(Object Relational Mapping对象关系映射)模块为流行的对象-关系映射API,如JPA,JDO,Hibernate,iBatis等,提供了一个交互层。利用ORM封装包,可以混合使用所有Spring提供的特性进行O/R映射。如前边提到的简单声明性事务管理。

    Spring框架插入了若干个ORM框架,从而提供了ORM的对象关系工具,其中包括JDO,hibernate和MyBatis。所有这些都遵从Spring的通用事务和DAO异常层次结构。

  • OXM模块提供了一个对Object/XML映射实现的抽象层,Object/XML映射实现包括JAXB(JAXB能够使用Jackson对JAXB注解的支持实现(jackson-module-jaxb-annotations),既方便生成XML,也方便生成JSON,这样一来可以更好的标志可以转换为JSON对象的JAVA类。
  • JMS(Java Messaging Service)模块主要包含了一些制造和消费消息的特性。
  • Transaction模块支持编程和声明性的事务管理,这些事务类必须实现特定的接口,并且对所有的POJO(实际就是普通JavaBeans)都适用。

    (4)切面层:

    1. AOP模块提供了一个符合AOP联盟标准的面向切面编程的实现,它让你可以定义例如方法拦截器和切点。从而将逻辑代码分开,降低它们之间的耦合性。利用source-level的元数据功能,还可以将各种行为信息合并到你的代码中,这有点像.Net技术中的attribute概念。

    通过配置管理特性,SpringAOP模块直接将面向切面的编程功能集成到了Spring框架中,所以可以很容易地使Spring框架管理的任何对象支持AOP。Spring AOP模块为基于Spring的应用程序中的对象提供了事务管理服务。通过使用Spring AOP,不用依赖EJB组件,就可以将声明性事务管理集成到应用程序中。

    2. Aspects模块提供了对AspectJ(一个面向切面的框架,它扩展了Java语言)的集成支持。

    3.Instrumentation模块提供了class instrumentation 支持和classloader实现,使得可以在特定的应用服务器上使用。

    (5)Test层:

    此层支持使用JUnit和TestNG对Spring组件进行测试。


二、以Bean为核心的IOC/DI机制:

总述理论核心:在spring中,所有的对象都会被spring核心容器管理。一切对象统称为Bean。

Spring容器可通过XML配置文件或者注解去管理这堆Bean。

(1)定义以及原理:

依赖注入(IOC/DI):spring容器负责将被依赖对象赋值给调用者的成员变量–相当于为调用者注入它依赖的实例。

依赖关系:A对象需要调用B对象方法的情况–A依赖B。

原理:

1. 原始做法:调用者主动创建被依赖对象,然后再调用被依赖对象的方法。

调用者需要通过像“new 对象”的操作去创建对象。

缺点:必然导致调用者与被依赖对象实现类的硬编码耦合。(因为我不想知道你的创建过程、也不想主动去创建你)

2. 简单工厂模式:调用者先找到被依赖对象的工厂,然后主动通过工厂去获取被依赖对象,最后再调用被依赖对象的方法。

调用者面向被依赖对象的接口编程;将被依赖对象的创建交给工厂;调用者通过工厂来获得被依赖组件。

缺点:调用组件需要主动通过工厂去获取被依赖对象,必然带来调用组件与被依赖对象工厂的耦合。(同样,我也不想知道你这个工厂存在,我只想要个依赖对象)

3.Spring框架下的依赖注入:框架为调用者注入它依赖的实例。

优点:程序无须例会被依赖对象的实现,也无须主动定位工厂。只需被动等待IOC容器分配依赖对象。

(2)应用:

依赖注入分为两种:[一]设值注入;[二]构造注入;

1. 设值注入:指IOC容器通过成员遍历的setter方法来注入被依赖对象。

使用方法:可见我们没有硬编码上去创建依赖对象

写个test类

2. 构造注入:IOC容器使用构造器来注入被依赖对象。

在构造实例时,已经为其完成了依赖关系的初始化。本质就是当执行带参数的构造器时,就可利用构造器参数对成员变量执行初始化。《contructopr-arg… /》

使用方法:

3. 注解注入装配: 以后再讲,其实是基于前两种注解方式实现的。

(3)IOC/DI依赖注入源码理解:(基于spring4.1.7)

重点理解两个过程:IoC容器初始化过程以及IoC容器依赖注入过程。

其实就是:资源定位、资源装载、资源解析、Bean生成,Bean注册、Bean依赖注入这几个过程(前五属于初始化过程)。方法:eclipse断点ClassPathXmlApplicationContext调用,不断跳进去,就看到spring执行栈了。

1. IoC容器初始化过程:

这里写图片描述

以上图思路进行详细讲述

初始化总过程:资源定位(确定工厂创建和bean的配置文件)—>资源装载(在文件系统路径上对IOC配置文件、bean配置进行加载)—>资源解析(解析bean配置,解析xml元素)—>生成bean(根据DOM资源解析成bean)—>在IoC容器中注册(交给IoC容器管理,还有依赖注入的权限)

这里写图片描述

整体过程大致如上图(从下往上看的栈,):保存配置,并刷新工厂–>创建载入BeanFactory–>创建XmlBeanDefinitionReader–>创建处理每一个Resource–>转换成Document对象–>处理XML每个元素–>解析注册bean


[一]资源定位:

追踪:我们可在此句获取工厂前设断点然后debug追踪进去,查看IoC的资源定位

然后一路跳进去:

根据资源定位核心调用层,我们可以看到他必执行AbstractApplicationContext中的refresh方法,执行容器刷新。部分叙述参考此博主此文章

AbstractRefreshableApplicationContext实现的refreshBeanFactory

总的来说:资源定位做的就是:确定工厂位置,执行工厂初始化并刷新,建立好bean资源加载路径。等待bean资源装载。


[二]资源装载

设置工厂配置,刷新容器后,还要把我们的bean配置给资源加载器。

目标:拿到资源加载器,可以classpath:那样直接拿到资源装载。

在这之前,很有必要大家一起区分:不同类中的loadBeanDefinitions解析职责。

在AbstractXmlApplicationContext类中,职责为:对applicationContext.xml的解析操作,就是解析工厂的那个xml

在AbstractBeanDefinitionReader类中,职责为:从指定的资源加载bean定义,真正实现在其子类,这里是做了些兼容性错误处理。

在XmlBeanDefinitionReader类中,是AbstractBeanDefinitionReader的子类,而且是一个真正的实现类 ,是实现BeanDefinitionReader接口的loadBeanDefinitions(Resource var1) 等方法的关键解析类。职责为:读取并真正解析 xml 文件。

AbstractRefreshableApplicationContext中只定义了抽象的loadBeanDefinitions方法,容器真正调用的是其子类AbstractXmlApplicationContext对该方法的实现。(全局搜索SHIFT)

由上面代码得知进入AbstractXmlApplicationContext的loadBeanDefinitions(XmlBeanDefinitionReader参数)

接着根据上面继续追踪,到了AbstractBeanDefinitionReader类的loadBeanDefinitions方法

所以,AbstractBeanDefinitionReader的loadBeanDefinitions方法源码分析可以看出该方法做了以下两件事:

(1)首先,调用资源加载器的获取资源方法resourceLoader.getResource(location),获取到要加载的资源。

(2)其次,真正执行加载功能是其子类XmlBeanDefinitionReader实现的loadBeanDefinitions方法。

另外,此时调用的是DefaultResourceLoader中的getSource()方法定位Resource。

然后我们再仔细看下各个类想拿到资源加载器就是通过getResourceLoader,拿到AbstractBeanDefinitionReader类定义的resourceLoader。这样的话,我们可通过此方式在工程spring下的任何地方拿到资源加载器,“随处加载”了。

总的来说,资源装载就是:根据之前确定好的bean资源配置路径,拿到资源、拿到加载器,并把bean配置丢进XmlBeanDefinitionReader。等待Bean资源解析。


[三]Bean资源解析:其实就是刚我们遇到的XmlBeanDefinitionReader类啦。真正去解析xml。解析的关注重心请注意到doLoadBeanDefinitions方法,从这里进行分发。从而到下一部生成bean对象。

目标:将XML文件转为DOM对象。进而交DocumentLoader和DocumentBuilderFactory处理dom对象给doLoadBeanDefinitions方法,从而为bean生成作铺垫

然后沿着doLoadDocument继续追踪,追踪到一个接口DocumentLoader和一个实现类DefaultDocumentLoader。以下是DefaultDocumentLoader这个实现类的部分代码

总的来说:Bean资源解析就是,先通过 XML解析器讲Bean定义资源文件转换得到Document对象,但是这堆Document对象没有按照spring的Bean规则的,所以不可直接转换成bean对象。然后完成XML解析变成Document对象后,就调用spring的解析方法按照Spring的Bean规则去对Document进行解析,生成Bean对象。


[四]生成Bean:还是关注XmlBeanDefinitionReader类的doLoadBeanDefinitions方法,他进行了一个调用:registerBeanDefinitions。

接下来就是document元素的一个个解析,然后转化成bean对象了。我们根据上面的解析入口可以追踪到 BeanDefinitionDocumentReader接口的实现类DefaultBeanDefinitionDocumentReader。在这个类进行详细的document元素解析成我们平常工程用的bean。但本博主不打算继续看了,因为对我们掌握spring的IOC原理并不影响。

[五]在IoC容器注册解析生成的Bean

目标:到IoC容器注册

我们能猜到他在类DefaultBeanDefinitionDocumentReader生成bean后必然会丢给IoC容器去注册,交给它管理。但是我们怎么找到呢??根据上面生成bean中的解析入口,我们CTRL+F查registerBeanDefinitions。

再进一步追踪到parseDefaultElement方法,可以看到这里就是识别主标签,从而进行解析生成bean。

终于找到非抽象实现类啦:DefaultListableBeanFactory(其实我当时查的快疯,是查他的继承树才查到这个的。)

使用一个HashMap的集合对象存放IoC容器中注册解析的BeanDefinition

总的来说就是:把beandefinition丢给工厂用hashmap存好。

IOC容器初始化总结

(1)通过setConfigLocations载入spring配置文件;

(2)初始化容器入口通过refresh方法,进入AbstractApplicationContext实现的refresh方法。

(3)然后通过obtainFreshBeanFactory方法进入子类AbstractRefreshableApplicationContext实现的refreshBeanFactory刷新一个容器工厂

(4)在此创建了DefaultListableBeanFactory类,并调用loadBeanDefinitions(beanFactory)装载bean定义

(5)接着以AbstractRefreshableApplicationContext为中心回到此类,进入其子类AbstractXmlApplicationContext实现的loadBeanDefinitions方法。对applicationContext.xml的解析操作,就是解析工厂的那个xml。

(6)再接着通过AbstractXmlApplicationContext的loadBeanDefinitions进入到AbstractBeanDefinitionReader类的loadBeanDefinitions。通过获取资源方法resourceLoader.getResource(location),获取到要加载的资源。再真正执行加载功能是其子类XmlBeanDefinitionReader实现的loadBeanDefinitions方法。

(6)接着进入XmlBeanDefinitionReader中的loadBeanDefinitions。(XmlBeanDefinitionReader通过调用其父类中调用的DefaultResourceLoader的getResource方法获取要加载的资源)DocumentLoader将Bean定义资源转换成Document对象。

(7)doLoadBeanDefinitions中进入DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions 解 析 D ocument对象

(8)解析完后,调用DefaultListableBeanFactory类中使用一个HashMap的集合对象存放IoC容器中注册解析的BeanDefinition


2.IoC容器依赖注入过程:

[ 一 ]先来读懂beanfactory的继承逻辑树

类图。我们根据DefaultListableBeanFactory继续追踪上去。一会详讲此逻辑线。参考此博主此文章,非常感谢他,写得太棒了。让我学到很多。

这里写图片描述

先来读懂它beanfactory的继承树逻辑线。图取自此文章

这里写图片描述

解析上图:

BeanFactory是Spring的最根的接口,类的工厂接口。HierarchicalBeanFactory接口是在继承BeanFactory的基础上,实现BeanFactory的父子关系。Hierarchical表示的是这些 Bean 是有继承关系的,也就是每个 Bean 有可能有父 Bean。

AutowireCapableBeanFactory接口是在继承BeanFactory的基础上,实现Bean的自动装配功能。定义 Bean 的自动装配规则。

ListableBeanFactory接口是在继承BeanFactory的基础上,实现Bean的list集合操作功能。表示这些 Bean 是可列表的。

ConfigurableBeanFactory接口是在继承HierarchicalBeanFactory的基础上,实现BeanFactory的全部配置管理功能, SingletonBeanRegistry是单例bean的注册接口

ConfigurableListableBeanFactory接口是继承AutowireCapableBeanFactory,ListableBeanFactory,ConfigurableBeanFactory三个接口的一个综合接口。

AliasRegistry接口是别名注册接口,SimpleAliasRegistry类是简单的实现别名注册接口的类。

DefaultSingletonBeanRegistry是默认的实现SingletonBeanRegistry接口的类,同时,继承类SimpleAliasRegistry 。

FactoryBeanRegistrySupport是实现FactoryBean注册的功能实现。继承类DefaultSingletonBeanRegistry 。负责FactoryBean相关的操作,并缓存FactoryBean的getObject实例化的bean. 判断factory是单例,同时已经new好了单例时,先尝试去缓存找;如果找不到或者不是单例,委托doGetObjectFromFactoryBean实例化一个。

AbstractBeanFactory是部分实现接口ConfigurableBeanFactory,并继承类FactoryBeanRegistrySupport 。这个是最顶层的抽象IOC容器空构造器,主要用来具体实现了BeanFactory接口

AbstractAutowireCapableBeanFactory是实现接口AutowireCapableBeanFactory,并继承类 AbstractBeanFactory 。主要的功能就是实现了默认的bean创建方法createBean().而在这个创建过程中,提供了诸如bean的属性注入,初始化方法的调用,自动装配的实现,bean处理器的调用。

DefaultListableBeanFactory实现接口 ConfigurableListableBeanFactory、BeanDefinitionRegistry(bean定义的注册接口), 并继承AbstractAutowireCapableBeanFactory,实现全部类管理的功能。

可以看出DefaultListableBeanFactory就是springIoC机制的入口。


[二]IoC容器依赖注入源码详讲:上面可知我们从实现BeanFactory的AbstractBeanFactory入手

1.我们从DefaultListableBeanFactory开始不断追踪父类,直到找到了AbstractBeanFactory。

我们可以很清晰看到,创建bean的时候所做的判断:

(1)如果Bean定义的单态模式(Singleton),则容器在创建之前先从缓存中查找,以确保整个容器中只存在一个实例对象

(2) 如果Bean定义的是原型模式(Prototype),则容器每次都会创建一个新的实例对象。

(3)两者都不是,则根据Bean定义资源中配置的生命周期范围,选择实例化Bean的合适方法,这种在Web应用程序中 比较常用,如:request、session、application等生命周期。

2.因此我们根据上面所述继续追踪,现追踪AbstractBeanFactory的一个匿名内部类的createBean方法,实际上这方法的实现就是在我们从DefaultListableBeanFactory追踪父类过来的途中遇到的AbstractAutowireCapableBeanFactory类,是AbstractBeanFactory子类。

上面还是一堆校验以及状态判断。

其中真正实现是分发到:

createBeanInstance:生成Bean所包含的Java对象实例。
populateBean :对Bean属性的依赖注入进行处理。

3.那么我们来看下createBeanInstance方法的代码,还是在AbstractAutowireCapableBeanFactory类中。

在此方法中,根据指定的初始化策略,使用静态工厂、工厂方法或者容器的自动装配特性生成java实例对象。

上面看到一个无参构造器:instantiateBean

4.我们都看到了,无论什么调用什么构造器,返回的都是统一的BeanWrapper

BeanWrapper是什么贵?org.springframework.beans.BeanWrapper是Spring框架中重要的组件类。BeanWrapper相当于一个代理器,Spring通过BeanWrapper完成Bean属性的填充工作。在Bean实例被InstantiationStrategy创建出来之后,容器主控程序将Bean实例通过BeanWrapper包装起来。

由于它是接口,必然有个实现类,实现依赖注入的具体实现。那就是BeanWrapperImpl,它的作用是:(1)Bean包裹器;(2)属性访问器;(3)属性编辑器注册表。

5.我们接着回到createBean,它还有个实现分发populateBean 方法:对Bean属性的依赖注入进行处理

过程是两部分:属性值解析和注入

6.那么我们继续追踪applyPropertyValues方法

总结applyPropertyValues方法(完成属性转换):

属性值类型不需要转换时,不需要解析属性值,直接准备进行依赖注入。

属性值需要进行类型转换时,如对其他对象的引用等,首先需要解析属性值,然后对解析后的属性值进行依赖注入。

而且我们看到调用了resolveValueIfNecessary方法对属性值的解析

7.追踪resolveValueIfNecessary,发现是在BeanDefinitionValueResolver类

可知:创建与注入是个递归的过程

刚也说了解析引用类型是特殊的:resolveReference方法

8.并且我们注意到applyPropertyValues方法(刚刚的AbstractAutowireCapableBeanFactory类)中,分发了一个方法实现对属性值的依赖注入setPropertyValues

前面也说到BeanWrapper接口的实现类就是BeanWrapperImpl。一系列的依赖注入都在这个里面,那么我们往上查找PropertyAccessor接口,并且发现路过的抽象类AbstractPropertyAccessor,终于找到了setPropertyValues方法的实现,有个模板模式,就是调用在AbstractPropertyAccessor中的抽象方法setPropertyValue(此方法在BeanWrapperImpl实现了!!)

很复杂的一个解析方法:将属性的值注入到Bean实例对象中情况如下:

1. 对于集合类型的属性,将其属性值解析为目标类型的集合后直接赋值给属性

2. 对于非集合类型的属性,大量使用了JDK的反射和内省机制,通过属性的getter方法(reader method)获取指定属性注入以前的值,同时调用属性的setter方法(writer method)为属性设置注入后的值。


依赖注入总结:

(一)DefaultListableBeanFactory父类AbstractBeanFactory根据Bean的状态定义(单例、存在、原型)进行构造方法分发。分发到AbstractAutowireCapableBeanFactory类的createBean方法

(二)createBean方法负责Bean实例要求状态判断以及再度分发到doCreateBean实现创建实例(再次配置判断)。并在doCreateBean再度分发去createBeanInstance去Bean所包含的Java对象实例以及去populateBean 方法对Bean属性的依赖注入进行处理(以此为责任分发中心)。

(三)在createBeanInstance方法中真正地根据之前的配置判断设置选择真正合适的构造器(自动装配、无参构造器);

(四)在populateBean 方法中真正地将Bean属性设置到生成的实例对象上 ,但在过程中注入依赖属性的是在applyPropertyValues方法(完成属性转换),调用BeanDefinitionValueResolver类调用resolveValueIfNecessary方法对属性值的解析,属性的真正注入实现在BeanWrapperImpl类。


好了,Spring应用、原理以及粗读源码系列(一)–框架总述、以Bean为核心的机制(IoC容器初始化以及依赖注入)讲完了,这个系列以应用为基本点,结合源码讲解那堆应用的机制原理,我会加快脚步学习,分享经验给大家,希望大家喜欢。欢迎在下面指出错误,共同学习!!你的点赞是对我最好的支持!!

更多内容,可以访问JackFrost的博客

码字很辛苦,转载请注明来自JackFrost《Spring应用、原理以及粗读源码系列(一)–框架总述、以Bean为核心的机制(IoC容器初始化以及依赖注入)》

Leave a Reply

Your email address will not be published. Required fields are marked *