Spring源码分析
第四章 Spring框架的Ioc实现(一)文章目录
前言
上一章,我们通过一个简单的业务场景,实现使用Ioc和Aop思想对代码的优化重构。那么从这一章开始,我们就要详细了解一下spring中Ioc与Aop的源码及相关实现
一、基础回顾
(一) spring bean的三种配置方式
在学习spring Ioc之前,我们先来回顾一下上一章的内容。在上一章中,Ioc思想的主要体现在beans.xml和BeanFactory.java类当中。
beans.xml:定义需要实例化对象的类的全限定名以及类之间依赖关系的描述
BeanFactory:通过反射技术实例化对象,并维护对象之间的依赖关系,实际上就是ioc容器的一种实现
同样对于Spring而言,beans.xml同样重要,在了解beans.xml之前,先来了解下spring bean的三种配置方式:
1. 纯xml配置方式
容器启动方式:
(1)javaSE应用:
● ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
● new FileSystemXmlApplicationContext();
(2)javaWeb应用:
● ContextLoaderListener方式加载xml
2. xml+注解配置方式
容器启动方式:
(1)javaSE应用:
● ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
● new FileSystemXmlApplicationContext();
(2)javaWeb应用:
● ContextLoaderListener方式加载xml
3. 纯注解配置方式
容器启动方式:
(1)javaSE应用:
● ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
(2)javaWeb应用:
● ContextLoaderListener方式加载注解配置类
(二) BeanFactory与ApplicationContext区别
(1)BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,而ApplicationContext是它的⼀个子接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能的。
(2)我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的高级接⼝,比BeanFactory要拥有更多的功能,比如说国际化⽀持和资源访问(xml,java配置类)等等
(三)实例化Bean的三种方式
- 方式⼀:使⽤⽆参构造函数
<!--配置service对象-->
<bean id="userService" class="com.cym.service.impl.TransferServiceImpl">
</bean>
在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数,将创建失败。
- 方式二:使⽤静态方法创建
在实际开发中,我们使⽤的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创建的过程 中会做很多额外的操作。此时会提供⼀个创建对象的⽅法,恰好这个⽅法是static修饰的⽅法,即是此种情况。
例如,我们在做Jdbc操作时,会⽤到java.sql.Connection接⼝的实现类,如果是mysql数据库,那么⽤的就 是JDBC4Connection,但是我们不会去写 JDBC4Connection connection = newJDBC4Connection() ,因 为我们要注册驱动,还要提供URL和凭证信息,⽤ DriverManager.getConnection ⽅法来获取连接。
那么在实际开发中,尤其早期的项⽬没有使⽤Spring框架来管理对象的创建,但是在设计时使⽤了⼯⼚模式 解耦,那么当接⼊spring之后,⼯⼚类创建对象就具有和上述例⼦相同特征,即可采⽤此种⽅式配置。
<!--使⽤静态⽅法创建对象的配置⽅式-->
<bean id="userService" class="com.lagou.factory.BeanFactory"
factory-method="getTransferService"></bean>
- 方式三:使⽤实例化方法创建
此种⽅式和上⾯静态⽅法创建其实类似,区别是用于获取对象的⽅法不再是static修饰的了,而是类中的⼀个普通方法。此种方式比静态方法创建的使用几率要高⼀些。
在早期开发的项⽬中,工厂类中的方法有可能是静态的,也有可能是非静态方法,当是非静态方法时,即可采用下面的配置方式
<!--使⽤实例⽅法创建对象的配置⽅式-->
<bean id="beanFactory"
class="com.lagou.factory.instancemethod.BeanFactory">
</bean>
<bean id="transferService" factory-bean="beanFactory" factory method="getTransferService">
</bean>
(四)Bean的作用域
(1)
(spring默认) 当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
(2)
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>
或者
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
(3)
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
(4)
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
(5)
当一个bean的作用域为Global Session,表示在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。
global session作用域类似于标准的HTTP Session作用域,不过仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。
<bean id="user" class="com.foo.Preferences "scope="globalSession"/>
(五)Bean的生命周期
单例模式:singleton
● 对象出⽣:当创建容器时,对象就被创建了。
● 对象活着:只要容器在,对象⼀直活着。
● 对象死亡:当销毁容器时,对象就被销毁了。
● ⼀句话总结:单例模式的bean对象⽣命周期与容器相同。
多例模式:prototype
● 对象出⽣:当使⽤对象时,创建新的对象实例。
● 对象活着:只要对象在使⽤中,就⼀直活着。
● 对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。
● ⼀句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。(spring只创建对象,不管理对象)
(六) 特殊属性
1、factory-bean属性:用于指定创建当前bean对象的工厂bean的唯⼀标识。当指定了此属性之后,class属性失效。
2、 factory-method属性:范围指定创建当前bean对象的工厂方法,如配合factory-bean属性使⽤,则class属性失效。如配合class属性范围,则⽅法必须是static的。
3、 scope属性:⽤于指定bean对象的用范围。通常情况下就是singleton。当要⽤到多例模式时,可以配置为prototype。
4、 init-method属性:⽤于指定bean对象的初始化方,此方会在bean对象装配后调⽤。必须是⼀个无参方法。
5、 destory-method属性:用于指定bean对象的销毁方,此方会在bean对象销毁前执行。它只能为scope是singleton时起作⽤。
(七) DI 依赖注入的xml配置
依赖注⼊分类 按照注⼊的⽅式分类
● 构造函数注⼊:顾名思义,就是利⽤带参构造函数实现对类成员的数据赋值。
● set⽅法注⼊:它是通过类成员的set⽅法实现数据的注⼊。(使⽤最多的)
按照注⼊的数据类型分类
● 基本类型和String:注⼊的数据类型是基本类型或者是字符串类型的数据。
● 其他Bean类型: 三种注⼊的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器中的。那么针对当前Bean来说,就是其他Bean了。
● 复杂类型(集合类型):注⼊的数据类型是Aarry,List,Set,Map,Properties中的⼀种类型。
依赖注⼊的配置实现之构造函数注⼊顾名思义,就是利⽤构造函数实现对类成员的赋值。它的使⽤要求是,类中提供的构造函数参数个数必须和配置的参数个数⼀致,且数据类型匹配。同时需要注意的是,当没有⽆参构造时,则必须提供构造函数参数的注⼊,否则Spring框架会报错。
(八) lazy-init 延迟加载
ApplicationContext 容器的默认行为是在启动服务器时将所有singleton bean
提前进行实例化。提前实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singleton bean
。
<bean id="testBean" class="cn.lagou.LazyBean" />
该bean默认的设置为: <bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="false" />
lazy-init="false"
,⽴即加载,表示在spring启动时,立刻进行实例化。如果不想让⼀个singleton bean
在 ApplicationContext
在这里插入代码片实现初始化时被提前实例化,那么可以将bean
设置为延迟实例化。
<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="true" />
设置 lazy-init
为 true 的 bean
将不会在 ApplicationContext
启动时提前被实例化,而是第⼀次向容器通过 getBean
索取 bean
时实例化的。
如果⼀个设置了立即加载的 bean1
,引用了⼀个延迟加载的 bean2
,那么 bean1 在容器启动时被实例化,而 bean2
由于被 bean1
引用,所以也也实例化,这种情况也符合延时加载的 bean
在第⼀次调用时才被实例化的规则。
也可以在容器层次中通过在 元素上使⽤ default-lazy-init
"属性来控制延时初始化。如下面配置:
<beans default-lazy-init="true">
<!-- no beans will be eagerly pre-instantiated... -->
</beans>
如果⼀个 bean
的 scope
属性为 scope="pototype"
时,即使设置了 lazy-init="false"
,容器启动时也不会实例化bean
,而是调用 getBean
⽅法实例化的。
应⽤场景
(1)开启延迟加载⼀定程度提⾼容器启动和运转性能
(2)对于不常使用的 Bean设置延迟加载,这样偶尔使用的时候再加载,不必要从⼀开始该 Bean 就占用资源
(九) FactoryBean和BeanFactory
- BeanFactory
BeanFactory
,以Factory
结尾,表示它是一个工厂类(接口),它负责生产和管理bean
的一个工厂。在Spring
中,BeanFactory
是IOC
容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。BeanFactory
只是个接口,并不是IOC
容器的具体实现,但是Spring
容器给出了很多种实现,如DefaultListableBeanFactory
、XmlBeanFactory
、ApplicationContext
等。其中XmlBeanFactory
就是常用的一个,该实现将以XML
方式描述组成应用的对象及对象间的依赖关系。 - FactoryBean
一般情况下,Spring通过反射机制利用<bean>
的class
属性指定实现类实例化Bean
,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在<bean>
中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring
为此提供了一个org.springframework.bean.factory.FactoryBean
的工厂类接口,用户可以通过实现该接口定制实例化Bean
的逻辑。
以Bean
结尾,表示它是一个Bean
,不同于普通Bean
的是:它是实现了FactoryBean<T>
接口的Bean
,根据该Bean
的ID
从BeanFactory
中获取的实际上是FactoryBean
的getObject()
返回的对象,而不是FactoryBean
本身,如果要获取FactoryBean
对象,请在id
前面加一个&
符号来获取。
// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
@Nullable
// 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
T getObject() throws Exception;
@Nullable
// 返回FactoryBean创建的Bean类型
Class<?> getObjectType();
// 返回作⽤域是否单例
default boolean isSingleton() {
return true;
}
}
(十) 后置处理器
Spring
提供了两种后处理bean
的扩展接口,分别为 BeanPostProcessor
和BeanFactoryPostProcessor
,两者在使用上是有所区别的。
- BeanPostProcessor
BeanPostProcessors
在spring
中是一个非常重要的扩展接口,它使得我们可以在创建bean
实例的前后做一些自己的处理,在了解BeanPostProcessors
之前,我们先来看下bean
的生成过程:
- BeanFactoryPostProcessor
BeanFactoryPostProcessor
主要在bean实例化之前进行使用。实际上在这上图几步骤之前还有很多步骤,这就涉及到一个非常重要的对象beanDefinition
,关于beanDefinition
和BeanFactoryPostProcessor
,请参见Spring源码分析之 BeanDefinition详解(后续更新)