【Spring】——4、使用@Scope注解设置组件的作用域
创始人
2024-01-26 15:16:08
0

在这里插入图片描述

📫作者简介:zhz小白
公众号:小白的Java进阶之路
专业技能:
1、Java基础,并精通多线程的开发,熟悉JVM原理
2、熟悉Java基础,并精通多线程的开发,熟悉JVM原理,具备⼀定的线上调优经验
3、熟悉MySQL数据库调优,索引原理等,⽇志原理等,并且有出过⼀篇专栏
4、了解计算机⽹络,对TCP协议,滑动窗⼝原理等有⼀定了解
5、熟悉Spring,Spring MVC,Mybatis,阅读过部分Spring源码
6、熟悉SpringCloud Alibaba体系,阅读过Nacos,Sentinel,Seata,Dubbo,Feign,Gateway核⼼源码与设计,⼆次开发能⼒
7、熟悉消息队列(Kafka,RocketMQ)的原理与设计
8、熟悉分库分表ShardingSphere,具有真实⽣产的数据迁移经验
9、熟悉分布式缓存中间件Redis,对其的核⼼数据结构,部署架构,⾼并发问题解决⽅案有⼀定的积累
10、熟悉常⽤设计模式,并运⽤于实践⼯作中
11、了解ElasticSearch,对其核⼼的原理有⼀定的了解
12、了解K8s,Jekins,GitLab
13、了解VUE,GO
14、⽬前有正在利⽤闲暇时间做互游游戏,开发、运维、运营、推销等

本人著作git项目:https://gitee.com/zhouzhz/star-jersey-platform,有兴趣的可以私聊博主一起编写,或者给颗star
领域:对支付(FMS,FUND,PAY),订单(OMS),出行行业等有相关的开发领域
🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~

文章目录

  • @Scope注解概述
    • 第一个实现类:ConfigurableBeanFactory
    • 第二个实现类:WebApplicationContext
    • @Scope注解取值如下
  • 单实例bean作用域
  • 多实例bean作用域
  • 单实例bean作用域如何创建对象?
  • 多实例bean作用域如何创建对象?
  • 单实例bean注意的事项
  • 多实例bean注意的事项
  • 自定义Scope的实现
    • 如何实现自定义Scope呢?
      • 1、实现Scope接口
      • 2、将自定义Scope注册到容器中
      • 3、使用自定义的作用域
    • 一个自定义Scope实现案例

@Scope注解概述

@Scope注解能够设置组件的作用域,我们先来看看@Scope注解类的源码,如下所示。

/** Copyright 2002-2018 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.context.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.annotation.AliasFor;/*** When used as a type-level annotation in conjunction with* {@link org.springframework.stereotype.Component @Component},* {@code @Scope} indicates the name of a scope to use for instances of* the annotated type.** 

When used as a method-level annotation in conjunction with* {@link Bean @Bean}, {@code @Scope} indicates the name of a scope to use* for the instance returned from the method.**

NOTE: {@code @Scope} annotations are only introspected on the* concrete bean class (for annotated components) or the factory method* (for {@code @Bean} methods). In contrast to XML bean definitions,* there is no notion of bean definition inheritance, and inheritance* hierarchies at the class level are irrelevant for metadata purposes.**

In this context, scope means the lifecycle of an instance,* such as {@code singleton}, {@code prototype}, and so forth. Scopes* provided out of the box in Spring may be referred to using the* {@code SCOPE_*} constants available in the {@link ConfigurableBeanFactory}* and {@code WebApplicationContext} interfaces.**

To register additional custom scopes, see* {@link org.springframework.beans.factory.config.CustomScopeConfigurer* CustomScopeConfigurer}.** @author Mark Fisher* @author Chris Beams* @author Sam Brannen* @since 2.5* @see org.springframework.stereotype.Component* @see org.springframework.context.annotation.Bean*/ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented 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.*

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 "";/*** Specifies whether a component should be configured as a scoped proxy* and if so, whether the proxy should be interface-based or subclass-based.*

Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates* that no scoped proxy should be created unless a different default* has been configured at the component-scan instruction level.*

Analogous to {@code } support in Spring XML.* @see ScopedProxyMode*/ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;}

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

  • ConfigurableBeanFactory#SCOPE_PROTOTYPE
  • ConfigurableBeanFactory#SCOPE_SINGLETON
  • org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
  • org.springframework.web.context.WebApplicationContext#SCOPE_SESSION

