当你创建一个bean定义时,你创建一个配方来创建由该bean定义定义的类的实际实例。bean定义是一个配方的想法很重要,因为它意味着,就像一个类一样,您可以从一个配方创建许多对象实例。
您不仅可以控制要插入到从特定的bean定义创建的对象中的各种依赖项和配置值,还可以控制从特定的bean定义创建的对象的范围。这种方法功能强大且灵活,因为您可以选择通过配置创建的对象的范围,而不必在Java类级别上烘焙对象的范围。Bean可以被定义为部署在多个作用域中的一个:
Spring框架支持六个作用域,其中四个作用域仅在使用Web感知时才可用ApplicationContext。
Scope(作用域) | Description(描述) |
---|---|
singleton(单例) | (默认)每个Spring IoC容器将单个bean定义作用于单个对象实例。 |
prototype(原型) | Scopes a single bean definition to any number of object instances.将任何数量的对象实例的单个bean定义作用域。 |
request(请求) | 将单个bean定义作用于单个HTTP请求的生命周期; 也就是说,每个HTTP请求都有自己的实例,这个实例是在单个bean定义的背后创建的。只有在Web认知的Spring环境中才有效ApplicationContext。 |
session(会话作用域) | 将一个单一的bean定义作用于HTTP的生命周期Session。只有在Web认知的Spring环境中才有效ApplicationContext。 |
application(应用作用域) | 将范围定义为a的生命周期的单个bean定义ServletContext。只有在Web认知的Spring环境中才有效ApplicationContext。 |
websocket(websocket作用域) | 将范围定义为a的生命周期的单个bean定义WebSocket。只有在Web认知的Spring环境中才有效ApplicationContext。 |
<bean id&#61;"accountService" class&#61;"com.foo.DefaultAccountService"/>
<bean id&#61;"accountService" class&#61;"com.foo.DefaultAccountService" scope&#61;"singleton"/>
为了支持Bean的范围界定在request&#xff0c;session&#xff0c;application&#xff0c;和 websocket&#xff08;即具有web作用域bean&#xff09;&#xff0c;在你定义你的Bean之前需要做少量的初始配置。&#xff08;这些初始设置对于标准的范围域是不需要的例如&#xff1a;singleton和prototype&#xff09;。
您如何完成此初始设置取决于您特定的Servlet环境。
如果你在Spring Web MVC中访问范围化的bean&#xff0c;实际上&#xff0c;在由Spring处理的请求中DispatcherServlet&#xff0c;不需要特别的设置&#xff1a; DispatcherServlet已经公开了所有相关的状态。
如果您使用Servlet 2.5 Web容器&#xff0c;并且在Spring之外处理请求 DispatcherServlet&#xff08;例如&#xff0c;使用JSF或Struts时&#xff09;&#xff0c;则需要注册 org.springframework.web.context.request.RequestContextListener ServletRequestListener。对于Servlet 3.0&#43;&#xff0c;这可以通过WebApplicationInitializer 接口以编程方式完成。或者&#xff0c;对于较旧的容器&#xff0c;将以下声明添加到Web应用程序的web.xml文件中&#xff1a;
或者&#xff0c;如果您的监听器设置存在问题&#xff0c;请考虑使用Spring’s RequestContextFilter&#xff08;过滤器&#xff09;。过滤器映射取决于周围的Web应用程序配置&#xff0c;因此您必须根据需要进行更改。
DispatcherServlet&#xff0c;RequestContextListener并且RequestContextFilter都做完全相同的事情&#xff0c;即将HTTP请求对象绑定到Thread服务该请求的对象。这使得请求和会话范围的bean可以在调用链的更下方进行访问。
考虑以下用于bean定义的XML配置&#xff1a;
Spring容器LoginAction通过loginAction为每个HTTP请求使用bean定义来创建一个新的bean 实例。也就是说&#xff0c;这个 loginAction bean的作用域是HTTP请求级别。您可以根据需要更改创建的实例的内部状态&#xff0c;因为从同一个loginActionbean定义创建的其他实例将不会看到这些状态变化; 他们对个人的要求很特别。当请求完成处理时&#xff0c;作用于该请求的bean将被丢弃。
使用注释驱动组件或Java Config时&#xff0c;&#64;RequestScope可以使用注释将组件分配给request作用域。
&#64;RequestScope
&#64;Component
public class LoginAction {// ...
}
考虑用以下的XML配置来定义一个会话范围&#xff1a;
Spring容器UserPreferences通过在userPreferences单个HTTP的生命周期中使用bean定义来创建bean 的新实例Session。换句话说&#xff0c;这个userPreferencesbean的有效范围在HTTP Session级别。和request-scopedbean一样&#xff0c;您可以根据需要更改创建的实例的内部状态&#xff0c;因为知道其他Session使用同一个userPreferencesbean定义创建的实例的HTTP 实例也不会看到这些状态变化&#xff0c;因为它们是特定的到一个单独的HTTP Session。当HTTP Session最终被丢弃时&#xff0c;作用于该特定HTTP的bean Session也被丢弃。
使用注释驱动组件或Java Config时&#xff0c;&#64;SessionScope可以使用注释将组件分配给session作用域。
&#64;SessionScope
&#64;Component
public class UserPreferences {// ...
}
考虑以下用以下的XML文件去给Bean定义一个应用程序的范围。
Spring容器使用在整个Web应用程序只定义一次的appPreferences bean&#xff0c;创建一个新的appPreferences实例。
也就是说&#xff0c;这个 appPreferencesbean的作用域是ServletContext作为一个常规ServletContext属性存储的 。这有点类似于Spring单例bean&#xff0c;但在两个重要方面有所不同&#xff1a;它是单例ServletContext&#xff0c;而不是每个Spring’ApplicationContext’&#xff08;在任何给定的Web应用程序中可能有几个&#xff09;&#xff0c;它实际上是公开的&#xff0c;因此是可见的作为ServletContext属性。
使用注释驱动组件或Java Config时&#xff0c;&#64;ApplicationScope 可以使用注释将组件分配给application作用域。
&#64;ApplicationScope
&#64;Component
public class AppPreferences {// ...
}
Spring IoC容器不仅管理对象&#xff08;bean&#xff09;的实例化&#xff0c;还管理协作者&#xff08;或依赖项&#xff09;的连接。如果您想要将HTTP请求范围的bean注入&#xff08;例如&#xff09;一个更长寿命范围的另一个bean中&#xff0c;您可以选择注入一个AOP代理来代替范围bean。也就是说&#xff0c;您需要注入一个代理对象&#xff0c;该对象公开与作用域对象相同的公共接口&#xff0c;但也可以从相关作用域&#xff08;例如HTTP请求&#xff09;中检索真实目标对象&#xff0c;并将方法调用委托给实际对象。
您也可以
在作为范围的bean之间使用singleton&#xff0c;然后通过可序列化的中间代理&#xff0c;因此可以在反序列化中重新获得目标单身bean。
当针对范围的bean进行声明prototype时&#xff0c;共享代理上的每个方法调用都将导致创建一个新的目标实例&#xff0c;然后将该呼叫转发给该实例。
此外&#xff0c;范围代理不是以生命周期安全的方式从较短范围访问Bean的唯一方法。您也可以简单地声明您的注入点&#xff08;即构造函数/设置器参数或自动装配的字段&#xff09;ObjectFactory&#xff0c;从而允许getObject()每次需要时根据需要调用一次调用来检索当前实例 - 而无需保留实例或单独存储实例。
作为一个扩展的变体&#xff0c;你可以声明ObjectProvider哪个提供了几个额外的访问变体&#xff0c;包括getIfAvailable和getIfUnique。
调用JSR-330变体Provider&#xff0c;与每次检索尝试的Provider声明和相应get()调用一起使用。
以下示例中的配置只有一行&#xff0c;但了解“原因”以及它“如何”很重要。
<beans xmlns&#61;"http://www.springframework.org/schema/beans"xmlns:xsi&#61;"http://www.w3.org/2001/XMLSchema-instance"xmlns:aop&#61;"http://www.springframework.org/schema/aop"xsi:schemaLocation&#61;"http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><bean id&#61;"userPreferences" class&#61;"com.foo.UserPreferences" scope&#61;"session"><aop:scoped-proxy/>bean><bean id&#61;"userService" class&#61;"com.foo.SimpleUserService"><property name&#61;"userPreferences" ref&#61;"userPreferences"/>bean>
beans>
要创建这样一个代理&#xff0c;可以将一个子
元素插入到一个有作用域的bean定义中&#xff08;请参阅选择要创建的代理类型和 基于XML模式的配置&#xff09;。豆类的定义为何作用域的request&#xff0c;session和自定义范围水平要求
元素&#xff1f;让我们来看看下面的单例bean定义&#xff0c;并将其与您需要为上述范围定义的内容进行对比&#xff08;请注意&#xff0c;下面的 userPreferencesbean定义是不完整的&#xff09;。
在前面的例子中&#xff0c;singleton bean userManager注入了对HTTP- Sessionscoped bean 的引用userPreferences。这里的要点是 userManagerbean是一个单独的实例&#xff1a;每个容器只会实例化一次&#xff0c;并且它的依赖关系&#xff08;在本例中只有一个userPreferencesbean&#xff09;也只能被注入一次。这意味着这个userManagerbean只会在完全相同的userPreferences对象上运行&#xff0c;也就是它最初注入的那个对象。
这不是将寿命较短的作用域bean注入到寿命较长的作用域bean时所需的行为&#xff0c;例如将一个HTTP- Sessionscoped协作bean作为依赖注入到singleton bean中。相反&#xff0c;您需要一个userManager 对象&#xff0c;并且在HTTP的生命周期中Session&#xff0c;您需要一个userPreferences特定于所述HTTP 的对象Session。因此&#xff0c;容器创建一个对象&#xff0c;该对象公开与UserPreferences该类完全相同的公共接口&#xff08;理想情况下是一个 UserPreferences实例的对象&#xff09;&#xff0c;该UserPreferences对象可以从作用域机制&#xff08;HTTP请求Session等&#xff09;获取真实 对象。容器将这个代理对象注入到userManagerbean中&#xff0c;但不知道这一点UserPreferences参考是一个代理。在这个例子中&#xff0c;当一个UserManager 实例在依赖注入UserPreferences对象上调用一个方法时&#xff0c;它实际上是在代理上调用一个方法。代理然后 UserPreferences从HTTP&#xff08;在本例中&#xff09;Session获取真实UserPreferences对象&#xff0c;并将方法调用委托给检索到的真实对象。
因此&#xff0c;你注射时需要以下&#xff0c;准确&#xff0c;完整&#xff0c;配置 request-和session-scoped bean到协作对象&#xff1a;
选择要创建的代理类型
默认情况下&#xff0c;当Spring容器为使用
元素标记的bean创建代理时&#xff0c;将创建一个基于CGLIB的类代理。
CGLIB代理只拦截公共方法调用&#xff01;不要在这样的代理上调用非公共方法; 它们不会被委派给实际的作用域目标对象。
或者&#xff0c;可以通过指定元素false的proxy-target-class属性值来配置Spring容器&#xff0c;以便为此类范围的bean创建标准的基于JDK接口的代理
。使用基于JDK接口的代理意味着在应用程序类路径中不需要额外的库来实现这种代理。但是&#xff0c;这也意味着作用域bean的类必须至少实现一个接口&#xff0c;并且注入了作用域bean的所有协作者都必须通过它的一个接口引用bean。
<bean id&#61;"userPreferences" class&#61;"com.foo.DefaultUserPreferences" scope&#61;"session"><aop:scoped-proxy proxy-target-class&#61;"false"/>
bean><bean id&#61;"userManager" class&#61;"com.foo.UserManager"><property name&#61;"userPreferences" ref&#61;"userPreferences"/>
bean>
bean范围机制是可扩展的; 您可以定义自己的作用域&#xff0c;甚至可以重新定义现有的作用域&#xff0c;尽管后者被认为是不好的做法&#xff0c;并且不能覆盖内置singleton和prototype作用域。
要将自定义作用域集成到Spring容器中&#xff0c;需要实现org.springframework.beans.factory.config.Scope本节所述的 接口。有关如何实现自己的作用域的想法&#xff0c;请参阅Scope 随Spring Framework本身和Scopejavadoc提供的实现 &#xff0c;它解释了您需要更详细地实现的方法。
该Scope接口有四种方法来从作用域中获取对象&#xff0c;将它们从作用域中移除并允许它们被销毁。
以下方法从基础范围返回对象。例如&#xff0c;会话范围实现返回会话范围的bean&#xff08;如果它不存在&#xff0c;该方法在绑定到会话以供将来参考之后返回该bean的新实例&#xff09;。
Object get(String name, ObjectFactory objectFactory)
以下方法将该对象从基础范围中移除。例如&#xff0c;会话范围实现从基础会话中删除会话范围的bean。应该返回该对象&#xff0c;但如果找不到具有指定名称的对象&#xff0c;则可以返回null。
Object remove(String name)
以下方法注册范围在销毁时或范围内的指定对象被销毁时应执行的回调。有关销毁回调的更多信息&#xff0c;请参阅javadocs或Spring范围实现。
void registerDestructionCallback(String name, Runnable destructionCallback)
以下方法获取基础范围的对话标识符。这个标识符对于每个范围是不同的。对于会话范围的实现&#xff0c;这个标识符可以是会话标识符。
String getConversationId()
在你编写和测试一个或多个自定义Scope实现之后&#xff0c;你需要让Spring容器知道你的新的作用域。以下方法是Scope使用Spring容器注册新的核心方法&#xff1a;
void registerScope(String scopeName, Scope scope);
这个方法是在ConfigurableBeanFactory接口中声明的&#xff0c;该接口在ApplicationContextSpring通过BeanFactory属性提供的大多数具体实现中都可用。
该registerScope(..)方法的第一个参数是与范围关联的唯一名称; Spring容器本身的这些名字的例子是singleton和 prototype。该registerScope(..)方法的第二个参数是Scope您希望注册和使用的自定义实现的实际实例。
假设你编写你的自定义Scope实现&#xff0c;然后如下注册它。
下面的例子使用了SimpleThreadScope&#xff0c;这个已经被Spring包括了&#xff0c;但是默认是没有注册的。这些说明对您自己的自定义Scope 实现而言是相同的。
Scope threadScope &#61; new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
然后创建符合自定义的范围规则的bean定义Scope&#xff1a;
通过自定义Scope实现&#xff0c;您不限于对范围进行程序化注册。您也可以Scope使用CustomScopeConfigurer该类以声明方式进行注册 &#xff1a;
<beans xmlns&#61;"http://www.springframework.org/schema/beans"xmlns:xsi&#61;"http://www.w3.org/2001/XMLSchema-instance"xmlns:aop&#61;"http://www.springframework.org/schema/aop"xsi:schemaLocation&#61;"http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><bean class&#61;"org.springframework.beans.factory.config.CustomScopeConfigurer"><property name&#61;"scopes"><map><entry key&#61;"thread"><bean class&#61;"org.springframework.context.support.SimpleThreadScope"/>entry>map>property>bean><bean id&#61;"bar" class&#61;"x.y.Bar" scope&#61;"thread"><property name&#61;"name" value&#61;"Rick"/><aop:scoped-proxy/>bean><bean id&#61;"foo" class&#61;"x.y.Foo"><property name&#61;"bar" ref&#61;"bar"/>bean>beans>
放置&#96;
在FactoryBean实现中时&#xff0c;它是工厂bean本身的作用域&#xff0c;而不是从中返回的对象getObject()。
好啦&#xff0c;这一节的细节到这里就结束啦。