Spring 配置使用 - Bean 作用域

来源:互联网 发布:支付宝软件下载 编辑:程序博客网 时间:2024/06/08 09:10

基本概念

Scope,也称作用域,在 Spring IoC 容器是指其创建的 Bean 对象相对于其他 Bean 对象的请求可见范围

在 Spring IoC 容器中具有以下几种作用域:基本作用域(request、prototype),Web 作用域(reqeust、session、globalsession),自定义作用域。

Spring 的作用域在装配 Bean 时就必须在配置文件中指明,配置方式如下(以 xml 配置文件为例):

<!-- 具体的作用域需要在 scope 属性中定义 --><bean id="animals" class="com.demo.Animals" scope="xxx" />

基本作用域

1.singleton

singleton,也称单例作用域。在每个 Spring IoC 容器中有且只有一个实例,而且其完整生命周期完全由 Spring 容器管理。对于所有获取该 Bean 的操作 Spring 容器将只返回同一个 Bean。

需要注意的是,若一个 Bean 未指定 scope 属性,默认也为 singleton 。

  • 在配置文件中定义:
<bean id="animals" class="com.demo.Animals" scope="singleton" />
  • 调用验证:
String location = ...ApplicationContext factory = new FileSystemXmlApplicationContext(location);// 获取 BeanAnimals animals = (Animals) factory.getBean("animals");Animals animals2 = (Animals) factory.getBean("animals");System.out.println(animals);System.out.println(animals2);//输出结果://com.demo.Animals@2151b0a5//com.demo.Animals@2151b0a5

观察输出结果,发现多次获取 Bean,返回的都是同一个 Bean,再次验证了其在 Spring IoC 容器中有且只有一个实例。


2.prototype

prototype,也称原型作用域。每次向 Spring IoC 容器请求获取 Bean 都返回一个全新的Bean。相对于 singleton 来说就是不缓存 Bean,每次都是一个根据 Bean 定义创建的全新 Bean。

  • 在配置文件中定义:
<bean id="animals" class="com.demo.Animals" scope="prototype" />
  • 调用验证,参照 singleton 的例子,观察输出结果,发现每次调用返回不同的实例。

Web 作用域

1.reqeust

request,表示每个请求需要容器创建一个全新Bean。

在 Spring IoC 容器,即XmlWebApplicationContext 会为每个 HTTP 请求创建一个全新的 RequestPrecessor 对象。当请求结束后,该对象的生命周期即告结束。当同时有 10 个 HTTP 请求进来的时候,容器会分别针对这 10 个请求创建 10 个全新的 RequestPrecessor 实例,且他们相互之间互不干扰,从不是很严格的意义上说,request 可以看做 prototype 的一种特例,除了场景更加具体之外,语意上差不多。

  • 在配置文件中定义:
<bean id="animals" class="com.demo.Animals" scope="request" />
  • 调用验证(这里以 SpringMVC 为例,模拟了不同的两个请求)
