前言:对于使用Spring框架的开发人员来说,我们主要做的主要有两件事情:①开发Bean;②配置Bean;而Spring帮我们做的就是根据配置文件来创建Bean实例,并调用Bean实例的方法来完成“依赖注入”,可以把Spring容器理解成一个大型工厂,Bean就是该工厂的产品,工厂(Spirng容器)里能生产出来什么样的产品(Bean),完全取决于我们在配置文件中的配置。我们那么今天我们就来说说关于Bean的故事。。。
容器中Bean的作用域
singleton | 在整个Spring IoC 容器中,使用 singleton 定义的Bean将只有一个实例 |
prototype | 原型模式,每次通过容器的getBean 方法获取prototype定义的Bean 时,都将产生一个新的Bean实例 |
request | 对于每次HTTP请求,使用request定义的Bean都将产生一个新的实例,每次HTTP请求都将产生不同的Bean实例,该作用域仅在给予web的Spring ApplicationContext情形下有效 |
session | 对于每次HTTP Session ,使用session定义的Bean都将产生一个新实例,该作用域仅在给予web的Spring ApplicationContext情形下有效 |
global session | 每个全局得HTTP Session对应一个Bean实例,该作用域仅在给予web的Spring ApplicationContext情形下有效 |
比较常用的是singleton 和 prototype 两种作用域,对于singleton作用域,每次请求该Bean都将获得相同的实例,Spring容器负责跟踪监视Bean实例的状态,负责维护Bean实例的生命周期行为,如果一个Bean被设置成prototype作用域,程序每次请求该id的Bean,Spring都会创建一个新的Bean实例,然后返回给程序,在这种情况下,Spring容器仅仅使用new 关键字创建Bean实例,一旦创建成功,容器Spring不再对Bean的生命周期负责,也不会维护Bean实例的状态。
如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在常见Java实例时,需要进行内存申请,销毁实例是,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此prototype作用域Bean 的创建销毁代价比较大。而singleton作用域的Bean 实例一旦创建成功,可以重复使用,因此,除非必要,否则避免将Bean作用域设置成prototype。
如果要使用 request,session,global session作用域的Bean,在配置Bean之前,还需要做少量的初始配置(将HTTP 请求banding到该提供服务的线程上,这使得具有request 和 session作用域的Bean实例能够在后面的调用链中被访问到),如果只是配置常规的作用域(singleton,prototype),则无须设置。如果Web 应用直接使用Spring MVC 作为MVC框架,即使用SpringDispatcherServlet 或DispatcherPorlet 来拦截所有用户请求,则无须设置,因为DispatcherServlet 和DispatcherPorlet已经处理了所有和请求有关的额状态处理。
当使用Spring's DispatcherServlet 以外的Servlet 2.4 及以上规范的Web 容器时需要在“web.xml”中增加如下配置:
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
如果使用的是早起版本的Web容器,则该容器不支持Listener 规范,故无法使用上述配置,只能改为Filter配置方式,配置代码如下:
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
容器中Bean的生命周期
Spring可以管理singleton作用域Bean的生命周期,Spring可以精确地知道singleton域bean何时被创建,何时初始化完成,以及容器何时准备销毁Bean实例。因为,对于singleton作用域的Bean,客户端的每次请求都返回同一个Bean实例,客户端代码不能控制Bean的销毁,它的生命周期都在Spring的掌握之中。这么一来,容器就可以管理实例化结束后(某些资源的申请)和销毁之前(进行某些资源的回收)的行为。管理Bean的生命周期行为主要有两个时机:注入依赖关系后,销毁实例之前;具体的管理方法如下
Spring提供两种方式在Bean全部属性设置成功后执行特定行为
使用init-method 属性(代码污染小)
在类中编写一个方法,在属性中指定该方法在依赖关系设置完成后自动执行。
实现InitializingBean接口(耦合较高)
编写afterPropertiesSet()方法的具体实现
同理,若在Bean销毁之前,执行特定的方法,只需要①使用 destroy-method属性②实现DisposableBean接口(实现destroy()方法)
对于prototype作用域的Bean,Spring容器只负责Bean的创建,当容器创建实例完成后,Bean将完全交给客户端代码管理,容器不再负责其生命周期。每次客户端请求prototype作用域的Bean时,Spring容器都会产生一个全新的Bean实例交个客户端(prototype就是这么任性),Spring容器本省也不知道自己创建了多少个实例,更无从知道这些实例什么时候才会被销毁。
Spring 的 Bean 和 JavaBean比较
- 规范:Spring容器对Bean 没有特殊要求,不像JavaBean 一样遵循一些规范(为每个属性提供相应的setter 和 getter 方法),不过对于设值注入的Bean,一定要提供setter 方法。
- 作用:Spring 中的Bean 是 java 实例,java组件,它的作用几乎无所不包,任何应用组件都被称为Bean,而传统的Java应用中的JavaBean通常作为DTO(数据传输对象),来封装值对象,在各层之间传递数据。
- 生命周期:传统的JavaBean作为值对象传递,不接受任何容器管理其生命周期,Spring中的Bean有Spring管理其生命周期行为。
Spring 的Bean继承和java继承的区别
- Spring中子Bean和父Bean可以是不同的类型,而java中的继承子类只是父类的一种特殊类型。
- Spring中Bean的继承是实例之间的关系,主要表现为参数值的延续,而java终的继承是类之间的关系,主要表现为属性,方法的延续。
- Spring中子Bean不可做父Bean使用,不具备多态性,java中的子类实例完全可以做父类的实例使用
Bean 实例的创建方式及依赖配置
大多数情况下,BeanFactory 直接通过new 关键字调用构造器来创建Bean 实例,而class属性指定了Bean实例的实现类。但这并不是实例化Bean的唯一方法。
创建Bean通常有以下三种方式
- 调用构造器创建Bean 实例
- 调用静态工厂方法创建Bean
- 调用实例工厂方法创建Bean
- 调用构造器创建Bean 实例
调用构造器创建Bean实例是最常见的情况,,BeanFactory 将使用默认的构造器来创建Bean实例,该实例是个默认实例,Spring对Bean实例的所有属性执行默认初始化,即所有基本类型的值初始化为0或false,引用类型初始化为null,接下来BeanFactory会根据配置文件决定依赖关系,先实例化被依赖的Bean 实例,然后为Bean注入依赖关系,最后将一个完整的Bean实例返回给程序,该Bean实例化的所有属性,已经由Spring容器完成了初始化。
- 使用静态工厂方法创建Bean
使用静态工厂方法创建Bean实例时,class属性也必须指定,但此时class属性并不是指定Bean实例的实现类,而是静态工厂类,Spring需要根据工厂类的工厂方法来创建Bean实例,除此之外,还需要使用factory-method 属性来指定静态工厂方法名,Spring调用静态工厂方法(可能包含一组参数,若需要,使用<constructor-arg.../>元素传入)返回一个Bean实例,获得Bean实例后,Spring后面的处理步骤与采用普通方法创建Bean实例完全一样。
使用静态工厂方法创建Bean实例时,Spring将先解析配置文件,并根据配置文件指定的信息,通过反射调用静态工厂类的静态工厂方法,将该静态工厂方法的凡湖值作为Bean实例。在这个过程中,Spring不再负责创建Bean实例,Bean实例的创建是用户提供的静态工厂类负责创建的。但是Spring容器依然可以管理Bean实例的依赖关系,生命周期。
- 使用实例工厂方法
实例工厂方法与静态工厂方法不同:调用静态工厂方法只需使用工厂类,调用实例工厂方法则必须使用工厂实例。采用实例工厂方法时,配置Bean实例的<bean../>元素无需class属性,因为Spring容器不再直接实例化该Bean,Spring容器仅仅调用实例工厂的工厂方法,工厂方法负责创建Bean实例。采用实例工厂方法创建Bean时,需要为<bean../>元素指定如下两个属性:factory-bean:该属性的值为工厂Bean的id.factory-method:该属性指定实例工厂的工厂方法。与静态工厂方法相似,如果需要调用工厂方法时传入参数,使用<constructor-arg.../>元素确定参数值。
静态工厂方法创建实例和工厂方法创建实例的异同点:
调用实例工厂方法创建Bean,必须将实例工厂配置成Bean实例。而静态工厂方法创建Bean,则无需配置工厂Bean。
调用实例工厂方法创建Bean,必须使用factory-bean属性确定工厂Bean。而静态工厂方法创建Bean,则使用class 元素确定静态工厂类。
都需要factory-method 指定相应产生Bean实例的工厂方法。
工厂方法需要参数都可以使用<constructor-art.../>元素指定参数。