1.5。bean 作用域
创建bean定义时,将通过一个配方来创建该bean的实际实例。把bean定义解释称配方这一思想跟重要,因为它意味着与类一样,您可以从一个配方中创建许多对象实例。
bean definition
是用来描述bean的一个结构体,就像每个人体检指标一样,进出一趟医院能拿到体检单,
bean在进入Spring流程中也会拿到属于自己的体检单。
虽然医院不能根据体检单创造出人类,但是Spring能根据bean definition创造出实例
您不仅可以控制从bean definition 创建的对象中的各种依赖项和配置值,还可以控制从特定bean定义创建的对象的作用域。
这种方法功能强大且灵活,因为您可以选择通过配置创建的对象的作用域,而不必在Java类层次上处理对象的范围。
可以将bean定义为部署在多种作用域中的一种。
Spring框架支持6种作用域,其中4种作用域只有在使用支持web的ApplicationContext时才可用。
您还可以创建自定义作用域。
Bean作用域范围 | 描述 |
---|---|
singleton | (默认)单例对象,容器中只存在一个对象。 |
prototype | 每次使用都会实例化新的对象 |
request | 将单个bean定义的范围限定为单个HTTP请求的生命周期。 也就是说,每个HTTP请求都有一个bean实例。 仅在支持web的Spring ApplicationContext上下文中有效。 |
session | 每一次HTTP请求都会产生一个新的bean, 同时该bean仅在当前HTTP session内有效。 仅在可感知网络的Spring上下文中有效ApplicationContext。 |
application | 将单个bean定义的作用域限定为的生命周期ServletContext。 仅在可感知网络的Spring上下文中有效ApplicationContext。 |
websocket | 将单个bean定义的作用域限定为的生命周期WebSocket。 仅在可感知网络的Spring上下文中有效ApplicationContext。 (这个scope没在源码中找到,暂不清楚实现逻辑, 但应该和之前的大同小异) |
从Spring 3.0开始,线程作用域可用,但默认情况下未注册。有关更多信息,请参见的文档 SimpleThreadScope。有关如何注册此自定义范围或任何其他自定义范围的说明,请参阅 使用自定义范围。
说一下大致流程(非文档内容)
https://gitee.com/cclookeme/tocmat-main
贴一个git地址,这个工程演示如何用最简单的代码启动一个Spring应用,非SpringBoot
拿出来大致一说
1.Tomcat启动会查找ServletContainerInitializer实现类并执行其中的onStartup方法。
2.Spring-web模块存在ServletContainerInitializer实现类,所以Tomcat启动会调用Spring-web的代码。
3.但是我们用Spring框架的话不需要实现这个接口,实现一个Spring的接口WebApplicationInitializer。
4.就可以由Tomcat调用Spring-web的ServletContainerInitializer实现类,
再由Spring-web模块调用我们的WebApplicationInitializer实现类
//WebApplicationInitializer 实现方式如下(SpringMvc官网提供地址如下)
//https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web.html#spring-web
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
//ac.refresh(); 官方文档提供了这样一行代码但是我们为了使用application Scope并不需要这个代码
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
有了如上代码,我们就有了一个ApplicationContext环境。
Tomcat启动见git项目就可以了,那个不重要。
我们就假定现在Tomcat已经启动,启动并执行到我们自定义的WebApplicationInitializer中了
在这个自定义方法中我们实例化了AnnotationConfigWebApplicationContext这个对象,
把这个对象带进了DispatcherServlet,以至于ServletContext中以备后续使用,
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
上边两行代码仅仅是指定了一个Config类,这个类上有很多注解相当于SpringBoot的启动类
还有其他的参数需要Spring自己来进行处理,所有后续会有这个方法来丰富ApplicationContext
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
//这个方法只看这个地方 如果之前我们自定义代码里面执行了ac.refresh()
//那么这个地方的active会是true,那就没法进入if里面
//而if里面则是给ApplicationContext赋值了ServletContext
//最后也会调用refresh方法
//而ServletContext的作用就是我们的 application Scope
//如果不注入ServletContext 则就无法启用这个 Scope
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
.
.
.
.
return wac;
}
在看一个refresh()里执行的代码
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory,
@Nullable ServletContext sc) {
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope());
//这里判断了sc sc就是上边我们要注入的ServletContext 没注入的话这里就不注册解析 application Scope的bean
if (sc != null) {
ServletContextScope appScope = new ServletContextScope(sc);
beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
sc.setAttribute(ServletContextScope.class.getName(), appScope);
}
.
.
.
}
在之后详细Spring启动代码我们不用看了
我们在看下Spring获取bean时候怎么处理的Scope
//如果是单例 直接获取单例池中的对象 单例池中不存在执行createBean方法
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
//如果是原型直接就createBean重新生成对象
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
//其他Scope
//首先获取当前bean的Scope
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
}
//根据Scope获取解析当前Scope的bean
//这里也就是给了我们自定义Scope的可能 我们可以自己定义Scope
//再定义一个Scope的解析类就哦了
Scope scope = this.scopes.get(scopeName);
//如果没找到解析类则报错 就像前边的ServletContext没注入一样
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
//这里的get是可以自定已实现缓存了,比如同一个request用一个实例
//相同sessionId 用一个实例
//如果没找到对应的实例则createBean 生成
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; consider " +
"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
下边介绍各个Scope内容和用法(官方文档)
1.5.1。 The Singleton Scope
换句话说,当您定义一个bean定义并且它的作用域是一个单例对象时,Spring IoC容器会创建该bean定义定义的对象的一个实例。这个单一实例存储在这样的单例bean的缓存中,对这个已命名bean的所有后续请求和引用都会返回缓存的对象。
<bean id="myDao" class="..."></bean>
<bean id="a" class="...">
<property name="ff" ref="myDao"></property>
</bean>
<bean id="b" class="...">
<property name="ff" ref="myDao"></property>
</bean>
<bean id="c" class="...">
<property name="ff" ref="myDao"></property>
</bean>
a,b,c三个对象持有的ff属性对应的都是一个实例
Spring的单例bean概念不同于“四人帮”(GoF)模式书中定义的单例模式。
GoF单例对对象的作用域进行硬编码,这样每个类加载器都会创建一个且只有一个特定类的实例。
Spring单例的作用域最好描述为单个容器的单个bean。
这意味着,如果您在单个Spring容器中为特定类定义了一个bean,那么Spring容器将创建由该bean定义定义的类的一个且仅一个实例。
单例作用域是Spring中的默认作用域。要在XML中定义一个单例的bean,你可以像下面的例子那样定义一个bean:
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- 下面的方法是等效的,但是是多余的(单例范围是默认的) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
1.5.2。The Prototype Scope
bean部署的非单例原型范围导致每次对特定bean发出请求时都创建一个新的bean实例。也就是说,该bean被注入到另一个bean中,或者您通过容器上的getBean()方法调用请求它。作为规则,您应该对所有有状态bean使用原型作用域,对无状态bean使用单例作用域。
<bean id="myDao" class="..." scope="prototype"></bean>
<bean id="a" class="..." scope="prototype">
<property name="ff" ref="myDao"></property>
</bean>
<bean id="b" class="..." scope="prototype">
<property name="ff" ref="myDao"></property>
</bean>
<bean id="c" class="..." scope="prototype">
<property name="ff" ref="myDao"></property>
</bean>
a,b,c持有不同的 myDao实例,前提a,b,c必须都不是单例
(数据访问对象(DAO)通常不配置为原型,因为典型的DAO不持有任何会话状态。)
下面的例子用XML将bean定义为原型:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
与其他作用域相比,Spring不管理原型bean的完整生命周期。容器实例化、配置和组装原型对象并将其交给客户端,而不进一步记录该原型实例。
因此,尽管初始化生命周期回调方法在所有对象上都被调用,但在原型的情况下,配置的销毁生命周期回调不会被调用。
客户端代码必须清理原型范围的对象并释放原型bean所持有的昂贵资源。
要让Spring容器释放原型作用域bean所持有的资源,请尝试使用自定义BeanPostProcessor,它持有对需要清理的bean的引用。
BeanPostProcessor接口定义了回调方法,
您可以实现这些方法来提供您自己的(或覆盖容器的默认值)实例化逻辑、依赖项解析逻辑等等。
如果您想在Spring容器完成实例化、配置和初始化bean之后实现一些自定义逻辑,
您可以插入一个或多个自定义BeanPostProcessor实现。
您可以配置多个BeanPostProcessor实例,
并且可以通过设置order属性来控制这些BeanPostProcessor实例运行的顺序。
只有当BeanPostProcessor实现了有序接口时,才能设置此属性。
如果您编写自己的BeanPostProcessor,也应该考虑实现Ordered接口。
以上是官方解释,通俗的说就是
BeanPostProcessor有一个方法可以在bean实例化之后被执行,那时候可以拿到bean的引用,并且修改bean
1.5.3。Singleton Bean 依赖 Prototype Bean
当您使用带有原型bean依赖项的单例范围bean时,请注意依赖项是在实例化时解析的。
因此,如果您将一个原型作用域的bean注入到一个单例作用域的bean中,那么只有一次原型bean的实例化,然后注入到单例bean中。
实例化单例bean时生成的原型bean实例是提供给单例作用域bean的唯一实例。
但是,假设您希望单例作用域bean在运行时重复获得原型作用域bean的新实例。
您不能依赖地将原型作用域的bean注入到单例bean中,因为该注入只在Spring容器实例化单例bean并解析和注入其依赖项时发生一次。
如果您不止一次地需要原型bean在运行时的新实例,请参阅一起来读官方文档-----SpringIOC(04)1.4.6小节
1.5.4。request,session,application,和websocket范围
在request,session,application,和websocket范围只有当你使用一个基于web的Spring可ApplicationContext实现(例如XmlWebApplicationContext)。
如果您将这些作用域与常规的Spring IoC容器一起使用,例如ClassPathXmlApplicationContext,则会引发抱怨未知bean作用域的IllegalStateException异常。
<bean id="serviceTwo" class="org.springframework.example.service.ServiceTwo" scope="application"/>
Caused by: java.lang.IllegalStateException: No Scope registered for scope name 'application'
初始Web配置
为了支持bean的范围界定在request,session,application,和 websocket(即具有web作用域bean),需要在定义你的bean之前做少量的初始配置。
如何完成此初始设置取决于您的特定Servlet环境。
如果您在Spring Web MVC中访问作用域化的bean,实际上是在Spring处理的请求中,则DispatcherServlet不需要特殊的设置。 DispatcherServlet已经公开了所有相关状态。
对于Servlet 3.0+,可以使用该WebApplicationInitializer 接口以编程方式完成此操作。
public class LearnSpringMain implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext aa =
new AnnotationConfigWebApplicationContext();
aa.register(LearnSpringConfig.class);
aa.setServletContext(servletContext);
DispatcherServlet dispatcherServlet = new DispatcherServlet(aa);
dispatcherServlet.setEnableLoggingRequestDetails(true);
ServletRegistration.Dynamic ds = servletContext.addServlet("dispatcherServlet",dispatcherServlet);
ds.addMapping("/*");
ds.setLoadOnStartup(1);
}
}
如果您使用Servlet 2.5 Web容器,并且在Spring之外处理请求 DispatcherServlet(例如,使用JSF或Struts时),则需要注册 org.springframework.web.context.request.RequestContextListener ServletRequestListener。 或者,或者对于较旧的容器,将以下声明添加到Web应用程序的web.xml文件中:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
另外,如果您的监听器设置存在问题,请考虑使用Spring的 RequestContextFilter。过滤器映射取决于周围的Web应用程序配置,因此您必须适当地对其进行更改。以下清单显示了Web应用程序的过滤器部分:
<web-app>
...
<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>
...
</web-app>
DispatcherServlet,RequestContextListener和RequestContextFilter都做完全相同的事情,即将HTTP请求对象绑定到Thread为该请求提供服务的对象。这使得在请求链和会话范围内的Bean可以在调用链的更下游使用。
Request scope
考虑以下XML配置来定义bean:
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring容器通过为每个HTTP请求使用LoginAction bean定义来创建LoginAction bean的一个新实例。也就是说,loginAction bean的作用域在HTTP请求级别。您可以随意更改所创建实例的内部状态,因为从相同loginAction bean定义创建的其他实例不会看到这些状态更改。它们是针对个别请求的。当请求完成处理时,作用域为该请求的bean将被丢弃。
当使用注释驱动的组件或Java配置时,可以使用@RequestScope注释将组件分配到请求范围。下面的例子展示了如何做到这一点:
@RequestScope
@Component
public class LoginAction {
// ...
}
Session Scope
考虑以下XML配置来定义bean:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring容器通过使用单个HTTP会话生存期的UserPreferences bean定义来创建一个UserPreferences bean的新实例。换句话说,userPreferences bean有效地限定了HTTP会话级别的范围。与请求范围内bean一样,你可以改变内部状态的实例创建尽可能多的你想要的,知道其他HTTP会话实例也使用相同的实例创建userPreferences bean定义看不到这些变化状态,因为他们是特定于一个单独的HTTP会话。当HTTP会话最终被丢弃时,作用域为该特定HTTP会话的bean也将被丢弃。
在使用注释驱动的组件或Java配置时,您可以使用@SessionScope注释来为会话范围分配一个组件。
@SessionScope
@Component
public class UserPreferences {
// ...
}
Application Scope
考虑以下XML配置来定义bean:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring容器为整个web应用程序使用一次App Preferences bean定义,从而创建AppPreferences bean的一个新实例。也就是说,appPreferences bean的作用域在ServletContext级别,并存储为一个常规的ServletContext属性。这有点类似于弹簧单例bean,但在两个重要方面不同:它是一个单例每ServletContext不是每春天ApplicationContext的(可能有几个在任何给定的web应用程序),它实际上是暴露,因此可见ServletContext属性。
当使用注释驱动的组件或Java配置时,您可以使用@ApplicationScope注释来为应用程序范围分配一个组件。
下面的例子展示了如何做到这一点:
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
将带有Scope属性的的bean作为依赖项
Spring IoC容器不仅管理对象(bean)的实例化,而且还管理协作者(或依赖关系)的连接。如果您想将(例如)一个HTTP请求作用域的bean注入到另一个更长寿作用域的bean中,您可以选择注入一个AOP代理来代替作用域bean。也就是说,您需要注入一个代理对象,该代理对象公开与作用域对象相同的公共接口,但也可以从相关作用域检索实际目标对象(例如HTTP请求),并将方法调用委托给实际对象。
您还可以在作用域为单例的bean之间使用<aop:scoped-proxy/>,然后通过一个可序列化的中间代理进行引用,
从而能够在反序列化时重新获得目标单例bean。
当对作用域原型bean声明<aop:scoped-proxy/>时,对共享代理的每个方法调用都会创建一个新的目标实例,
然后将调用转发到该目标实例。
而且,作用域代理并不是解决长作用域bean接收短作用域bean的唯一方法。
你也可以声明你的注入方式(也就是构造函数或setter参数或autowired的字段)
作为ObjectFactory<MyTargetBean>,
允许getObject()调用来检索当前实例对需求每次需要——没有分别持有实例或存储它。
例如:
@Autowired
private ObjectFactory<ServiceTwo> serviceTwo;
@RequestMapping("/b")
@ResponseBody
public String getName(){
return serviceTwo.getObject().getServiceOneName();
}
作为扩展变量,您可以声明ObjectProvider<MyTargetBean>,
它提供了几个额外的访问变量,包括getIfAvailable和getIfUnique。
以下示例中的配置仅一行,但是了解其背后的“原因”和“方式”很重要:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--scope 为 session 的bean-->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<!--增加当前标签后 每次userService 请求该bean的实例都是请求的代理对象
代理对象再去调用真是实例,就可以保证短期Scope能正常使用,而不至于使短期
Scope保持和单例Scope一样长的生命周期-->
<aop:scoped-proxy/> !!!
</bean>
<!-- scope 为单例的bean -->
<bean id="userService" class="com.something.SimpleUserService">
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
要创建 !!! 处这样的代理,需要将子元素aop:scoped-proxy/插入作用域bean定义中。
为什么在request,session和自定义Scope的bean需要aop:scoped-proxy/元素?
看下面的例子和上边的做对比!
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的示例中,单例bean (userManager)被注入了对HTTP会话作用域bean (userPreferences)的引用。
这里的重点是userManager bean是一个单例:每个容器只实例化一次,
它的依赖项(在本例中只有一个,即userPreferences bean)也只被注入一次。
这意味着userManager bean只操作完全相同的userPreferences对象(即最初注入它的那个对象,因为userPreferences并没有达到 session Scope 应有的作用域的效果)。
当将较短的作用域bean注入到较长作用域bean中时(例如,将HTTP会话作用域的协作bean作为依赖项注入到单例bean中),这不是您想要的行为。
相反,您仅仅只需要一个userManager对象,但是在HTTP会话的生命周期中,您需要一个特定于HTTP会话的userPreferences对象。
因此,容器创建一个对象,该对象公开与UserPreferences类完全相同的公共接口(理想情况下,该对象是一个UserPreferences实例),它可以从作用域机制(HTTP请求、会话等)获取真正的UserPreferences对象。
容器将这个代理对象注入到userManager bean中,但是userManager bean不知道这个UserPreferences引用是一个代理。
在本例中,当UserManager实例调用依赖注入的UserPreferences对象上的方法时,它实际上是在调用代理上的方法。代理然后从(在本例中讨论的是HTTP会话)HTTP会话中获取真实的UserPreferences对象,并将方法调用委托给检索到的真实UserPreferences对象。
因此,你当注射需要满足以下(正确和完整)的配置 request-和session-scoped豆类为合作对象,如下例所示:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
选择要创建的代理类型
默认情况下,当Spring容器为标记为aop:scoped-proxy/元素的bean创建代理时,将创建一个基于cglib的类代理。
CGLIB代理仅拦截公共方法调用!不要在这样的代理上调用非公共方法。它们没有被委托给实际作用域的目标对象。
或者,您可以配置Spring容器,通过为aop:scoped-proxy/元素的proxy-target-class属性的值指定false,为这种作用域bean创建标准的基于JDK接口的代理。
aop:scoped-proxy/使用JDK基于接口的代理意味着您不需要在应用程序类路径中添加其他库即可使用这种代理。
然而,这也意味着作用域bean的类必须实现至少一个接口,并且所有注入作用域bean的协作者必须通过它的其中一个接口引用该bean。
以下示例显示基于接口的代理:proxy-target-classaop:scoped-proxy/
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.stuff.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
1.5.5。自定义作用域
Bean作用域机制是可扩展的。您可以定义自己的作用域,甚至重新定义现有作用域,尽管后者被认为是不好的做法,并且您不能覆盖内置作用域singleton和prototype作用域。
创建自定义范围
要将您的自定义作用域集成到Spring容器中,您需要实现org.springframework.bean .factory.config.Scope接口。
Scope接口有四个方法,用于从范围中获取对象、从范围中删除对象以及销毁它们。
例如,session scope实现返回session scope的bean(如果它不存在,则在将其绑定到会话以供将来引用之后,该方法返回该bean的一个新实例)。下面的方法从底层范围返回对象:
Object get(String name, ObjectFactory<?> objectFactory)
session scope的实现,例如,从基础会话中删除了session scope的bean并返回该对象。 如果找不到具有指定名称的对象,则可以返回null。以下方法从基础范围中删除该对象:
Object remove(String name)
以下方法注册一个回调,当销毁作用域或销毁作用域中的指定对象时,作用域应调用该回调:
void registerDestructionCallback(String name, Runnable destructionCallback)
以下方法获取基础范围的会话标识符:
String getConversationId()
每个范围的标识符都不相同。对于会话范围的实现,此标识符可以是会话标识符。
使用自定义范围
在编写和测试一个或多个自定义Scope实现之后,您需要使Spring容器意识到您的新作用域。以下方法是Scope在Spring容器中注册新方法的主要方法:
void registerScope(String scopeName, Scope scope);
此方法在ConfigurableBeanFactory接口上声明,该接口可通过Spring附带的大多数具体ApplicationContext实现上的BeanFactory属性使用。
registerScope(..)方法的第一个参数是与作用域关联的惟一名称。此类名称在Spring容器本身中的例子有singleton和prototype。
registerScope(..)方法的第二个参数是您希望注册和使用的自定义范围实现的一个实际实例。
假设您编写了自定义范围实现,然后注册它,如下一个示例所示。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
然后,您可以按照您的custom的作用域规则创建bean定义, Scope如下所示:
<bean id="..." class="..." scope="thread">
使用自定义Scope实现,您不仅仅局限于按部就班式的注册bean。
您还可以Scope使用CustomScopeConfigurer该类以声明方式进行注册 ,如以下示例所示:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://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="thing2" class="x.y.Thing2" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="thing1" class="x.y.Thing1">
<property name="thing2" ref="thing2"/>
</bean>
</beans>
注意:
当您放置aop:scoped-proxy/在FactoryBean实现中时,作用域是工厂Bean本身,而不是从中返回的对象getObject()。