我们可以看到@Scope注解的注释中有一句话,表明它是有两个实现类的,所以我们直接上实现类中看。
在这里插入图片描述

第一个实现类:ConfigurableBeanFactory

在这里插入图片描述

我们可以发现SCOPE_SINGLETON就是singleton,而SCOPE_PROTOTYPE就是prototype。

第二个实现类:WebApplicationContext

在这里插入图片描述

我们可以发现SCOPE_REQUEST的值就是request,SCOPE_SESSION的值就是session。
request和session作用域是需要Web环境来支持的,这两个值基本上使用不到。当我们使用Web容器来运行Spring应用时,如果需要将组件的实例对象的作用域设置为request和session,那么我们通常会使用

request.setAttribute(“key”, object);
session.setAttribute(“key”, object);

这两种形式来将对象实例设置到request和session中,而不会使用@Scope注解来进行设置。

@Scope注解取值如下

在这里插入图片描述

单实例bean作用域

我们在MainConfig中添加一个Bean对象,放入spring容器中,如下:

package com.zhz.config;import com.zhz.bean.Person;
import com.zhz.filter.MyTypeFilter;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;/*** @author zhouhengzhe* @description: todo* @date 2022/11/4 10:27* @since v1*/@Configuration
public class MainConfig {/*** @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id*/@Bean(name = "person")public Person person1() {return new Person("zhz", 20);}
}

然后我们在IOCTest中,执行如下代码:

package com.zhz.test;import com.zhz.bean.Person;
import com.zhz.config.MainConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author zhouhengzhe* @description: todo* @date 2022/11/4 10:58* @since v1*/
public class IOCTest {@SuppressWarnings("resource")@Testpublic void test() {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);Person person = applicationContext.getBean(Person.class);Person person1 = applicationContext.getBean(Person.class);System.out.println(person == person1);}
}

我们可以发现他们是同一个对象,因此可知Spring容器默认是单例Bean,所以只要启动之后,Spring就会把实例对象加载到Bean当中,然后接下来的每一次去获取实例对象,都会是同一个引用地址返回。
在这里插入图片描述

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

多实例bean作用域

我们在MainConfig中添加一个Bean对象,放入spring容器中,并且添加**@Scope(“prototype”)到Bean对象中,**代码如下:

package com.zhz.config;import com.zhz.bean.Person;
import com.zhz.filter.MyTypeFilter;
import org.springframework.context.annotation.*;/*** @author zhouhengzhe* @description: todo* @date 2022/11/4 10:27* @since v1*/@Configuration
public class MainConfig {/*** @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id*/@Scope("prototype")@Bean(name = "person")public Person person1() {return new Person("zhz", 20);}
}

然后我们运行测试类IOCTest,代码如下:

package com.zhz.test;import com.zhz.bean.Person;
import com.zhz.config.MainConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author zhouhengzhe* @description: todo* @date 2022/11/4 10:58* @since v1*/
public class IOCTest {@SuppressWarnings("resource")@Testpublic void test() {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);Person person = applicationContext.getBean(Person.class);Person person1 = applicationContext.getBean(Person.class);System.out.println(person == person1);}
}

演示效果如下:
在这里插入图片描述

总结:从以上输出结果中也可以看出,此时,输出的person对象和person2对象已经不是同一个对象了。说明他是一个原型实例

单实例bean作用域如何创建对象?

