一、Spring容器
在Spring中,Bean的是一个非常广义的概念,任何的Java对象、Java组件都被当成Bean处理。 Spring容器负责创建Bean实例,所以需要知道每个Bean的实现类,Java程序面向接口编程,无须关心Bean实例的实现类;但是Spring容器必须能够精确知道每个Bean实例的实现类,因此Spring配置文件必须精确配置Bean实例的实现类。
1、BeanFactory与ApplicationContext
Spring的容器有很多,可以归为两个不同的类型
- 一类是实现BeanFactory接口的简单容器系列,这系列容器只实现了容器的最基本功能
- 另一类是ApplicationContext应用上下文是BeanFactory的子接口,它作为容器的高级形态而存在。
BeanFactory
BeanFactory接口包含以下几个基本方法:
- Boolean containBean(String name):判断Spring容器是否包含id为name的Bean实例。
getBean(Class requiredType):获取Spring容器中属于requiredType类型的唯一的Bean实例。 - Object getBean(String name):返回Sprin容器中id为name的Bean实例。
T getBean(String name,Class requiredType):返回容器中id为name,并且类型为requiredType的Bean - Class <?> getType(String name):返回容器中指定Bean实例的类型。
这个容器基本很少使用,基本都是使用更高级的ApplicationContext容器。
ApplicationContext
ApplicationContext作为BeanFactory的子接口,使用它作为Spring容器会更加方便,它主要有如下几个实现类:
- FileSystemXmlApplicationContext:以基于文件系统的XML配置文件创建ApplicationContext实例。
- ClassPathXmlApplicationContext:以类加载路径下的XML配置文件创建的ApplicationContext实例。
- AnnotationConfigApplicationContext:以基于Java注解为配置创建的ApplicationContext实例。
创建Spring容器实例时,必须提供Spring容器管理的Bean的详细配置信息。Spring的配置信息通常采用xml配置文件来设置,因此,创建BeanFactory实例时,应该提供XML配置文件作为参数。
XML配置文件通常使用Resource对象传入。Resource接口是Spring提供的资源访问接口,通过使用该接口,Spring能够以简单、透明的方式访问磁盘、类路径以及网络上的资源。
1 | ApplicationContext ctx=new ClassPathXmlApplicationContext("bean.xml"); |
二、Bean的生命周期
1、接口的分类
Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类:
Bean自身的方法:这个包括了Bean本身调用的方法和通过配置文件中
<bean>
的- init-method
- destroy-method指定的方法
Bean级生命周期接口方法
BeanNameAware
BeanFactoryAware
InitializingBean
DiposableBean
这些接口的方法
容器级生命周期接口方法
InstantiationAwareBeanPostProcessor
BeanPostProcessor
这两个接口,一般称它们的实现类为“后处理器”。
2、启动步骤
Bean的建立
容器寻找Bean的定义信息并将其实例化。看过源码都知道,容器首先将Bean的定义信息以BeanDefinition的形式存储,等第一次getBean的时候再将其实例化并完成下面的依赖注入过程。
属性注入
使用依赖注入,Spring将值和bean的引用注入到bean对应的属性中。
BeanNameAware的setBeanName():
如果bean实现了org.springframework.beans.BeanNameAware接口,Spring将bean的ID传递给setBeanName()方法。
BeanFactoryAware的setBeanFactory():
如果bean实现了org.springframework.beans.factory.BeanFactoryAware接口,Spring将调用setBeanName()方法,将BeanFactory容器实例传入。
BeanPostProcessors的ProcessBeforeInitialization()
如果有org.springframework.beans.factory.config.BeanPostProcessors和Bean关联,那么其postProcessBeforeInitialization()方法将被将被调用。
InitializingBean的afterPropertiesSet()
如果Bean类已实现org.springframework.beans.factory.InitializingBean接口,则执行他的afterProPertiesSet()方法。
Bean定义文件中定义init-method:
如果bean使用init-method生命了初始化方法,该方法也会被调用
BeanPostProcessors的ProcessaAfterInitialization()
如果有任何的BeanPostProcessors实例与Bean实例关联,则执行BeanPostProcessors实例的ProcessaAfterInitialization()方法
就绪
此时,Bean已准备就绪,可以被应用程序使用了,它们将一直驻留在容器中,直到该容器被销毁。
DisposableBean的destroy()
在容器关闭时,如果Bean类有实现org.springframework.beans.factory.DisposableBean接口,Spring将调用他的destroy()方法。
Bean定义文件中定义destroy-method
如果bean使用destroy-method生命了初始化方法,该方法也会被调用
3、总结
这个生命周期是在是太复杂了,需要很长时间才能理清他们之间的逻辑关系。
BeanNameAware、ApplicationContextAware和BeanFactoryAware
这三个接口放在一起写,是因为它们是一组的,作用相似。
“Aware”的意思是”感知到的”,那么这三个接口的意思也不难理解:
- 实现BeanNameAware接口的Bean,在Bean加载的过程中可以获取到该Bean的id
- 实现ApplicationContextAware接口的Bean,在Bean加载的过程中可以获取到Spring的ApplicationContext,这个尤其重要,ApplicationContext是Spring应用上下文,从ApplicationContext中可以获取包括任意的Bean在内的大量Spring容器内容和信息
- 实现BeanFactoryAware接口的Bean,在Bean加载的过程中可以获取到加载该Bean的BeanFactory
从上面的调用顺序可以看出,在bean实例化并设置属性之后,紧接着就是先调用这三个接口相关的方法。
InitialingBean和DisposableBean
InitialingBean是一个接口,提供了一个唯一的方法afterPropertiesSet()。
DisposableBean也是一个接口,提供了一个唯一的方法destory()。
这两个接口是一组的,功能类似,因此放在一起:前者顾名思义在Bean属性都设置完毕后调用afterPropertiesSet()方法做一些初始化的工作,后者在Bean生命周期结束前调用destory()方法做一些收尾工作。
1 | public class LifecycleBean implements InitializingBean, DisposableBean { |
1 | Enter LifecycleBean.setLifeCycleBeanName(), lifeCycleBeanName = lifeCycleBean |
执行结果和我们想的一样,afterPropertiesSet()方法就如同它的名字所表示的那样,是在Bean的属性都被设置完毕之后,才会调用。
- InitializingBean接口、Disposable接口可以和init-method、destory-method配合使用,接口执行顺序优先于配置
- InitializingBean接口、Disposable接口底层使用类型强转.方法名()进行直接方法调用,init-method、destory-method底层使用反射。
- 一但bean实现了InitializingBean接口,那么这个bean的代码就和Spring耦合到一起了,因此不鼓励使用接口的形式。
BeanPostProcessor
之前的InitializingBean、DisposableBean、FactoryBean包括init-method和destory-method,针对的都是某个Bean控制其初始化的操作,而似乎没有一种办法可以针对每个Bean的生成前后做一些逻辑操作,PostProcessor则帮助我们做到了这一点
BeanPostProcess接口有两个方法,都可以见名知意:
- postProcessBeforeInitialization:在初始化Bean之前
- postProcessAfterInitialization:在初始化Bean之后
值得注意的是,这两个方法是有返回值的,不要返回null,否则getBean的时候拿不到对象。
写一段测试代码,首先定义一个普通的Bean,为了后面能区分,给Bean加一个属性:
1 | public class CommonBean { |
定义一个PostProcess,实现BeanPostProcess接口:
1 | /** |
配置一个spring.xml,给CommonBean的commonName赋予不同的值以区分:
1 | <?xml version="1.0" encoding="UTF-8"?> |
运行一个Spring容器, 初始化结果为:
1 | Enter ProcessorBean.postProcessBeforeInitialization() |
看到每个Bean初始化前后都会分别执行postProcessorBeforeInitiallization()方法与postProcessorAfterInitialization()方法,最后两行出现原因是,PostProcessorBean本身也是一个Bean。
注意:
这两个方法分别叫before和after,而中间的Initialization过程就是上面说到的各个bean实现的InitializingBean接口和inti-method方法。
BeanFactory和ApplicationContext对待bean后置处理器稍有不同。ApplicationContext会自动检测在配置文件中实现了BeanPostProcessor接口的所有bean,并把它们注册为后置处理器,然后在容器创建bean的适当时候调用它,因此部署一个后置处理器同部署其他的bean并没有什么区别。而使用BeanFactory实现的时候,bean 后置处理器必须通过代码显式地去注册。
4、再次总结一下
现在bean的生命周期各个阶段应该就很清晰了,上面的步骤对应一下几个大的阶段
实例化与属性
对应步骤1和2
Aware相关的三个方法
BeanNameAware、ApplicationContextAware和BeanFactoryAware三个接口的方法,对应上面的步骤3和4,应该还有一个的里面没有写上
BeanPostProcessor后置处理器
这个接口是容器级别的,对所有的bean都起作用,也就是说每个bean初始化的时候都会检测实现这个接口的bean。
而这个接口对应两个方法,postProcessBeforeInitialization和postProcessAfterInitialization,分别对应上面的步骤5和8,因为一个是before一个是after,而中间的步骤6和7就是下面的各个bean自己实现的接口或者方法
InitializingBean接口和init-method
InitializingBean接口可以和init-method配合使用,接口执行顺序优先于配置,也就是上面的步骤6和步骤7他们的区别上面已经介绍过。
准备就绪
至此bean已经可以使用,可以看出大体分为上面的四个阶段
销毁
销毁阶段相对简单,因为没有上面的后置处理器,就是Disposable接口的方法和destory-method,两者的顺序和左右与InitializingBean接口和init-method都很类似,对应上面的步骤10和步骤11.
三、Bean的作用域
当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:
- singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
- prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例
- request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
- session:对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
- globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效
测试
1、定义不同作用域的java类
1 |
|
2、注入到controller
由于controller是单例的,因此必须通过实现ApplicationContextAware接口,直接从容器中取出对象。
1 |
|
3、运行结果
1 | //使用chrome第一次打印数据: |
4、结果分析
从结果来看,单例的bean的三次的数据都是打印一样的(默认的bean的级别就是单例);
prototype的bean每次的数据都是不一样的,每次请求的时候调用两次结果都不一样。
request的bean在每次request的时候都不一致,但是同一次request返回的数据是一致的。
session的bean在前两次结果一致,最后一次数据不一致,和session的节奏是一致的。
5、总结
如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。
现在开发应用大多数都是无状态的,所以单例多线程是比较好的实现方式,多例的比较有利于保存状态,完全没必要。
四、参考地址
http://gaddma.iteye.com/blog/2037038
http://www.cnblogs.com/zrtqsk/p/3735273.html