1、Spring Bean的作用域
作用域 | 描述 |
---|---|
singleton | 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。 |
prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。 |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。 |
application | 限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。 |
(1)被声明为singleton的bean
如果bean的作用域的属性被声明为singleton,那么Spring Ioc容器只会创建一个共享的bean实例。对于所有的bean请求,只要id与该bean定义的相匹配,那么Spring在每次需要时都返回同一个bean实例。
Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,singleton作用域是Spring中的缺省作用域。你可以在 bean 的配置文件中设置作用域的属性为 singleton
(2)被声明为prototype的bean
当一个bean的作用域为prototype,表示一个bean定义对应多个对象实例。声明为prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。
(3)请求作用域
请求作用域参考如下的Bean定义
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
Spring容器会在每次用到loginAction来处理每个HTTP请求的时候都会创建一个新的LoginAction实例。也就是说,loginActionBean的作用域是HTTP Request级别的。
当http请求调用作用域为request的bean的时候,每增加一个HTTP请求,Spring就会创建一个新的bean,在请求处理完成之后便及时销毁这个bean。开发者可以随意改变实例的状态,因为通过loginAction请求来创建的其他实例根本看不到开发者改变的实例状态,所有创建的Bean实例都是根据独立的请求来的。
(4)会话作用域
会话作用域参考如下的Bean定义
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
Spring容器会在每次调用到userPreferences时,在一个单独的HTTP会话周期来创建一个新的UserPreferences实例。换言之,userPreferencesBean的作用域是HTTP Session级别的。
Session中所有http请求共享同一个请求的bean实例。Session结束后就销毁bean。 在request-scoped作用域的Bean上,开发者可以随意的更改实例的状态。同样,使用从同一个userPreferences bean定义创建的其他HTTP Session实例在看不到不是自己的内部状态的修改,因为他们是单个的HTTP会话。每个Session请求都会创建新的userPreferences实例,所以开发者更改一个Bean的状态,对于其他的Bean仍然是不可见的。
(5)全局作用域
全局作用域参考如下的Bean定义
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
Spring容器会在整个web应用范围使用到appPreferences的时候创建一个新的AppPreferences的实例。也就是说,appPreferencesBean是在ServletContext级别的,作为常规的ServletContext属性。这种作用域在一些程度上来说和Spring的单例作用域相似,但是也有如下不同之处:
1.application作用域是每个ServletContext中包含一个,而不是每个SpringApplicationContext之中包含一个(某些应用中可能包含不止一个ApplicationContext)。
2.application作用域仅仅作为ServletContext的属性可见,单例Bean是ApplicationContext可见。
接下来再来简单的学习下在Spring当中如何自定义作用域:
在Spring 2.0中,Spring的Bean作用域机制是可以扩展的,这意味着,你不仅可以使用Spring提供的预定义Bean作用域,还可以定义自己的作用域,甚至重新定义现有的作用域(不提倡这么做,而且你不能覆盖内置的singleton和prototype作用域)
(6)自定义作用域
除了使用Spring已经定义好的作用域之外,还可以自定义bean的作用域。
要底线自定义作用域
1.首先需要实现自定义Scope类。
首先要先实现org.springframework.beans.factory.config.Scope这个接口,要将自定义scope集成到Spring容器当中就必须要实现这个接口。接口中有两个常用的方法,分别用于底层存储机制获取和删除这个对象。
2.在实现一个或多个自定义Scope并测试通过之后,接下来便是如何让Spring容器来识别新的作用域。registerScope方法就是在Spring容器中用来注册新的作用域。
void registerScope(String scopeName, Scope scope);
其中:第一个参数是与作用域相关的全局唯一的名称,第二个参数是准备实现的作用域的实例,就是实现Scope接口的实例。
比如实现Scope接口的类为SimpleThreadScope,要实现的自定义的bean的作用域的名称为“thread”,那就可以这么写。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope(“thread”, threadScope);
3.在实现和注册自定义的scope类之后,就可以通过如下类似的Bean定义来使用自定义的Scope:
<bean id="..." class="..." scope="thread">
另外,在自定义的Scope中,开发者也不限于仅仅通过编程方式来实现自定义的bean的作用域,也可以在Spring的配置文件中配置和使用自定义作用域和,比如配置CustomScopeConfigurer实例实现自定义的作用域,声明作用域名称为“thread”,就可以在xml文件中做如下类似的定义。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="bar" class="x.y.Bar" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="foo" class="x.y.Foo">
<property name="bar" ref="bar"/>
</bean>
</beans>
以上就是Spring Bean作用域的一些基本信息。
2、spring的生命周期
3、spring常用的三种注入方式
通常有三种方式:
1.通过构造器来注入;
2.通过setter方法来注入;
3.通过filed变量来注入;
三种方式的区别小结:
1.基于构造器的注入,会固定依赖注入的顺序;该方式不允许我们创建bean对象之间的循环依赖关系,这种限制其实是一种利用构造器来注入的益处 - 当你甚至没有注意到使用setter注入的时候,Spring能解决循环依赖的问题;
2.基于setter的注入,只有当对象是需要被注入的时候它才会帮助我们注入依赖,而不是在初始化的时候就注入;另一方面如果你使用基于构造器注入,CGLIB不能创建一个代理,迫使你使用基于接口的代理或虚拟的无参数构造函数。
3.相信很多同学都选择使用直接在成员变量上写上注解来注入,正如我们所见,这种方式看起来非常好,精短,可读性高,不需要多余的代码,也方便维护;
缺点:
1.当我们利用构造器来注入的时候,比较明显的一个缺点就是:假如我们需要注入的对象特别多的时候,我们的构造器就会显得非常的冗余、不好看,非常影响美观和可读性,维护起来也较为困难;
2.当我们选择setter方法来注入的时候,我们不能将对象设为final的;
3.当我们在field变量上来实现注入的时候
a.这样不符合JavaBean的规范,而且很有可能引起空指针;
b.同时也不能将对象标为final的;
c.类与DI容器高度耦合,我们不能在外部使用它;
d.类不通过反射不能被实例化(例如单元测试中),你需要用DI容器去实例化它,这更像集成测试;
总结:
1.强制性的依赖性或者当目标不可变时,使用构造函数注入(应该说尽量都使用构造器来注入)
2.可选或多变的依赖使用setter注入(建议可以使用构造器结合setter的方式来注入)
3.在大多数的情况下避免field域注入(感觉大多数同学可能会有异议,毕竟这个方式写起来非常简便,但是它的弊端确实远大于这些优点)
4.Spring 4.3+ 的同学可以试一试构造器的隐式注入,采用此方式注入后,使得我们的代码更优雅,更独立,减少了对Spring的依赖性。