Bean配置文件之scope元素

前言:

  • Spring容器在创建bean对象时,一般默认创建的都是单例对象,在整个容器中仅仅只存在一个该类型的bean对象
  • 若有需求是要求每次调用的bean对象都是不同的bean对象,则需要配置作用域为prototype
  • 在Spring Web容器中,作用域则有request、session、application三种类型
  • 有时候,Spring容器内置的作用域并不能满足我们的需求,则可以通过自定义作用域来扩展

案例源码:码云仓库的base-004

1、Scope属性

  • 基本格式:

    <bean id="bean名称" class="完整类路径" scope="作用域" />
    
    • scope值:有singleton、prototype、request、session、application等,默认值为singleton。而request、session、application三者皆在Spring Web容器中才存在

2、作用域singleton

  • 基本格式:

    <bean id="bean名称" class="完整类路径" scope="singleton" />
    
  • scope值为singleton时,Spring容器创建的bean对象为单例对象,在该容器仅且只存在一个该类型的bean对象

  • Spring容器在启动时,会自动创建好单例对象,放在容器中,以供使用。注意若bean元素的lazy值为true时,即懒加载,bean对象的创建则是在被使用时才创建,与作用域值为prototype时相同

  • 案例

    • 实体类

      package com.spring.study;
      
      public class BeanScope {
          /**
           * bean对象的作用域的值,用于打印Spring容器创建bean对象的方式--单例 or 多例
           */
          private String beanScope;
      
          public String getBeanScope() {
              return beanScope;
          }
      
          public void setBeanScope(String beanScope) {
              this.beanScope = beanScope;
          }
      
          public BeanScope(String beanScope) {
              this.beanScope = beanScope;
              System.out.println(String.format("正在创建bean对象{作用域=%s,内存地址=%s}",beanScope,this));
          }
      }
      
    • 配置文件

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
      
          <!--    单例对象,scope值为singleton-->
          <bean id="beanScope1" class="com.spring.study.BeanScope" scope="singleton">
              <constructor-arg index="0" value="singleton" />
          </bean>
      
      </beans>
      
    • 测试类

      package com.spring.test;
      
      import org.junit.BeforeClass;
      import org.junit.Test;
      import org.springframework.context.support.ClassPathXmlApplicationContext;
      
      public class TestSingleton {
          /**
           * ClassPathXmlApplicationContext容器
           */
          static ClassPathXmlApplicationContext context;
      
          /**
           * junit提供--@BeforeClass:初始化方法,在所有的方法被调用之前仅且只执行一次
           * 构建Spring容器,初始化bean对象
           */
          @BeforeClass
          public  static void createBean(){
              System.out.println("Spring容器准备启动......");
      
              // 1、定义bean配置文件
              String classPathXml = "classpath:applicationContext.xml";
      
              // 2、创建ClassPathXmlApplicationContext容器,加载配置文件
              context = new ClassPathXmlApplicationContext(classPathXml);
      
              System.out.println("Spring容器启动完毕!!!");
          }
      
          /**
           * junit提供--@Test:测试方法
           * 用于测试
           */
          @Test
          public void printResult(){
      
              // 3、获取所有bean对象的bean名称
              String[] beanNames = context.getBeanDefinitionNames();
      
              // 4、多次获取bean对象,并打印输出bean对象的内存地址,来确认调用的是否是同一个对象
              for (String beanName : beanNames){
                  for (int i = 1; i <= 2; i++){
                      System.out.println(String.format("第%d次调用%s对象,其内存地址:%s",i,beanName,context.getBean(beanName)));
                  }
              }
      
          }
      
      }
      
    • 运行结果

      Spring容器准备启动......
      正在创建bean对象{作用域=singleton,内存地址=com.spring.study.BeanScope@29b5cd00}
      Spring容器启动完毕!!!
      第1次调用beanScope1对象,其内存地址:com.spring.study.BeanScope@29b5cd002次调用beanScope1对象,其内存地址:com.spring.study.BeanScope@29b5cd00
      
      • 从打印结果来看,单例对象的初始化,是在Spring容器启动阶段完成的。多次调用的该类型对象,都是同一个对象

3、作用域prototype

  • 基本格式:

    <bean id="bean名称" class="完整类路径" scope="prototype" />
    
  • scope值为prototype时,Spring容器创建的bean对象为多例对象,每次从容器获得的该类型对象都是不同的,因为容器每次都会重新创建该类型的对象

  • 案例

    • 实体类同上例的BeanScope类

    • 配置文件

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
      
          <!--    多例对象,scope值为prototype-->
          <bean id="beanScope2" class="com.spring.study.BeanScope" scope="prototype">
              <constructor-arg index="0" value="prototype"/>
          </bean>
      </beans>
      
    • 测试类同上例的TestSingleton类

    • 运行结果

      Spring容器准备启动......
      Spring容器启动完毕!!!
      正在创建bean对象{作用域=prototype,内存地址=com.spring.study.BeanScope@64485a47}1次调用beanScope2对象,其内存地址:com.spring.study.BeanScope@64485a47
      正在创建bean对象{作用域=prototype,内存地址=com.spring.study.BeanScope@25bbf683}2次调用beanScope2对象,其内存地址:com.spring.study.BeanScope@25bbf683
      
      • 从运行结果来看,多例对象的初始化,是在被调用时才创建的。且每次都会重新创建新的对象

