【Spring注解驱动开发】使用@Scope注解设置组件的作用域(1)

public Person person(){

return new Person(“binghe002”, 18);

}

}

接下来,在SpringBeanTest类中创建testAnnotationConfig2()测试方法,在testAnnotationConfig2()方法中,创建ApplicationContext对象,创建完毕后,从Spring容器中按照id获取两个Person对象,并打印两个对象是否是同一个对象,代码如下所示。

@Test

public void testAnnotationConfig2(){

ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);

//从Spring容器中获取到的对象默认是单实例的

Object person1 = context.getBean(“person”);

Object person2 = context.getBean(“person”);

System.out.println(person1 == person2);

}

由于对象在Spring容器中默认是单实例的,所以,Spring容器在启动时就会将实例对象加载到Spring容器中,之后,每次从Spring容器中获取实例对象,直接将对象返回,而不必在创建新对象实例,所以,此时testAnnotationConfig2()方法会输出true。如下所示。

在这里插入图片描述

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

多实例bean作用域


修改Spring容器中组件的作用域,我们需要借助于@Scope注解,此时,我们将PersonConfig2类中Person对象的作用域修改成prototype,如下所示。

@Configuration

public class PersonConfig2 {

@Scope(“prototype”)

@Bean(“person”)

public Person person(){

return new Person(“binghe002”, 18);

}

}

其实,使用@Scope设置作用域就等同于在XML文件中为bean设置scope作用域,如下所示。

在这里插入图片描述

此时,我们再次运行SpringBeanTest类的testAnnotationConfig2()方法,此时,从Spring容器中获取到的person1对象和person2对象还是同一个对象吗?

在这里插入图片描述

通过输出结果可以看出,此时,输出的person1对象和person2对象已经不是同一个对象了。

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


接下来,我们验证下在单实例作用域下,Spring是在什么时候创建对象的呢?

首先,我们将PersonConfig2类中的Person对象的作用域修改成单实例,并在返回Person对象之前打印相关的信息,如下所示。

@Configuration

public class PersonConfig2 {

@Scope

@Bean(“person”)

public Person person(){

System.out.println(“给容器中添加Person…”);

return new Person(“binghe002”, 18);

}

}

接下来,我们在SpringBeanTest类中创建testAnnotationConfig3()方法,在testAnnotationConfig3()方法中,我们只创建Spring容器,如下所示。

@Test

public void testAnnotationConfig3(){

ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);

}

此时,我们运行SpringBeanTest类中的testAnnotationConfig3()方法,输出的结果信息如下所示。

在这里插入图片描述

从输出的结果信息可以看出,Spring容器在创建的时候,就将@Scope注解标注为singleton的组件进行了实例化,并加载到Spring容器中。

接下来,我们运行SpringBeanTest类中的testAnnotationConfig2(),结果信息如下所示。

在这里插入图片描述

说明,Spring容器在启动时,将单实例组件实例化之后,加载到Spring容器中,以后每次从容器中获取组件实例对象,直接返回相应的对象,而不必在创建新对象。

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


如果我们将对象的作用域修改成多实例,那什么时候创建对象呢?

此时,我们将PersonConfig2类的Person对象的作用域修改成多实例,如下所示。

@Configuration

public class PersonConfig2 {

@Scope(“prototype”)

@Bean(“person”)

public Person person(){

System.out.println(“给容器中添加Person…”);

return new Person(“binghe002”, 18);

}

}

我们再次运行SpringBeanTest类中的testAnnotationConfig3()方法,输出的结果信息如下所示。

在这里插入图片描述

可以看到,终端并没有输出任何信息,说明在创建Spring容器时,并不会实例化和加载多实例对象,那多实例对象是什么时候实例化的呢?接下来,我们在SpringBeanTest类中的testAnnotationConfig3()方法中添加一行获取Person对象的代码,如下所示。

@Test

public void testAnnotationConfig3(){

ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);

Object person1 = context.getBean(“person”);

}

此时,我们再次运行SpringBeanTest类中的testAnnotationConfig3()方法,结果信息如下所示。

在这里插入图片描述

从结果信息中,可以看出,当向Spring容器中获取Person实例对象时,Spring容器实例化了Person对象,并将其加载到Spring容器中。

那么,问题来了,此时Spring容器是否只实例化一个Person对象呢?我们在SpringBeanTest类中的testAnnotationConfig3()方法中再添加一行获取Person对象的代码,如下所示。

@Test

public void testAnnotationConfig3(){

ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);

Object person1 = context.getBean(“person”);

Object person2 = context.getBean(“person”);

}

此时,我们再次运行SpringBeanTest类中的testAnnotationConfig3()方法,结果信息如下所示。

在这里插入图片描述