@RequestMapping(value = "/hello1")public void hello1(HttpServletRequest request) throws Exception {    // 在 Web 程序取得 Ioc 容器    ApplicationContext context = (ApplicationContext) WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());    Animals  animals1 = (Animals) context.getBean(Animals.class);    animals1.setName("animals");    Animals  animals2 = (Animals) context.getBean(Animals.class);    System.out.println(animals1.getName());    System.out.println(animals2.getName());    // 输出结果:    // animals    // animals}@RequestMapping(value = "/hello2")public void hello2(HttpServletRequest request) throws Exception {    ApplicationContext context = (ApplicationContext) WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());    Animals  animals = (Animals) context.getBean(Animals.class);    System.out.println(animals1.getName());    // 输出结果:    // null}

观察输出结果,发现在同一个请求中(hello1 )多次获取 Bean 返回的是同一个实例,而在不同请求(hello2)中获取 Bean 返回的是不同的 Bean。


2.session

session,表示每个会话需要容器创建一个全新 Bean。比如对于每个用户一般会有一个会话,该用户的用户信息需要存储到会话中,此时可以将该 Bean 配置为 web 作用域。

  • 在配置文件中定义:
<bean id="animals" class="com.demo.Animals" scope="session" />
  • 调用验证(使用不同的浏览器发起该请求)
@RequestMapping(value = "/hello1")public void hello1(HttpServletRequest request) throws Exception {    // 在 Web 程序取得 Ioc 容器    ApplicationContext context = (ApplicationContext) WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());    Animals  animals1 = (Animals) context.getBean(Animals.class);    animals1.setName("animals");    Animals  animals2 = (Animals) context.getBean(Animals.class);    System.out.println(animals1);    System.out.println(animals2);}

观察输出结果,会发现不同浏览器(不同会话)返回的 Bean 实例不同,而同一个浏览器(同一会话)多次发起请求返回的是同一个 Bean 实例。


3.globalSession

globalSession,类似于session 作用域,只是其用于 portlet 环境的 web 应用。如果在非portlet 环境将视为 session 作用域。


自定义作用域

1.实例探究

自定义作用域,需要实现 Scope 接口。

首先来看该接口的定义:

public interface Scope {    // 从作用域中获取Bean, objectFactory 表示当在当前作用域没找到合适Bean时使用它创建一个新的Bean    Object get(String name, ObjectFactory<?> objectFactory);    // 从作用域中移除 Bean    Object remove(String name);    // 用于注册销毁回调,如果想要销毁相应的对象则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象;    void registerDestructionCallback(String name, Runnable callback);    // 用于解析相应的上下文数据,比如request作用域将返回request中的属性。    Object resolveContextualObject(String key);    // 作用域的会话标识,比如session作用域将是sessionId。    String getConversationId();}

接下来看看如何使用自定义的作用域

  • 自定义作用域 ThreadScope,表示 Bean 作用范围为同一个线程:
public class ThreadScope implements Scope {    // 用于存放线程中的 Bean    private final ThreadLocal<Map<String, Object>> THREAD_SCOPE = new ThreadLocal<Map<String, Object>>() {        protected Map<String, Object> initialValue() {            return new HashMap<String, Object>();        }    };    @Override    public Object get(String name, ObjectFactory<?> objectFactory) {        Map<String, Object> map = THREAD_SCOPE.get();        if (!map.containsKey(name)) {            map.put(name, objectFactory.getObject());        }        return map.get(name);    }    @Override    public Object remove(String name) {        Map<String, Object> map = THREAD_SCOPE.get();        return map.remove(name);    }    @Override    public void registerDestructionCallback(String name, Runnable callback) {    }    @Override    public Object resolveContextualObject(String key) {        return null;    }    @Override    public String getConversationId() {        return null;    }}
  • 在配置文件定义:
<!-- CustomScopeConfigurer 的 scopes 属性注册自定义作用域实现 --><bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">    <property name="scopes">        <map>            <entry key="thread" >                <bean class="scope.ThreadScope"/>            </entry>        </map>    </property></bean>  <bean  id="animals" class="com.demo.Animals"  scope="thread"/>
  • 调用验证:
public static void main(String [ ] args) {    String location = "WebRoot/WEB-INF/spring-bean.xml";    ApplicationContext factory = new FileSystemXmlApplicationContext(location);    // 在 main 线程获取 Bean     Animals animals1 = (Animals) factory.getBean("animals");    Animals animals2 = (Animals) factory.getBean("animals");    System.out.println(animals1);    System.out.println(animals2);    // 新建线程获取 Bean    Thread thread = new Thread() {        public void run() {            String location = "WebRoot/WEB-INF/spring-bean.xml";            ApplicationContext factory = new FileSystemXmlApplicationContext(location);            Animals animals =  (Animals) factory.getBean("animals");            System.out.println(animals);        }    };    thread.start();    // 输出结果:    // com.demo.Animals@3ddfd90f    // com.demo.Animals@3ddfd90f    // com.demo.Animals@153b2cb}

观察输出结果,发现同一线程多次获取 Bean 返回的是同一个实例,而不同线程获取 Bean 返回的是不同实例。


2.原理探究

这里以自定义作用域为例,探究下 scope 的基本原理。在 Spring 中对于 Bean 的 scope(作用域)的检查发生在【获取 Bean】的过程中。获取方法如下:

Animals animals1 = (Animals) factory.getBean("animals");

由此可见,获取 Bean 的入口在 BeanFacotry 中定义。而 BeanFacotry 的基本功能实现都在它的基本实现类 AbstractBeanFactory 中,具体的调用过程这里不再探究,简单的调用流程如下: getBean -> doGetBean。因此这里重点来看下 doGetBean 这个方法:

protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object [ ] args, boolean typeCheckOnly) throws BeansException {    final String beanName = transformedBeanName(name);    // 省略部分源码...        try {            // 省略部分源码...            // 判断 scope 作用域            // 若既不是 singleton 也不是 prototype,表明该 Bean 的作用域是自定义作用域或 web 作用域            if (mbd.isSingleton()) {                // 省略部分源码...            }else if (mbd.isPrototype()) {                // 省略部分源码...            }else {                // 取得 Bean 的 scope 名称,这里指 thread                String scopeName = mbd.getScope();                // 取得在 CustomScopeConfigurer 中定义的 scope 对象,这里指 ThreadScope 对象                final Scope scope = this.scopes.get(scopeName);                if (scope == null) {                    // 抛出异常...                }                try {                    // 调用 scope 的 get 方法                    Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {                        // 若不存在该标识的 bean,则触发 map.put(name, objectFactory.getObject()) 的 getObject 方法                        @Override                        public Object getObject() throws BeansException {                            beforePrototypeCreation(beanName);                            try {                                // 创建 Bean 实例                                 return createBean(beanName, mbd, args);                            } finally {                                afterPrototypeCreation(beanName);                            }                        }                    });                    bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);                } catch (IllegalStateException ex) {                    // 抛出异常...                }            }        } catch (BeansException ex) {            cleanupAfterBeanCreationFailure(beanName);            throw ex;        }    }    // 省略部分源码...}

参考

  • http://jinnianshilongnian.iteye.com/blog/1415463
0 0
原创粉丝点击