使用@Scope注解设置组件的作用域

前言

Spring容器中的组件默认是单例的,在Spring启动时就会实例化并初始化这些对象,并将其放到Spring容器中,之后,每次获取对象时,直接从Spring容器中获取,而不再创建对象。

1.@Scope注解概述

@Scope注解能够设置组件的作用域,Scope注解类的源码,如下所示

public @interface Scope {

	/**
	 * Alias for {@link #scopeName}.
	 * @see #scopeName
	 */
	@AliasFor("scopeName")
	String value() default "";

	/**
	 * Specifies the name of the scope to use for the annotated component/bean.
	 * <p>Defaults to an empty string ({@code ""}) which implies
	 * {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
	 * @since 4.2
	 * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
	 * @see ConfigurableBeanFactory#SCOPE_SINGLETON
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
	 * @see #value
	 */
	@AliasFor("value")
	String scopeName() default "";

	ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

}

从**@Scope**注解类的源码中可以看出,在@Scope注解中可以设置如下值:

  1. ConfigurableBeanFactory#SCOPE_PROTOTYPE
  2. ConfigurableBeanFactory#SCOPE_SINGLETON
  3. org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
  4. org.springframework.web.context.WebApplicationContext#SCOPE_SESSION

查看一下ConfigurableBeanFactory接口的源码,发现在该接口中存在两个常量的定义

public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {

	String SCOPE_SINGLETON = "singleton";


	String SCOPE_PROTOTYPE = "prototype";
}

SCOPE_SINGLETON就是singleton,而SCOPE_PROTOTYPE就是prototype

而SCOPE_REQUEST的值就是request,SCOPE_SESSION的值就是session

备注:request和session作用域是需要Web环境来支持的,这两个值基本上使用不到,实例对象的作用域设置为request和session

request.setAttribute("key", object);

session.setAttribute("key", object);

2.单实例bean作用域

创建MainConfig02配置类,实例化一个Person对象,并将其放置在Spring容器中

package com.tianxia.springannotation.config;

import com.tianxia.springannotation.entity.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置类
 * @author liqb
 * @date 2023-04-23 9:45
 **/
@Configuration
public class MainConfig02 {

    /**
     * 创建person实例
     * @author liqb
     * @date 2023-04-23 09:46
     * @return
     */
    @Bean("person02")
    public Person person() {
        return new Person("liqb", 24);
    }
}

编写测试方法,从Spring容器中按照id获取两个Person对象,并判断这两个对象是否是同一个对象

/**
 * 测试单实例bean
 * @author liqb
 * @date 2023-04-23 09:47
 */
@Test
public void testSingleBean() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig02.class);

    // 获取到的这个Person对象默认是单实例的,因为在IOC容器中给我们加的这些组件默认都是单实例的,
    // 所以说在这儿我们无论多少次获取,获取到的都是我们之前new的那个实例对象
    Person person = (Person) applicationContext.getBean("person02");
    Person person2 = (Person) applicationContext.getBean("person02");
    System.out.println(person == person2);
}

测试结果:true

结论:对象在Spring容器中默认是单实例的,Spring容器在启动时就会将实例对象加载到Spring容器中,之后,每次从Spring容器中获取实例对象,都是直接将对象返回,而不必再创建新的实例对象了。

2.多实例bean作用域

将MainConfig02配置类中Person对象的作用域修改成prototype,如下所示。

package com.tianxia.springannotation.config;

import com.tianxia.springannotation.entity.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

/**
 * 配置类
 * @author liqb
 * @date 2023-04-23 9:45
 **/
@Configuration
public class MainConfig02 {

    /**
     * 创建person实例
     * @author liqb
     * @date 2023-04-23 09:46
     * @return
     */
    @Bean("person02")
    // 通过@Scope注解来指定该bean的作用范围,也可以说成是调整作用域
    @Scope("prototype")
    public Person person() {
        return new Person("liqb", 24);
    }
}

再次运行测试方法

测试结果:false

结论:当对象是多实例时,每次从Spring容器中获取对象时,都会创建新的实例对象,并且每个实例对象都不相等

3.单实例bean作用域何时创建对象

package com.tianxia.springannotation.config;

import com.tianxia.springannotation.entity.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置类
 * @author liqb
 * @date 2023-04-23 9:45
 **/
@Configuration
public class MainConfig02 {

    /**
     * 创建person实例
     * @author liqb
     * @date 2023-04-23 09:46
     * @return
     */
    @Bean("person02")
    public Person person() {
        System.out.println("给容器中添加咱们这个Person对象...");
        return new Person("liqb", 24);
    }
}

创建测试方法

/**
 * 测试单实例bean何时创建对象
 * @author liqb
 * @date 2023-04-23 10:01
 */
@Test
public void testSingleBeanTime() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig02.class);
}

输出的结果信息如下所示

在这里插入图片描述

结论:Spring容器在创建的时候,就将@Scope注解标注为singleton的组件进行了实例化,并加载到了Spring容器中。

/**
 * 测试单实例bean何时创建对象
 * @author liqb
 * @date 2023-04-23 10:01
 */
@Test
public void testSingleBeanTime() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig02.class);

    Person person = (Person) applicationContext.getBean("person02");
    Person person2 = (Person) applicationContext.getBean("person02");
    System.out.println(person == person2);
}

输出的结果信息如下所示

在这里插入图片描述

结论:Spring容器在启动时,将单实例组件实例化之后,会即刻加载到Spring容器中,以后每次从容器中获取组件实例对象时,都是直接返回相应的对象,而不必再创建新的对象了。

4.多实例bean作用域何时创建对象?

