04、IoCContainer-4 Bean作用域(Bean Scopes)

本文详细解析了Spring框架中的六种Bean作用域,包括singleton、prototype、request、session、application和websocket,以及如何在XML和注解配置中定义它们。同时介绍了在不同作用域下Bean的生命周期和管理方式。

Bean作用域(Bean Scopes)

  1. Spring总的6种作用域

    作用域描述
    singleton(默认作用域)在每个Spring容器中,一个bean定义只有一个对象实例
    prototype一个bean定义可以有任意多个对象实例
    request一次HTTP请求中,一个bean定义对应一个对象实例。该作用域只在基于Web的Spring上下文中有效
    session在一个HTTP Session 中,一个bean定义对应一个对象实例。该作用域只在基于Web的Spring上下文中有效
    application在一个ServeletContext中,一个bean定义对应一个对象实例。该作用域只在基于Web的Spring上下文中有效
    websocket在一个Websocket中,一个bean定义对应一个对象实例。该作用域只在基于Web的Spring上下文中有效
  2. Singleton Scope

    如果一个bean被定义为singleton,那么这个bean被实例化后存储在一个singletonbean缓存中,接下来所有对该bean名称的请求或者引用,容器都会返回那个存储在缓存中的实例。

     
    5893832-6f279c1e36809ef8.png
    Singleton Scope的工作模式

     

    我们可以像如下这样定义singletonbean:

     <bean id="accountService" class="com.something.DefaultAccountService"/>
    
     <!-- the following is equivalent, though redundant (singleton scope is the default) -->
     <bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
    
  3. Prototype Scope

    对于prototype scope的bean来说,每一次对该bean的请求,容器都将创建一个新的实例。

    无状态bean使用singleton scope,为有状态bean使用prototype scope

    无状态bean:没有实例域的对象,不会有状态的改变。

    有状态bean:有实例域的对象,这些实例域是线程不安全的,所以每次使用都应该创建新的实例。

     
    5893832-13c9788e5cbd3ca0.png

    下面定义了一个prototype scopebean

    <bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
    

    同其它的作用域不同,Spring不会在prototypebean的整个生命周期都管理整个bean,在容器实例化、配置和装配整个bean后,就将这个bean交给了客户端,不会记录这个实例域。
    所以,prototypebean的摧毁生命周期回调方法无法被调用,客户端应该负责 清除这个实例并释放它锁占有的资源。可以使用org.springframework.beans.factory.config.BeanPostProcessor接口来实现资源的释放。

  4. Rquest、Session、Application,and WebSocket Scopes

    这几中作用域只在基于Web的Spring上下文中有效,如果在一个普通的Spring上下文中(如ClassPathXmlApplicationContext),使用这些作用域,将会抛出IllegalStateExcetion

    • 4.1 Web初始配置

      完成Web初始设置需要取决于我们使用的Servlet环境。

      • 如果我们访问的bean是在Spring Web MVC 中(事实上是在被SpringDespatcherServlet处理的一次请求中),那么就不需要做任何特殊的设置,DespatcherServlet已经暴露了所有相关的内容。
      • 如果我们使用的是Servlet 2.5 web容器,且请求不被Spring的DespatcherServlet处理,那么我们需要注册org.springframework.web.context.request.RequestContextListener ServletRequestListener.
        对于Servlet 3.0+ web容器,注册ServletRequestListener可以通过实现WebApplicationInitializer接口来编程实现。不然像老的容器一样,在web.xml文件中进行注册:
       <web-app>
         ...
         <listener>
             <listener-class>
                 org.springframework.web.context.request.RequestContextListener
             </listener-class>
         </listener>
         ...
       </web-app>
      
      • 另外,如果我们的监听器设置有问题的话,考虑使用RequestContextFilter。过滤器的映射取决于我们的web应用配置,所以filter-mapping应该设置成合适的值,下面是一个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>
      

      DespatcherServletRequestContextListenerRequestContextFilter事实上作用相同,就是将HTTP请求对象绑定到响应这个请求的线程上。

    • 4.2 Request Scope

      <bean id="loginAction" class="com.something.LoginAction" scope="request"/>
      

      上面定义了一个request作用域的bean,这个bean的实例在每一次HTTP请求这个对象时都会被创建一次,我们可以修改实例的状态,因为每一个实例都是独立的属于某一次请求的。当请求处理完成后,对应的实例就会被丢弃。

      基于注解的配置中,使用@RequestScope,指定一个组件(Component)的作用域为request:

      @RequestScope
      @Component
      public class LoginAction {
         // ...
      }
      
    • 4.3 Session Scope

      <bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
      

      上面定义了一个session作用域的bean,这个bean的实例在每一次建立HTTP会话时这个对象时都会被创建一次,我们可以修改实例的状态,因为每一个实例都是独立的属于某一次会话的。当会话结束后,实例就会被丢弃。

      基于注解的配置中,使用@SessionScope,指定一个组件(Component)的作用域为session:

      @SessionScope
      @Component
      public class UserPreferences {
          // ...
      }
      
    • 4.4 Application Scope

      <bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
      

      容器为整个web应用创建一个AppPreferencesbean实例。这意味着appPreferencesbean作用域在ServletContext层,当成ServletContext的属性被存储。这有点像Spring的单例bean,但有两点不同:
      一是,appPreferencesbean在每个ServletContext中是单例,而不是在每个ApplicationContext中;二是appPreferencesbean被当做ServletContext的属性而暴露出来。

      基于注解的配置中,使用@ApplicationScope,指定一个组件(Component)的作用域为application:

      @ApplicationScope
      @Component
      public class AppPreferences {
          // ...
      }
      
    • 4.5 有作用域的bean作为依赖

      如果我们想将一个(比如)request作用域bean注入到一个有更长生命周期作用域的bean中时,我们应该使用AOP代理来替换这个bean。我们将与目标bean具有相同接口的代理对象注入,但是这个代理对象也可以从相关作用域检索真正的目标对象,并将方法调用委托给真正的对象。

      <?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">
      
          <!-- an HTTP Session-scoped bean exposed as a proxy -->
          <bean id="userPreferences" class="com.something.UserPreferences" scope="session">
              <!-- instructs the container to proxy the surrounding bean -->
              <aop:scoped-proxy/>
          </bean>
      
          <!-- a singleton-scoped bean injected with a proxy to the above bean -->
          <bean id="userService" class="com.something.SimpleUserService">
              <!-- a reference to the proxied userPreferences bean -->
              <property name="userPreferences" ref="userPreferences"/>
          </bean>
      </beans>
      

      在上面的配置中,singleton-scopedbeanuserService注入了一个session-scopedbean userPreferencesuserService只在容器初始化时被实例化一次,这意味着它的依赖注入也只会在容器初始化时进行。而它所依赖的userPreferences在每一个HTTP Session中都会被实例化。所以,我们需要将com.something.UserPreferences的代理类注入userService

      • 容器在初始化时创建userServicebean,并创建一个UserPreferences的代理类,将其注入userService.
      • userService实例调用userPreferences方法时,它其实调用的是UserPreferences代理类对象的方法,这个方法从HTTP Session 中取到真正的UserPreferences对象实例,然后将方法的调用委托给真正对象。
    • 4.6 选择代理类的类型

      默认的,使用<aop:scoped-proxy/>元素配置而生成的代理类是基于CGLIB的,CGLIB代理类只拦截公共的方法,非公共的方法不会被拦截。

      所以,我们可以设置生成标准的基于接口的JDK代理。

      <!-- DefaultUserPreferences implements the UserPreferences interface -->
      <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>
      

      使用基于接口的JDK代理意味着我们不在需要额外的包来生成代理,同时也意味着com.stuff.DefaultUserPreferences至少需要实现一个接口,并且com.stuff.UserManageruserPreferences属性的类型必须是com.stuff.DefaultUserPreferences所实现的一个接口(这里是UserPreferences

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值