当前位置: 首页 > article >正文

使用@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();
    }
}

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


http://www.kler.cn/a/13627.html

相关文章:

  • 专业四js知识点
  • 如何快速将PDF文件转换为Word文档
  • 15天学习MySQL计划(常用函数/约束)(基础篇)第三天
  • shell:清理指定目录中指定天数之前的旧文件
  • 【学习笔记】从MySQL快速入门 PostgreSQL
  • 美国肝素钠专用树脂,医药肝素钠提取工艺专用树脂
  • 三谈ChatGPT(ChatGPT可以解决问题的90%)
  • OpenCV 图像处理学习手册:6~7
  • 液压轴位置闭环控制(比例伺服阀应用)
  • 不知道玩什么游戏的你看过来
  • 功率MOS管烧毁,有这些原因
  • 大型体检管理系统源码:适用于大中型医院或独立体检中心
  • C/C++每日一练(20230419)
  • Elasticsearch
  • 格式工厂将视频导出Maya需要的图像序列帧
  • 我的第一台电脑------计算机类专业学生购置电脑的一些个人心得
  • 如何用AI生成《ios客户端学习笔记(一):Swift学习路径》
  • VUE入门
  • VMware vSphere 8.0 Update 1 正式版发布 - 企业级工作负载平台
  • 【设计模式】Java 的三种代理模式