package com.tianxia.springannotation.config;

import com.tianxia.springannotation.entity.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

/**
 * 配置类
 * @author liqb
 * @date 2023-04-23 9:45
 **/
@Configuration
public class MainConfig02 {

    /**
     * 创建person实例
     * @author liqb
     * @date 2023-04-23 09:46
     * @return
     */
    @Bean("person02")
    //通过@Scope注解来指定该bean的作用范围,也可以说成是调整作用域
    @Scope("prototype")
    public Person person() {
        System.out.println("给容器中添加咱们这个Person对象...");
        return new Person("liqb", 24);
    }
}

创建测试方法

/**
 * 测试多实例bean何时创建对象
 * @author liqb
 * @date 2023-04-23 10:01
 */
@Test
public void testPrototypeBeanTime() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig02.class);
}

输出的结果信息如下所示

在这里插入图片描述

结论:当向Spring容器中获取Person实例对象时,Spring容器才会实例化Person对象,再将其加载到Spring容器中去。

/**
 * 测试多实例bean何时创建对象
 * @author liqb
 * @date 2023-04-23 10:01
 */
@Test
public void testPrototypeBeanTime() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig02.class);

    Person person = (Person) applicationContext.getBean("person02");
    Person person2 = (Person) applicationContext.getBean("person02");
    System.out.println(person == person2);
}

输出的结果信息如下所示

在这里插入图片描述

结论:当对象的Scope作用域为多实例时,每次向Spring容器获取对象时,它都会创建一个新的对象并返回。很显然,以上获取到的person和person2就不是同一个对象了,每次从Spring容器中获取对象时,都会创建新的实例对象,并且每个实例对象都不相等。

5.单实例bean注意的事项

单实例bean是整个应用所共享的,所以需要考虑到线程安全问题,在玩SpringMVC的时候,SpringMVC中的Controller默认是单例的,有

些开发者在Controller中创建了一些变量,那么这些变量实际上就变成共享的了,Controller又可能会被很多线程同时访问,这些线程并发去修

改Controller中的共享变量,此时很有可能会出现数据错乱的问题,所以使用的时候需要特别注意。

6.多实例bean注意的事项

多实例bean每次获取的时候都会重新创建,如果这个bean比较复杂,创建时间比较长,那么就会影响系统的性能,因此这个地方需要注意点。

7.自定义Scope

实现一个线程级别的bean作用域,同一个线程中同名的bean是同一个实例,不同的线程中的bean是不同的实例

package com.tianxia.springannotation.config.configEntity;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 自定义scope
 * @author liqb
 * @date 2023-04-23 10:26
 **/
public class CustomerScope implements Scope {

    public static final String CUSTOMER_SCOPE = "thread";

    private ThreadLocal<Map<String, Object>> beanMap = new ThreadLocal() {
        @Override
        protected Object initialValue() {
            return new HashMap<>();
        }
    };

    /**
     * 返回当前作用域中name对应的bean对象
     * @author liqb
     * @date 2023-04-23 10:29
     * @param name 需要检索的bean对象的名称
     * @param objectFactory 如果name对应的bean对象在当前作用域中没有找到,那么可以调用这个objectFactory来创建这个bean对象
     * @return
     */
    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        // 获取bean对象
        Object bean = beanMap.get().get(name);
        if (Objects.isNull(bean)) {
            bean = objectFactory.getObject();
            beanMap.get().put(name, bean);
        }
        return bean;
    }

    /**
     * 将name对应的bean对象从当前作用域中移除
     * @author liqb
     * @date 2023-04-23 10:43
     * @param name bean名称
     * @return
     */
    @Override
    public Object remove(String name) {
        return this.beanMap.get().remove(name);
    }

    /**
     * 用于注册销毁回调,若想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象
     * @param name bean名称
     * @param callback 回调方法
     */
    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        System.out.println(name);
    }

    /**
     * 用于解析相应的上下文数据,比如request作用域将返回request中的属性
     * @author liqb
     * @date 2023-04-23 10:44
     * @param key
     * @return
     */
    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    /**
     * 作用域的会话标识,比如session作用域的会话标识是sessionId
     * @author liqb
     * @date 2023-04-23 10:44
     * @return
     */
    @Override
    public String getConversationId() {
        return Thread.currentThread().getName();
    }
}

创建一个配置类

package com.tianxia.springannotation.config;

import com.tianxia.springannotation.config.configEntity.CustomerScope;
import com.tianxia.springannotation.entity.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

/**
 * 配置类
 * @author liqb
 * @date 2023-04-23 10:45
 **/
@Configuration
public class MainConfig03 {

    /**
     * 创建person实例
     * @author liqb
     * @date 2023-04-23 09:46
     * @return
     */
    @Bean("person03")
    @Scope(CustomerScope.CUSTOMER_SCOPE)
    public Person person() {
        System.out.println("给容器中添加咱们这个Person对象...");
        return new Person("liqb", 24);
    }
}

编写测试方法

/**
 * 测试自定义Scope
 * @author liqb
 * @date 2023-04-23 10:47
 */
@Test
public void testCustomScope() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig03.class);

    // 向容器中注册自定义的Scope
    applicationContext.getBeanFactory().registerScope(CustomerScope.CUSTOMER_SCOPE, new CustomerScope());

    // 使用容器获取bean
    for (int i = 0; i < 2; i++) {
        new Thread(() -> {
            System.out.println(Thread.currentThread() + "," + applicationContext.getBean("person03"));
            System.out.println(Thread.currentThread() + "," + applicationContext.getBean("person03"));
        }).start();
    }
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

实验结果
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值