• 后端——框架——容器框架——spring_core——《官网》阅读笔记——第一章节3(Bean的作用域)


      作用域分为两类,第一类为框架内置的作用域,第二类为自定义的作用域。

    1、内置作用域

    1.1   Singleton

      对于同一个IOC容器,每次获取对象,都返回相同的实例。它是默认的作用域

    /**
     * 
     * @Title: testUserScope
     * @Description: 测试User对象的作用域
     */
    public static void testUserScope() {
    	// 获取applicationContext对象
    	ApplicationContext ac = getContext();
    	// 获取User实例
    	User user1 = ac.getBean("singleton_user",User.class);
    	User user2 = ac.getBean("singleton_user",User.class);
    	System.out.println(user1 == user2);// 返回true
    }
    

      与单例模式不同,单例模式是指在每个JVM中只存在一个实例,而这里是指每个IOC容器,即每一个applicationContext对象。

    1.2   Prototype

      对于同一个IOC容器,每次获取对象,都会返回不同的实例

    // 获取prototype的User实例
    User user3 = ac.getBean("prototype_user",User.class);
    User user4 = ac.getBean("prototype_user",User.class);
    System.out.println(user3 == user4); // 返回false
    

      当singleton的beanA依赖singleton的beanB时,beanA和beanB都只会创建一次,并且beanB在beanA之前创建

      当singleton的beanA依赖prototype的beanB时,理论上每次获取beanA都应该返回不同的beanB,但实际上每次获取得到的beanB是相同的,是因为对象beanA只会创建一次,依赖关系在创建过程中已经决定。

    // 获取User实例
    User user1 = ac.getBean("singleton_user",User.class);
    User user2 = ac.getBean("singleton_user",User.class);
    System.out.println(user1.getHomeAddress() == user2.getHomeAddress());
    

      当prototype的beanA依赖prototype的beanB时,每次获取都会创建beanA和beanB,并且beanB在beanA之前创建。

           当prototype的beanA依赖singleton的beanB时,每次获取都会创建beanA,如果beanB不存在,则创建beanB,在beanA之前创建。之后都会使用已创建的beanB。

    1.3  方法注入

      当singleton的beanA依赖prototype的beanB时,要达到每次获取beanB不同,只能通过方法注入。本示例中User类为beanA,Address类为beanB。

      第一种方法注入方式,beanA实现ApplicationContextAware接口,在获取beanB时通过applicationContext对象获取

    public class User implements ApplicationContextAware {
    	// applicationContext对象,
    	private ApplicationContext applicationContext;
    	// 用户的家庭地址
    	private Address homeAddress;
        // 通过applicationContext获取,不再是this.homeAddress
    	public Address getHomeAddress() {
    		return this.applicationContext.getBean("address", Address.class);
    	}
    	public void setApplicationContext(ApplicationContext arg0) throws BeansException {
    		this.applicationContext = arg0;
    	}
    }
    

      第二种方式使用lookup-method子标签,此时该方法只能是抽象方法

    public abstract Address getHomeAddress();
    

      在配置文件中,添加如下配置

    <bean id="address" class="com.bean.Address" scope="prototype"/>
    <!-- 定义User类,它的作用域为singleton -->
    <bean id="singleton_user" class="com.bean.User">
    	<lookup-method name="getHomeAddress" bean="address"/>
    </bean>
    

      其中name属性为方法的名称,方法的返回值为Address类型。Bean属性值为Address类的bean ID。

      其实无论哪种方式,都存在缺点,但个人建议采用第一种方式,因为第二种必须包含抽象方法,类必须为抽象类。

    1.4  request

      request的作用域只有在Web application下才会有效果。在使用之前,需要将HttpRequest对象与处理请求的线程进行关联。关联的方式有三种

    1. 如果使用spring mvc框架,无需任何额外配置,只需要配置DispatcherServlet。在Servlet 3.0以下,在web.xml中配置DispatcherServlet,在Servlet 3.0以上,实现WebApplicationInitializer接口,具体的代码参考mvc官网。
    2. 添加RequestContextFilter。
    3. 添加RequestContextListener。

    1.5  Session & Application & webSocket

      Session也只有在web application下才有用,对应Session的作用域。

      Application对应web application的Application作用域。HttpRequest,Session,Application这些都是Servlet的九大内置对象。

      webSocket对应webSocket的作用域。

    1.6   代理

      当短周期的bean A依赖长周期的bean B时,此时bean B 在bean A的生命周期中是一直存在的。

      当长周期的bean A依赖短周期的bean B时,此时bean B在其生命周期结束后,由于有bean A的引用,所以不会被垃圾回收,所以造成在bean A的整个生命周期中,一直引用同一个bean B,相当于延长了bean B的生命周期。这种情况是不允许出现的。例如Application应用中session对象一直不被回收,或者引用的是同一个session,后果是可想而知的。在这种情况下需要使用代理。

      代理的实现方式有两种,JDK和CGLIb,JDK方式下被代理类必修实现某个接口,否则会出错,所以一般使用CGLib。它的配置如下:

    <!-- 定义Address类,它的作用域为request -->
    <bean id="address" class="com.bean.Address" scope="request">
    	<aop:scoped-proxy/>
    </bean>
    <!-- 定义User类,它的作用域为session -->
    <bean id="session_user" class="com.bean.User" scope="session" >
    	<property name="homeAddress" ref="address"/>
    </bean>
    

      当aop:scoped-proxy的proxy-target-class属性为false时,为JDK方式。属性的默认值为true。

      默认情况下,代理类会被删除,如果需要深入了解,可以查看代理究竟产生哪些class,以及它们的源码。

      通过设置DEBUG_LOCATION_PROPERTY属性,代理类编译后的class都会存放在D:code目录下。

    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\code");
    

    2、自定义作用域

      编写自定义作用域的步骤只有两步:

    1. 编写自定义XXScope,实现Scope接口
    2. 注册XXScope,通过ConfigurableBeanFactory的registerScope方法,它的第一个参数为作用域的名称,第二个参数为作用域对象。

      Scope接口如下,Spring框架中有simpleThreadScope,但是没有注册,可以尝试注册线程作用域作为练习。

    public interface Scope {
    	/**
    	 * 
    	 * @Title: get
    	 * @Description: 从当前作用域中获取bean对象
    	 * @param name bean的标识,ID,name或alias
    	 * @param objectfactory
    	 * @return
    	 */
    	Object get(String name, ObjectFactory objectfactory);
    
    	/**
    	 * 
    	 * @Title: remove
    	 * @Description: 从当前作用域中移除bean对象
    	 * @param name
    	 * @return
    	 */
    	Object remove(String name);
    
    	/**
    	 * 
    	 * @Title: registerDestructionCallback
    	 * @Description: 当对象被销毁时,调用runnable中的execute方法,相当于在销毁过程中,注入回调函数
    	 * @param name
    	 * @param runnable
    	 */
    	void registerDestructionCallback(String name, Runnable runnable);
    	/**
    	 * 
    	 * @Title: resolveContextualObject  
    	 * @Description: 获取当前上下文中的变量,它们以key-value形式存在。
    	 * @param key  变量的key
    	 * @return
    	 */
    	Object resolveContextualObject(String key);
    	/**
    	 * 
    	 * @Title: getConversationId  
    	 * @Description: 获取作用域对象的唯一标识,当为session对象时获取sessionId,当为Thread时,获取PID
    	 * @return
    	 */
    	String getConversationId();
    }
  • 相关阅读:
    FusionCharts ScrollColumn2D图
    Java Web项目部署Tomcat运行出错
    Eclipse部署Java Web项目到Tomcat出错
    JavaScript过滤特殊字符
    pl/sql 在一个程序块里打印日志输出到表格
    Java中过滤出字母、数字和中文的正则表达式
    pl/sql 程序块里打印问题
    C++函数的Boost内存池性能介绍
    boost内存池的使用介绍
    内存管理 Boost::singleton_pool
  • 原文地址:https://www.cnblogs.com/rain144576/p/12272496.html
Copyright © 2020-2023  润新知