4、作用域request、session、application

  • request

    • 基本格式:

      <bean id="bean名称" class="完整类路径" scope="request" />
      
    • 存在环境:Spring容器的web环境

    • 当scope值为request时,该容器会针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效

  • session

    • 基本格式:

      <bean id="bean名称" class="完整类路径" scope="session" />
      
    • 存在环境:Spring容器的web环境

    • 当scope值为session时,每个会话会对应一个bean实例,不同的session对应不同的bean实例

  • application

    • 基本格式:

      <bean id="bean名称" class="完整类路径" scope="application" />
      
    • 存在环境:Spring容器的web环境

    • 当scope值为application时,一个web应用程序对应一个bean实例,通常情况下和singleton效果类似的。但是也有区别:singleton是每个Spring容器中只有一个bean实例,不同的Spring容器中可以有同名的bean对象;而application是所有的Spring容器中仅且只有一个bean对象,不可以存在其它同名的bean对象

5、自定义作用域

  • 若内置的作用域不能满足需求的时候,可以自定义作用域

  • 实现步骤:

    • 第一步:实现org.springframework.beans.factory.config.Scope 接口,以此来自定义作用域

      package org.springframework.beans.factory.config;
      
      import org.springframework.beans.factory.ObjectFactory;
      import org.springframework.lang.Nullable;
      
      public interface Scope {
          
          /**
          * 返回当前作用域中name对应的bean对象
          * name:需要检索的bean名称
          * objectFactory:如果name对应的bean对象在当前作用域中没有找到,那么可以调用这个ObjectFactory来创建这个对象
          **/
          Object get(String name, ObjectFactory<?> objectFactory);
          
          /**
           * 将name对应的bean对象从当前作用域中移除
           **/
          @Nullable
          Object remove(String name);
          
          /**
           * 用于注册销毁回调,如果想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象
           */
          void registerDestructionCallback(String name, Runnable callback);
          
          /**
           * 用于解析相应的上下文数据,比如request作用域将返回request中的属性。
           */
          @Nullable
          Object resolveContextualObject(String key);
          
          /**
           * 作用域的会话标识,比如session作用域将是sessionId
           */
          @Nullable
          String getConversationId();
      }
      
    • 第二步:将自定义作用域注册到容器中,需要用到org.springframework.beans.factory.config.ConfigurableBeanFactory的registerScope方法

      /**
      * 向容器中注册自定义的Scope
      * scopeName:作用域名称
      * scope:作用域对象
      */
      void registerScope(String scopeName, Scope scope);
      
    • 第三步:在配置文件中,指定bean元素的scope值为第二步注册到容器的自定义作用域名称scopeName

      <bean id="bean名称" class="完整类路径" scope="自定义作用域名称"/>
      
  • 自定义作用域的关键在于Scope接口的get方法中是如何处理返回bean对象的

  • 案例

    • 需求:采用多线程来访问容器中的bean对象,实现不同线程的bean对象是不同的对象,相同线程的bean对象是相同的对象。并与singleton作对比

    • 实体类为BeanScope类

    • 自定义作用域类

      package com.spring.study;
      
      import org.springframework.beans.factory.ObjectFactory;
      import org.springframework.beans.factory.config.Scope;
      
      import java.util.HashMap;
      import java.util.Map;
      
      public class ThreadScope implements Scope {
      
          /**
           *  自定义作用域Scope名称常量
           */
          public static final String THREAD_SCOPE = "thread_scope";
      
          /**
           * 初始化线程局部变量
           */
          private final ThreadLocal<Map<String,Object>> threadLocal = new ThreadLocal(){
              @Override
              protected Object initialValue() {
                  System.out.println("正在初始化线程局部变量ThreadLocal......");
                  return new HashMap<>();
              }
          };
      
          /**
           * 返回当前作用域中name对应的bean对象
           * @param name
           * @param objectFactory
           * @return
           */
          @Override
          public Object get(String name, ObjectFactory<?> objectFactory) {
              System.out.println(String.format("正在获取该作用域中的%s对象......",name));
      
              Object bean = threadLocal.get().get(name);
              if (bean == null){
                  System.out.println(String.format("%s对象不存在,正在创建中......",name));
                  bean = objectFactory.getObject();
                  threadLocal.get().put(name,bean);
                  System.out.println(String.format("%s对象创建完毕!!!",name));
              }
      
              System.out.println(String.format("已获取到%s对象,over!!!",name));
              return bean;
          }
      
          @Override
          public Object remove(String name) {
              return null;
          }
      
          @Override
          public void registerDestructionCallback(String name, Runnable callback) {
      
          }
      
          @Override
          public Object resolveContextualObject(String key) {
              return null;
          }
      
          @Override
          public String getConversationId() {
              return null;
          }
      }
      
    • 测试类(在测试类中将自定义作用域注册到Spring容器中)

      package com.spring.test;
      
      import com.spring.study.ThreadScope;
      import org.junit.Before;
      import org.junit.Test;
      import org.springframework.beans.BeansException;
      import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
      import org.springframework.context.support.ClassPathXmlApplicationContext;
      
      import java.util.concurrent.TimeUnit;
      
      public class TestThreadScope {
      
          /**
           * ClassPathXmlApplicationContext容器
           */
          ClassPathXmlApplicationContext context;
      	
          /**
           * 在测试方法之前执行此方法
           */
          @Before
          public void registerThreadScope(){
              
              System.out.println("Spring容器准备启动......");
      
              // 1、设置配置文件的位置
              String classPathXml = "classpath:applicationContext.xml";
      
              // 2、手动创建容器
              context = new ClassPathXmlApplicationContext(){
                  // 向容器中注册自定义的作用域
                  @Override
                  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
                      beanFactory.registerScope(ThreadScope.THREAD_SCOPE,new ThreadScope());
                      super.postProcessBeanFactory(beanFactory);
                  }
              };
      
              // 3、设置配置文件位置
              context.setConfigLocation(classPathXml);
      
              // 4、启动容器
              context.refresh();
              
              System.out.println("Spring容器启动完毕!!!");
          }
      	
          /**
           * 测试方法
           * @throws InterruptedException
           */
          @Test
          public void ThreadScopeTest() throws InterruptedException {
              
              // 获取容器中所有的bean对象的bean名称
              String[] beanNames = context.getBeanDefinitionNames();
      
              for (String beanName : beanNames) {
                  for (int i = 0; i < 2; i++) {
                      new Thread(() -> {
                          for (int j = 1; j <= 2; j++) {
                              System.out.println(String.format("当前线程:%s,第%d次获取%s对象,内存地址:%s", Thread.currentThread(), j, beanName,context.getBean(beanName)));
                          }
                      }).start();
      				//间隔1秒
                      TimeUnit.SECONDS.sleep(1);
                  }
              }
      
          }
      }
      
    • 配置文件

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
      
          <!--    单例对象,scope值为singleton-->
          <bean id="beanScope1" class="com.spring.study.BeanScope" scope="singleton">
              <constructor-arg index="0" value="singleton" />
          </bean>
      	
          <!--    自定义作用域-->
          <bean id="threadScope" class="com.spring.study.BeanScope" scope="thread_scope">
              <constructor-arg index="0" value="thread_scope"/>
          </bean>
      </beans>
      
    • 运行结果

      Spring容器准备启动......
      正在创建bean对象{作用域=singleton,内存地址=com.spring.study.BeanScope@5f3a4b84}
      Spring容器启动完毕!!!
      当前线程:Thread[Thread-1,5,main],第1次获取beanScope1对象,内存地址:com.spring.study.BeanScope@5f3a4b84
      当前线程:Thread[Thread-1,5,main],第2次获取beanScope1对象,内存地址:com.spring.study.BeanScope@5f3a4b84
      当前线程:Thread[Thread-2,5,main],第1次获取beanScope1对象,内存地址:com.spring.study.BeanScope@5f3a4b84
      当前线程:Thread[Thread-2,5,main],第2次获取beanScope1对象,内存地址:com.spring.study.BeanScope@5f3a4b84
      正在获取该作用域中的threadScope对象......
      正在初始化线程局部变量ThreadLocal......
      threadScope对象不存在,正在创建中......
      正在创建bean对象{作用域=thread_scope,内存地址=com.spring.study.BeanScope@43b3a2ba}
      threadScope对象创建完毕!!!
      已获取到threadScope对象,over!!!
      当前线程:Thread[Thread-3,5,main],第1次获取threadScope对象,内存地址:com.spring.study.BeanScope@43b3a2ba
      正在获取该作用域中的threadScope对象......
      已获取到threadScope对象,over!!!
      当前线程:Thread[Thread-3,5,main],第2次获取threadScope对象,内存地址:com.spring.study.BeanScope@43b3a2ba
      正在获取该作用域中的threadScope对象......
      正在初始化线程局部变量ThreadLocal......
      threadScope对象不存在,正在创建中......
      正在创建bean对象{作用域=thread_scope,内存地址=com.spring.study.BeanScope@2432e026}
      threadScope对象创建完毕!!!
      已获取到threadScope对象,over!!!
      当前线程:Thread[Thread-4,5,main],第1次获取threadScope对象,内存地址:com.spring.study.BeanScope@2432e026
      正在获取该作用域中的threadScope对象......
      已获取到threadScope对象,over!!!
      当前线程:Thread[Thread-4,5,main],第2次获取threadScope对象,内存地址:com.spring.study.BeanScope@2432e026
      
      • 从运行结果来看
        • 自定义的作用域与singleton作用域的相同点:在同一个线程中,两者从容器中多次获取的同一个bean对象都是同一个对象
        • 自定义的作用域与singleton作用域的不同点:在不同线程中,自定义作用域从容器中多次获取的同一个bean对象是不同的对象,而singleton作用域从容器中多次获取的同一个bean对象始终是是同一个对象

参考:Java充电社笑得好虚伪

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值