我们思考一下单例bean是什么时候创建的呢?
我们来做个实现,首先我们再MainConfig中创建一个对象,如下:

@Bean(name = "person")public Person person1() {System.out.println("单例bean创建");return new Person("zhz", 20);}

然后我们写一个测试用例,如下:

  @Testpublic void test() {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);}

运行结果:
在这里插入图片描述
因此我们可以知道Spring容器在创建的时候,就将@Scope注解标注为singleton的组件进行了实例化,并加载到了Spring容器中,以后每次从容器中获取组件实例对象时,都是直接返回相应的对象,而不必再创建新的对象了。

多实例bean作用域如何创建对象?

我们也根据上面的测试案例去测一遍多例Bean是怎么创建对象的,测试代码如下:

@Scope("prototype")@Bean(name = "person")public Person person1() {System.out.println("单例bean创建");return new Person("zhz", 20);
}

测试用例1:

  @Testpublic void test() {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);}

运行结果:
在这里插入图片描述

测试用例2:

@Testpublic void test() {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);Person person = applicationContext.getBean(Person.class);Person person1 = applicationContext.getBean(Person.class);System.out.println(person == person1);}

在这里插入图片描述

由测试用例1和测试用例2我们可以发现当对象是多实例时,每次从Spring容器中获取对象时,都会创建新的实例对象,并且每个实例对象都不相等

单实例bean注意的事项

单实例bean是整个应用所共享的,所以需要考虑到线程安全问题,之前在玩SpringMVC的时候,SpringMVC中的Controller默认是单例的,有些开发者在Controller中创建了一些变量,那么这些变量实际上就变成共享的了,Controller又可能会被很多线程同时访问,这些线程并发去修改Controller中的共享变量,此时很有可能会出现数据错乱的问题,所以使用的时候需要特别注意。

多实例bean注意的事项

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

自定义Scope的实现

如果Spring内置的几种scope都无法满足我们的需求时,我们可以自定义bean的作用域。

如何实现自定义Scope呢?

1、实现Scope接口

public interface Scope {/*** 返回当前作用域中name对应的bean对象* @param name 需要检索的bean对象的名称* @param objectFactory 如果name对应的bean对象在当前作用域中没有找到,那么可以调用这个objectFactory来创建这个对象*/Object get(String name, ObjectFactory objectFactory);/*** 将name对应的bean对象从当前作用域中移除*/@NullableObject remove(String name);/*** 用于注册销毁回调,若想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象*/void registerDestructionCallback(String name, Runnable callback);/*** 用于解析相应的上下文数据,比如request作用域将返回request中的属性*/@NullableObject resolveContextualObject(String key);/*** 作用域的会话标识,比如session作用域的会话标识是sessionId*/@NullableString getConversationId();}

2、将自定义Scope注册到容器中

需要调用org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope这个方法,咱们看一下这个方法的声明。
在这里插入图片描述

3、使用自定义的作用域

在定义bean的时候,指定bean的scope属性为自定义的作用域名称。

一个自定义Scope实现案例

  • 我们来实现一个线程级别的bean作用域,同一个线程中同名的bean是同一个实例,不同的线程中的bean是不同的实例。
  • 要求bean在线程中是共享的,所以我们可以通过ThreadLocal来实现,ThreadLocal可以实现线程中数据的共享。

我们在com.zhz.scope包下新建一个ThreadScope类。

