本文中讲解IOC容器时,大多时候采用XML方式讲解;可能会让人感觉别扭,毕竟现在是注解为王的时候。但是注解的背后就是依靠它们的原理来实现的。只有更好理解注解背后的运作流程,才能更好的使用。
特点:默认采用延迟加载策略,只有当客户端访问容器中某个对象时才会初始化对象及进行依赖注入操作。
BeanFactory接口中主要相关方法:
getBean(String name): Spring容器中获取对应Bean对象的方法,如存在,则返回该对象
containsBean(String name):Spring容器中是否存在该对象
isSingleton(String name):通过beanName是否为单例对象
isPrototype(String name):判断bean对象是否为多例对象
isTypeMatch(String name, ResolvableType typeToMatch):判断name值获取出来的bean与typeToMath是否匹配
getType(String name):获取Bean的Class类型
getAliases(String name):获取name所对应的所有的别名
BeanFactory只是个接口,并不是IOC容器的具体实现,Spring中给出了很多种实现,如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等。spring中默认采用DefaultListableBeanFactory,其中XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。XmlBeanFactory类将持有此XML配置元数据,并用它来构建一个完全可配置的系统或应用。
特点:ApplicationContext本身是一个资源加载器,ApplicationContext由BeanFactory派生而来,默认采取及时加载,即容器启动完成就全部初始化并绑定完成。ApplicationContext容器构建于BeanFactory之上,更加的简化了开发者对容器的访问。
在spring的IOC容器中只存在一个实例,所有对该对象的引用将共享这个实例。该实例从容器启动,并因为第一次请求而初始化之后,将一直存活到容器退出。同时,其生命周期同统一受Spring容器的管理。若不指定bean的作用域,singletion便是容器默认的。
注:标记为singleton的bean是由容器来保证这种类型的bean在同一个容器中只存在一个共享实例;而设计模式中的单列模式则是保证在同一个Classloader中只存在一个这种类型的实例。
若bean的作用域定义为prototype,容器在接到该类型对象的请求的时候,会每次都重新生成一个新的对象实例给请求方。 并且对象实例返回给请求方之后,容器就不再拥有当前返回对象的引用,请求方需要自己负责当前返回对象的后续生命周期的管理。
若bean的作用域为request,每次HTTP请求调用对象时,容器都会创建一个新的bean对象请求处理完毕之后就会销毁这个bean对象。
若bean的作用域为session,session中所有的HTTP请求共享一个bean对象,当session结束后才会销毁这个bean对象。
global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。
小结:
类型 | |
---|---|
singleton | 在Sping容器中仅存在一个Bean实例 |
prototype | 每次容器中请求Bean实例时,都会返回一个新实例 |
request | 每次HTTP请求都会创建一个新的Bean。仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session共享一个Bean。仅适用于WebApplicationContext环境 |
globalSession | 同一个全局Session共享一个Bean,一般用于Portlet应用环境。仅适用于WebApplicationContext环境 |
Spring的IOC容器装配Bean时,基本上可以按照流程划分成容器启动阶段和Bean实例化阶段。
容器启动阶段 | Bean实例化阶段 |
---|---|
加载配置… | 实例化对象… |
分析配置信息… | 装配依赖… |
装配到BeanDefinition… | 生命周期回调… |
其他启动后处理… | 对象其他处理… |
注册回调接口… |
Spring容器在初始时常用的一些辅助类
在分析bean在spring的容器中从加载到可使用的准备状态前,需要了解一些spring在加载bean过程中使用到的一些组件类;IOC容器就是在他们的帮助下完成bean的装载。
BeanDefinition
org.springframework.beans.factory.config.BeanDefinition是XML配置文件标签元素在容器中的内部表示。元素标签具有的class、scope、lazy-init等属性,容器中的BeanDefinition提供了相应的属性。
Spring使用BeanDefinition将配置文件中的配置信息转换为容器的内部表示,并将这些BeanDefinition注册到BeanDefinitionResgistry中。一般情况下,BeanDefinition只在容器启动时加载并解析,除非容器刷新或重启,这些信息不会发生变化。
InstantiationStrategy
org.springframework.beans.factory.support.InstantiationStratery类,负责根据容器中加载的BeanDefinition对象创建一个Bean实例。并且Spring在实例化时,采用策略模式进行;但是,InstantiationStrategy仅负责实例化Bean的操作,相当于java中的new的功能,它并不会参与Bean属性的赋值。
BeanWrapper
org.springframework.beans.BeanWrapper类是Spring中的重要组件类,Spring委托BeanWrapper完成Bean的属性赋值功能。在容器通过InstantiationStratery类将Bean实例化后,容器将调用该类将bean包装起来,进行赋值操作。
首先依赖某些工具类(BeanDefinitionReader)对加载的资源进行解析和分析,并将分析后的信息组织为一个个的BeanDefinition对象,最后把这些保存了Bean定义必要信息的BeanDefinition,注册到响应的BeanDefinitionRegistry(Bean定义注册表),这样容器的启动工作就完成了。
这一阶段所做的工作更倾向于信息的收集,为下一个实例化对象做准备。
介入容器的启动阶段
Spring为我们提供了叫做BeanFactoryPostProcessor(Bean工厂后处理器)的容器扩展机制,该机制允许我们在容器实例化对象之前,对注册到容器的BeanDefinition对象进行修改;如修改Bean定义的某些属性,为Bean增加其他信息等。实现Spring中的org.springframework.beans.factory.config.BeanPostProcessor接口即可实现自定义的BeanFactoryPostProcessor。同时,同一个容器中可以存在多个BeanPostProcessor,这时需要实现Spring的Ordered接口来保证各个接口按照设定的顺序执行。
Spring已提供了几个现成的BeanPostProcessor,其中PropertyPlaceholderConfigurer、PropertyOverrideConfigurer是两个比较常用的,主要用于处理定义的bean属性与配置文件中属性项的映射。
PropertyPlaceholderConfigurer
如下面所示,PropertyPlaceholderConfigurer类可以帮助从properties文件中获取bean中使用占位符形式的属性的值。
db.druid.driverClassName=com.mysql.jdbc.Driver
db.druid.url: jdbc:mysql://127.0.0.1:3306/Db
db.druid.username: username
db.druid.password: password
PropertyPlaceholderConfigurer类可以帮助我们从properties文件中获取bean中使用占位符形式的属性的值。
过程说明:
当BeanFactory在第一阶段加载完成所有配置信息时,BeanFactory中保存的对象属性信息还只是以占位符的形式存在,如${db.druid.url}等信息。当PropertyPlaceholderConfigurer类作为BeanPostProcessor被应用时,它会使用配置文件中的信息来替换响应的Beanfinition中占位符表示的属性值。这样,当第二阶段实例化Bean时,bean定义中的属性值就是最终替换后的值。
同时,PropertyPlaceholderConfigurer不单会从其配置的properties文件中加载配置项,同时还会检查Java的System类中的Properties,可 以通过setSystemProperti esMode()或者setSystemPropertiesModeName ()来控制是否加载或者覆盖System相应Properties的行为。PropertyPlaceholderConfigurer提供了SYSTEM PROPERTIES_ MODE FALLBACK、SYSTEM PROPERTIES MODE_ NEVER和SYSTEM_PROPERTIES_ MODE_ OVERRIDE三种模式。默认采用的是SYSTEM PROPERTIES MODE FALLBACK, 即如果properties文件中找不到相应配置项,则到System的Properties中查找,我们还可以选择不检查system的Properties或者覆盖它。更多信息请参照PropertyPlaceholderConfigurer的Javadoc文档。
PropertyOverrideConfigurer
可以通过PropertyOverrideConfigurer对容器中配置的任何你想处理bean定义的property信息进行覆盖替换。它的properties配置文件中的配置项,覆盖掉原来XML中bean定义的property信息。另外,properties文件中的键时以Bean定义的beanName为标志开始的,即id指定的值。
属性编辑器
通常我们在Spring中定义的属性大多都是字符串,但是由字符串转换到具体类型,如Date,Array等类型。要想完成这种由字符串到具体对象的转换,都需要转换规则。Spring内部通过JavaBean的PropertyEditor来帮助String类型到其他类型的转换工作。只要为每种对象类型提供一个PropertyEditor,Spring容量内部在做具体的类型转换的时候会采用相应的PropertyEditor。
Spring内部已经为我们封装了大多数属性编辑器,大部分位于org.springFramework.beans.propertyeditors包下面。
另外一个编辑器类PropertyEditorRegistrySupport中用于保存属性编辑器的Map类型变量。
defaultEditors:用于保存默认属性类型的编辑器,元素的键为属性类型,值为对应的属性编辑器是实例。
customEditors:用于保存用户自定义的属性编辑器,元素的键值和defaultEditors相同。
当我们特别需要时,可以根据自定义的编辑器的方式来实现,将自定义方式映射到具体对象值。
通过此方法我们可以自定义配置文件值的类型,不再拘泥于字符串形式。
具体方法:
我们可以直接自定义类并让其实现类去实现java. beans . PropertyBditor接口,不过,通常情况下,我们可以直接继承java.beans .PropertyEditorSupport类以避免实现java. beans . PropertyEditor接口的所有方法。我们只需要重写setAsText (string)方法,而其他方法一概不管。
实现自定义的属性编辑器后,我们紧接着需要向容器中注册它,通知Spring使用它。
通常我们使用时,只需要在配置文件中通过CustomEditorConfigurer注册即可。
CustomEditorConfigurer实现了BeanPostProcessor接口,会被Spring自动扫描到容器中。
在①处定义了用于注册自定义属性编辑器的CustomEditorConfigurer, Spring 容器将通过反射机制自动调用这个Bean. CustomEditorConfigurer通过一 个Map属性定义需要自动注册的自定义属性编辑器。
在②处为Car类型指定了对应的属性编辑器CustomCarEditor, 其中键是属性类型,值是属性编辑器的类名。
最精彩的当然是③处的配置。原来通过一个元素标签配置好在Boss的中通过ref 引用car Bean,而现在直接通过value为car属性提供配置。BeanWrapper在设置Boss的car 属性时,将检索自定义属性编辑器的注册表,当发现Car属性类型拥有对应的属性编辑CustomCarEditor时,就会利用CustomCarEditor将“红旗CA72,20020000.00”转换为Car对象。
在经过第一阶段容器的启动后,容器中仅仅拥有对象的BeanDefinition来保存实例化阶段必要的信息。只有当请求方通过BeanFactory的getBean()方法来请求某个对象实例的时候,才有可能触发Bean实例化阶段的活动。
BeanFactory中,对象实例化默认采用延迟初始化。通常情况下,当对象被调用时,容器内部才会初始化对象。
ApplicationContext中,容器启动后就会实例化所有bean对象。这就是为什么我们能够直接使用ApplicationContext类的原因。具体可以查看AbstractApplicationContext的refresh()方法。
实例化与BeanWrapper
容器内部采用“策略模式”来决定采用何种方式初始化bean实例。通常,都使用CGLIB动态字节码生成相应的bean实例或者动态生成子类。容器只需要根据相应bean定义的BeanDefintion取得实例化信息,并结合容器的生成策略,就可以返回实例化完成的对象实例。但是,容器返回的并不是直接构造的对象实例,而是以BeanWrapper对构造对象实例进行包裹,其实现类会完成对bean属性设置的工作。
由上图中可以看出BeanWrapper的继承关系,通常容器内部启动完成后返回的就是BeanWrapperImpl对象包裹的bean对象。同时BeanWrapper间接或者直接继承了PropertyEditorRegistry、TypeConverter接口。这样使得BeanWrapper得到了一份PropertyEditor编辑器和TypeConverter类型转换器的内容,为下面bean的赋值做准备。
所以BeanWrapper有三重身份:
BeanWrapper利用得到的属性编辑器和容器启动阶段的BeanDefinition对象就能顺利的对bean进行属性的设置。
到此一个属性完整的被BeanWrapper包裹的bean对象就完成了,但此时距离变成可使用的bean对象还有很大距离。下图是个bean实例化流程图
多样的Aware接口
当Bean完成属性设置后,Spring容器会检查当前实例对象是否实现了一系列以Aware命名结尾的接口定义。如果有,则将这些接口定义中规定的依赖注入给当前对象实例。
BeanFactory有如下常见的接口:
org.springframework.beans.factory.BeanNameAware。将对象实例的bean定义的beanName设置到当前对象实例。
org.springframework.beans.factory.BeanClassLoadAware。将加载当前bean对象的Classloader注入当前对象实例。
org.springframework.beans.factory.BeanFactoryAware。将BeanFactory容器自身设置到当前对象。
上述接口只是针对BeanFactory类型的容器而言,对于我们经常使用的ApplicationContext类型容器也存在几个相关的接口。但是,ApplicationContext类型的容器是依赖于BeanFactory容器的,所有两者之间的接口检测相邻,方式不同。
ApplicationContext有如下常见的接口:
org.springframework.context.ResourceLoaderAware。将ApplicationContext自身设置到对象实例。
org.springframework.context.ApplicationEventPublisherAware。将ApplicationContext自身设置到对象实例。
BeanPostProcessor(Bean后处理器)
BeanPostProcessor概念易与第一阶段的BeanFactoryPostProcessor概念混淆。
BeanFactoryPostProcessor | BeanPostProcessor |
---|---|
容器启动阶段 | bean对象实例化阶段 |
BeanPostProcessor会扫描处理容器中所有符合实例化后的对象实例。
public interface BeanPostProcessor {
//bean初始化方法调用前被调用
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
//bean初始化方法调用后被调用
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
该接口提供了两个方法给我们在扩展容器时使用。实际,上面的Aware接口就是通过BeanPostProcessor的方式进行处理的。当ApplicationContext中每个对象的实例化过程走到BeanPostProcessor前置处理这一步时,ApplicationContext容器会检测到之前通过ApplicationContextAwareProcessor这个实现了BeanPostProcessor接口的实现类注册到容器的bean对象,并调用其postProcessBeforeInitialization方法,检查并设置Aware相关依赖。Spring的AOP功能就是使用此接口生成对象的代理类。
InitializingBean和init-method
在对象实例化过程调用过BeanPostProcessor的前置处理之后,会接着检测当前对象是否实现了InitializingBean接口,如果是,则会调用器afterPropertiesSet()方法进一步调整对象实例的状态。
public interface InitializingBean(){
void afterPropertiesSet() throw Exception
}
init-method方法,系统重业务对象的自定义初始化操作。使得不再受制于实现InitializingBean接口。
DisposableBean接口
当一个对象使用完成之后,若实现了DisposableBean,并重写了destroySingletons(),那么将进行对象销毁。但是如果不能在合适的时机调用destroysingletons(),那么所有实现了DisposableBean接口的对象实例或者声明了destroy-method的bean定义对应的对象实例,它们的自定义对象销毁逻辑就形同虚设,因为根本就不会被执行!
对于ApplicationContext容器来说。道理是一样的。但AbstractApplicationContext为我们提供了registerShutdowmHook()方法,该方法底层使用标准的Runtime类的addshutdownHook()方式来调用相应bean对象的销毁逻辑,从而保证在Java虚拟机退出之前,这些singtleton类型的bean对象实例的自定义销毁逻辑会被执行。当然AbstractApplicationContext注册的shutdownHook不只是调用对象实例的自定义销毁逻辑,也包括Applicationcontext相关的事件发布等。
不过,所有这些规则不包含prototype类型的bean对象,因为对象实例在容器实例化返回给请求方之后,容器就不再管理这种类型对象实例的生命周期了。
最后放置一个IOC简略流程图
本文参看《精通Spring4.x企业应用开发实战》和《Spring揭秘》创作而成,若有错误欢迎指正