从输出结果可以看出,当对象的Scope作用域为多实例时,每次向Spring容器获取对象时,都会创建一个新的对象并返回。此时,获取到的person1和person2就不是同一个对象了,我们也可以打印结果信息来进行验证,此时在SpringBeanTest类中的testAnnotationConfig3()方法中打印两个对象是否相等,如下所示。

@Test

public void testAnnotationConfig3(){

ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);

Object person1 = context.getBean(“person”);

Object person2 = context.getBean(“person”);

System.out.println(person1 == person2);

}

此时,我们再次运行SpringBeanTest类中的testAnnotationConfig3()方法,结果信息如下所示。

在这里插入图片描述

可以看到,当对象是多实例时,每次从Spring容器中获取对象时,都会创建新的实例对象,并且每个实例对象都不相等。

单实例bean注意的事项


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

多实例bean注意的事项


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

自定义Scope


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

1.如何实现自定义Scope

自定义Scope主要分为三个步骤,如下所示。

(1)实现Scope接口

我们先来看下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();

}

(2)将Scope注册到容器

需要调用org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope的方法,看一下这个方法的声明

/**

  • 向容器中注册自定义的Scope

*scopeName:作用域名称

  • scope:作用域对象

**/

void registerScope(String scopeName, Scope scope);

(3)使用自定义的作用域

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

2.自定义Scope实现案例

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

这里,要求bean在线程中是共享的,所以我们可以通过ThreadLocal来实现,ThreadLocal可以实现线程中数据的共享。

此时,我们在io.mykit.spring.plugins.register.scope包下新建ThreadScope类,如下所示。

package io.mykit.spring.plugins.register.scope;

import org.springframework.beans.factory.ObjectFactory;

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

import org.springframework.lang.Nullable;

import java.util.HashMap;

import java.util.Map;

import java.util.Objects;

/**

  • 自定义本地线程级别的bean作用域,不同的线程中对应的bean实例是不同的,同一个线程中同名的bean是同一个实例

*/

public class ThreadScope implements Scope {

public static final String THREAD_SCOPE = “thread”;

private ThreadLocal<Map<String, Object>> beanMap = new ThreadLocal() {

@Override

protected Object initialValue() {

return new HashMap<>();

}

};

@Override

public Object get(String name, ObjectFactory<?> objectFactory) {

Object bean = beanMap.get().get(name);

if (Objects.isNull(bean)) {

bean = objectFactory.getObject();

beanMap.get().put(name, bean);

}

return bean;

}

@Nullable

@Override

public Object remove(String name) {

return this.beanMap.get().remove(name);

}

@Override

public void registerDestructionCallback(String name, Runnable callback) {

//bean作用域范围结束的时候调用的方法,用于bean清理

System.out.println(name);

}

@Nullable

@Override

public Object resolveContextualObject(String key) {

return null;

}

@Nullable

@Override

public String getConversationId() {

return Thread.currentThread().getName();

}

}

在ThreadScope类中,我们定义了一个常量THREAD_SCOPE,在定义bean的时候给scope使用。

接下来,我们在io.mykit.spring.plugins.register.config包下创建PersonConfig3类,并使用@Scope(“thread”)注解标注Person对象的作用域为Thread范围,如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Scope;

/**

  • @author binghe

  • @version 1.0.0

  • @description 测试@Scope注解设置的作用域

*/

@Configuration

public class PersonConfig3 {

@Scope(“thread”)

@Bean(“person”)

public Person person(){

System.out.println(“给容器中添加Person…”);

return new Person(“binghe002”, 18);

}

}

最后,我们在SpringBeanTest类中创建testAnnotationConfig4()方法,在testAnnotationConfig4()方法中创建Spring容器,并向Spring容器中注册ThreadScope对象,接下来,使用循环创建两个Thread线程,并分别在每个线程中获取两个Person对象,如下所示。

@Test

public void testAnnotationConfig4(){

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig3.class);

//向容器中注册自定义的scope

context.getBeanFactory().registerScope(ThreadScope.THREAD_SCOPE, new ThreadScope());

//使用容器获取bean

for (int i = 0; i < 2; i++) {

new Thread(() -> {

System.out.println(Thread.currentThread() + “,” + context.getBean(“person”));

System.out.println(Thread.currentThread() + “,” + context.getBean(“person”));

}).start();

try {

TimeUnit.SECONDS.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

给大家送一个小福利

附高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**[外链图片转存中…(img-KqgKE1mE-1713407388960)]

[外链图片转存中…(img-6mmgw6ZX-1713407388961)]

[外链图片转存中…(img-TAprobHZ-1713407388961)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

给大家送一个小福利

[外链图片转存中…(img-7Pj0WBrd-1713407388962)]

附高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。

[外链图片转存中…(img-msNH9xRA-1713407388962)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值