package com.zhz.scope;import com.alibaba.ttl.TransmittableThreadLocal;
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;/*** @author zhouhengzhe* @description: 自定义本地线程级别的bean作用域,不同的线程中的bean是不同的实例,同一个线程中同名的bean是同一个实例* @date 2022/11/5 23:46* @since v1*/
public class ThreadScope implements Scope {public static final String THREAD_SCOPE = "thread";private ThreadLocal> beanMap = new TransmittableThreadLocal<>();/*** 返回当前作用域中name对应的bean对象** @param name:需要检索的bean对象的名称* @param objectFactory:如果name对应的bean对象在当前作用域中没有找到,那么可以调用这个objectFactory来创建这个bean对象*/@Overridepublic Object get(String name, ObjectFactory objectFactory) {Map map = beanMap.get();Object bean=null;if (Objects.isNull(map)) {map = new HashMap<>(16);bean = objectFactory.getObject();map.put(name, bean);beanMap.set(map);}bean = map.get(name);return bean;}/*** 将name对应的bean对象从当前作用域中移除*/@Overridepublic Object remove(String name) {return this.beanMap.get().remove(name);}/*** 用于注册销毁回调,若想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象* bean作用域范围结束的时候调用的方法,用于bean的清理*/@Overridepublic void registerDestructionCallback(String name, Runnable callback) {System.out.println("name");}/*** 用于解析相应的上下文数据,比如request作用域将返回request中的属性*/@Overridepublic Object resolveContextualObject(String key) {return null;}/*** 作用域的会话标识,比如session作用域的会话标识是sessionId*/@Overridepublic String getConversationId() {return Thread.currentThread().getName();}
}

我们在com.zhz.config包下创建一个配置类,例如MainConfig,并使用@Scope(“thread”)注解标注Person对象的作用域为Thread范围:

 @Scope("thread")@Bean(name = "person")public Person person1() {System.out.println("单例bean创建");return new Person("zhz", 20);}

测试类:

 @Testpublic void test1() {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();// 向容器中注册自定义的ScopebeanFactory.registerScope(ThreadScope.THREAD_SCOPE, new ThreadScope());for (int i = 0; i < 2; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName() + ","+applicationContext.getBean("person"));System.out.println(Thread.currentThread().getName() + ","+applicationContext.getBean("person"));}).start();}try {TimeUnit.SECONDS.sleep(1);}catch (Exception e){e.printStackTrace();}}

演示效果:
在这里插入图片描述

由上可知:bean在同样的线程中获取到的是同一个bean的实例,不同的线程中bean的实例是不同的。

相关内容

热门资讯

女生创业有什么好项目有哪些 女... 女生创业者越来越多,那么对于初次创业的女生来说选好一个项目是非常重要的,下面是百分网小编整理的女生适...
适合女性的创业项目排行榜 10...   儿童早期教育行业超过半数的中国城市家庭,孩子每月花费占家庭总收入的20%以上,44%的家庭每月用...
银行创业贷款有什么条件 银行创... 一、银行创业贷款条件1、身份及营业场所证明贷款申请人必须具备合法有效的身份证明和在贷款行所在地合法居...
大学生创业银行贷款需要什么条件...   大学生创业贷款优惠政策  1大学毕业生在毕业后两年内自主创业,到创业实体所在地的工商部门办理营业...
5大适合大最适合大学生创业的项... 大学生创业能做什么?没有社会经验,经济基础差,可是思维活跃,学习能力强,这样的情况下,能做什么创业呢...
大学生创业计划项目有哪些 附... 创业已经不单单是社会人会做的事情了,现在的在校大学生,挥着毕业的大学生都会想着参与创业,为自己的成功...
2020创业选项目 2020创... 创业还是比较难的,首先要选好了创业的方向,并且要为之去不懈的努力,同时要有一定的抗压能力和坚韧不拔的...
2020年,创业者可以选择哪些... 危机其实是一个资源重新分配的过程,如果你不掌握资源,被动等待分配,最后仅有的那一点也将被拿走。这场疫...
就业创业牡丹项目包装 就业创业... 油用牡丹挂果了,李晓鹏很开心在西安当快递员的“90后”李晓鹏回铜川老家创业,种植油用牡丹。目前,陕西...
女性创业农村创业项目 女性创业... 女性在农村怎样创业,有哪些可以推荐的创业项目。以下是学习啦小编分享给大家的关于女性农村创业项目,一起...