spring 单例 获取多例的位_Spring系列第6篇:玩转bean scope,避免跳坑里!

bfb34be251c3c70e181631d90680f3d6.png

公众号关注“程序员二哥”,

设为‘星标’,带你学习更多的知识。

本文内容

  1. 详细介绍5中bean的sope及使用注意点

  2. 自定义作用域的实现

应用中,有时候我们需要一个对象在整个应用中只有一个,有些对象希望每次使用的时候都重新创建一个,spring对我们这种需求也提供了支持,在spring中这个叫做bean的作用域,xml中定义bean的时候,可以通过scope属性指定bean的作用域,如:

<bean id="" class="" scope="作用域" /> 

spring容器中scope常见的有5种,下面我们分别来介绍一下。

singleton

当scope的值设置为singleton的时候,整个spring容器中只会存在一个bean实例,通过容器多次查找bean的时候(调用BeanFactory的getBean方法或者bean之间注入依赖的bean对象的时候),返回的都是同一个bean对象,singleton是scope的默认值,所以spring容器中默认创建的bean对象是单例的,通常spring容器在启动的时候,会将scope为singleton的bean创建好放在容器中(有个特殊的情况,当bean的lazy被设置为true的时候,表示懒加载,那么使用的时候才会创建),用的时候直接返回。

案例

bean xml配置

<bean id="singletonBean" class="com.javacode2018.lesson001.demo4.BeanScopeModel" scope="singleton">
    <constructor-arg index="0" value="singleton"/>
bean>
BeanScopeModel代码
package com.javacode2018.lesson001.demo4;

public class BeanScopeModel {
    public BeanScopeModel(String beanScope) {
        System.out.println(String.format("create BeanScopeModel,{sope=%s},{this=%s}", beanScope, this));
    }
}

上面构造方法中输出了一段文字,一会我们可以根据输出来看一下这个bean什么时候创建的,是从容器中获取bean的时候创建的还是容器启动的时候创建的。

测试用例
package com.javacode2018.lesson001.demo4;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
 * 


 * bean作用域
 */


public class ScopeTest {

    ClassPathXmlApplicationContext context;

    @Before
    public void before() {
        System.out.println("spring容器准备启动.....");
        //1.bean配置文件位置
        String beanXml = "classpath:/com/javacode2018/lesson001/demo4/beans.xml";
        //2.创建ClassPathXmlApplicationContext容器,给容器指定需要加载的bean配置文件
        this.context = new ClassPathXmlApplicationContext(beanXml);
        System.out.println("spring容器启动完毕!");
    }

    /**
     * 单例bean
     */
    @Test
    public void singletonBean() {
        System.out.println("---------单例bean,每次获取的bean实例都一样---------");
        System.out.println(context.getBean("singletonBean"));
        System.out.println(context.getBean("singletonBean"));
        System.out.println(context.getBean("singletonBean"));
    }

}

上面代码中before方法上面有@Before注解,这个是junit提供的功能,这个方法会在所有@Test标注的方法之前之前运行,before方法中我们对容器进行初始化,并且在容器初始化前后输出了一段文字。

上面代码中,singletonBean方法中,3次获取singletonBean对应的bean。

运行测试用例
spring容器准备启动.....
create BeanScopeModel,{sope=singleton},{this=com.javacode2018.lesson001.demo4.BeanScopeModel@e874448}
spring容器启动完毕!
---------单例bean,每次获取的bean实例都一样---------
com.javacode2018.lesson001.demo4.BeanScopeModel@e874448
com.javacode2018.lesson001.demo4.BeanScopeModel@e874448
com.javacode2018.lesson001.demo4.BeanScopeModel@e874448
结论

从输出中得到2个结论

  • 前3行的输出可以看出,BeanScopeModel的构造方法是在容器启动过程中调用的,说明这个bean实例在容器启动过程中就创建好了,放在容器中缓存着

  • 最后3行输出的是一样的,说明返回的是同一个bean对象

单例bean使用注意

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

prototype

如果scope被设置为prototype类型的了,表示这个bean是多例的,通过容器每次获取的bean都是不同的实例,每次获取都会重新创建一个bean实例对象。

案例

bean xml配置

<bean id="prototypeBean" class="com.javacode2018.lesson001.demo4.BeanScopeModel" scope="prototype">
    <constructor-arg index="0" value="prototype"/>
bean>
新增一个测试用例

ScopeTest中新增一个方法

/**
 * 多例bean
 */
@Test
public void prototypeBean() {
    System.out.println("---------单例bean,每次获取的bean实例都一样---------");
    System.out.println(context.getBean("prototypeBean"));
    System.out.println(context.getBean("prototypeBean"));
    System.out.println(context.getBean("prototypeBean"));
}
运行测试用例
spring容器准备启动.....
spring容器启动完毕!
---------单例bean,每次获取的bean实例都一样---------
create BeanScopeModel,{sope=prototype},{this=com.javacode2018.lesson001.demo4.BeanScopeModel@289d1c02}
com.javacode2018.lesson001.demo4.BeanScopeModel@289d1c02
create BeanScopeModel,{sope=prototype},{this=com.javacode2018.lesson001.demo4.BeanScopeModel@22eeefeb}
com.javacode2018.lesson001.demo4.BeanScopeModel@22eeefeb
create BeanScopeModel,{sope=prototype},{this=com.javacode2018.lesson001.demo4.BeanScopeModel@17d0685f}
com.javacode2018.lesson001.demo4.BeanScopeModel@17d0685f
结论

输出中可以看出,容器启动过程中并没有去创建BeanScopeModel对象,3次获取prototypeBean得到的都是不同的实例,每次获取的时候才会去调用构造方法创建bean实例。

多例bean使用注意

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

下面要介绍的3个:request、session、application都是在spring web容器环境中才会有的。

request

当一个bean的作用域为request,表示在一次http请求中,一个bean对应一个实例;对每个http请求都会创建一个bean实例,request结束的时候,这个bean也就结束了,request作用域用在spring容器的web环境中,这个以后讲springmvc的时候会说,spring中有个web容器接口WebApplicationContext,这个里面对request作用域提供了支持,配置方式:

<bean id="" class="" scope="request" />

session

这个和request类似,也是用在web环境中,session级别共享的bean,每个会话会对应一个bean实例,不同的session对应不同的bean实例,springmvc中我们再细说。

<bean id="" class="" scope="session" />

application

全局web应用级别的作用于,也是在web环境中使用的,一个web应用程序对应一个bean实例,通常情况下和singleton效果类似的,不过也有不一样的地方,singleton是每个spring容器中只有一个bean实例,一般我们的程序只有一个spring容器,但是,一个应用程序中可以创建多个spring容器,不同的容器中可以存在同名的bean,但是sope=aplication的时候,不管应用中有多少个spring容器,这个应用中同名的bean只有一个。

<bean id="" class="" scope="application" />

自定义scope

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

自定义Scope 3步骤

第1步:实现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属性为自定义的作用域名称。

案例

需求

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

实现分析

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

下面我们来上代码。

ThreadScope
package com.javacode2018.lesson001.demo4;

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";//@1

    private ThreadLocal> beanMap = new ThreadLocal() {@Overrideprotected Object initialValue() {return new HashMap<>();
        }
    };@Overridepublic 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@Overridepublic Object remove(String name) {return this.beanMap.get().remove(name);
    }@Overridepublic void registerDestructionCallback(String name, Runnable callback) {//bean作用域范围结束的时候调用的方法,用于bean清理
        System.out.println(name);
    }@Nullable@Overridepublic Object resolveContextualObject(String key) {return null;
    }@Nullable@Overridepublic String getConversationId() {return Thread.currentThread().getName();
    }
}

@1:定义了作用域的名称为一个常量thread,可以在定义bean的时候给scope使用

BeanScopeModel
package com.javacode2018.lesson001.demo4;

public class BeanScopeModel {
    public BeanScopeModel(String beanScope) {
        System.out.println(String.format("线程:%s,create BeanScopeModel,{sope=%s},{this=%s}", Thread.currentThread(), beanScope, this));
    }
}

上面的构造方法中会输出当前线程的信息,到时候可以看到创建bean的线程。

bean配置文件

beans-thread.xml内容

<?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-4.3.xsd">

    
    <bean id="threadBean" class="com.javacode2018.lesson001.demo4.BeanScopeModel" scope="thread">
        <constructor-arg index="0" value="thread"/>
    bean>
beans>

注意上面的scope是我们自定义的,值为thread

测试用例
package com.javacode2018.lesson001.demo4;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.concurrent.TimeUnit;

/**
 * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
 * 


 * 自定义scope
 */


public class ThreadScopeTest {
    public static void main(String[] args) throws InterruptedException {
        String beanXml = "classpath:/com/javacode2018/lesson001/demo4/beans-thread.xml";
        //手动创建容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
        //设置配置文件位置
        context.setConfigLocation(beanXml);
        //启动容器
        context.refresh();
        //向容器中注册自定义的scope
        context.getBeanFactory().registerScope(ThreadScope.THREAD_SCOPE, new ThreadScope());//@1

        //使用容器获取bean
        for (int i = 0; i 2; i++) { //@2
            new Thread(() -> {
                System.out.println(Thread.currentThread() + "," + context.getBean("threadBean"));
                System.out.println(Thread.currentThread() + "," + context.getBean("threadBean"));
            }).start();
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

注意上面代码,重点在@1,这个地方向容器中注册了自定义的ThreadScope。

@2:创建了2个线程,然后在每个线程中去获取同样的bean 2次,然后输出,我们来看一下效果。

运行输出
线程:Thread[Thread-1,5,main],create BeanScopeModel,{sope=thread},{this=com.javacode2018.lesson001.demo4.BeanScopeModel@4049d530}
Thread[Thread-1,5,main],com.javacode2018.lesson001.demo4.BeanScopeModel@4049d530
Thread[Thread-1,5,main],com.javacode2018.lesson001.demo4.BeanScopeModel@4049d530
线程:Thread[Thread-2,5,main],create BeanScopeModel,{sope=thread},{this=com.javacode2018.lesson001.demo4.BeanScopeModel@87a76da}
Thread[Thread-2,5,main],com.javacode2018.lesson001.demo4.BeanScopeModel@87a76da
Thread[Thread-2,5,main],com.javacode2018.lesson001.demo4.BeanScopeModel@87a76da

从输出中可以看到,bean在同样的线程中获取到的是同一个bean的实例,不同的线程中bean的实例是不同的。

总结

  1. spring容器自带的有2种作用域,分别是singleton和prototype;还有3种分别是spring web容器环境中才支持的request、session、application

  2. singleton是spring容器默认的作用域,一个spring容器中同名的bean实例只有一个,多次获取得到的是同一个bean;单例的bean需要考虑线程安全问题

  3. prototype是多例的,每次从容器中获取同名的bean,都会重新创建一个;多例bean使用的时候需要考虑创建bean对性能的影响

  4. 一个应用中可以有多个spring容器

  5. 自定义scope 3个步骤,实现Scope接口,将实现类注册到spring容器,使用自定义的sope

案例源码

链接:https://pan.baidu.com/s/1p6rcfKOeWQIVkuhVybzZmQ 
提取码:zr99

Spring系列

  1. Spring系列第1篇:为何要学spring?

  2. Spring系列第2篇:控制反转(IoC)与依赖注入(DI)

  3. Spring系列第3篇:Spring容器基本使用及原理

  4. Spring系列第4篇:xml中bean定义详解(-)

  5. Spring系列第5篇:创建bean实例这些方式你们都知道?

更多好文章

  1. Java高并发系列(共34篇)

  2. MySql高手系列(共27篇)

  3. Maven高手系列(共10篇)

  4. Mybatis系列(共12篇)

  5. 聊聊db和缓存一致性常见的实现方式

  6. 接口幂等性这么重要,它是什么?怎么实现?

  7. 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!

感谢大家的阅读,也欢迎您把这篇文章分享给更多的朋友一起阅读!谢谢!

﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌

b7305335296c19736844538c440a7399.png

如果觉得文章还不错,

大家可以扫码点个关注,

和你一起成长,学习更多知识。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/298552.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

女生的拳头有多厉害?

1 天下武功唯快不破2 狗子&#xff1a;没想到我有这种天赋3 哈哈哈还是你社会啊4 女生的拳头有多厉害&#xff1f;?5 怎么解开的&#xff1f;6 心中有海&#xff0c;脚下有沙&#xff0c;这就是马尔代夫7 你的人生概括

与ln的指数转化公式_高考数学48条秒杀型公式与方法

点上方蓝字&#xff0c;关注“云学冠学习资讯”有助于提高成绩哦&#xff01;除了课本上的常规公式之外&#xff0c;掌握一些必备的秒杀型公式能够帮你在考试的时候节省大量的时间&#xff0c;师姐这次的分享就是48条秒杀公式&#xff0c;直接往下看&#xff01;1.适用条件&…

在C#2.0中使用Nullable可空类型

在C#1.x的版本中&#xff0c;一个值类型变量是不可以被赋予null值的&#xff0c;否则会产生异常。在C#2.0中&#xff0c;微软提供了Nullable类型&#xff0c;允许用它定义包含null值&#xff08;即空值&#xff09;的数据类型&#xff0c;这对处理数据库中包含可选字段以及很多…

豆瓣9.7,这些舍不得看完的神剧,看一集少一集!我刷了一晚上……

全世界只有3.14 % 的人关注了爆炸吧知识今天给大家介绍一位好朋友——小樱&#xff0c;小樱&#xff0c;作为资深美剧迷英语博主&#xff0c;收藏了很多经典美剧、动画资源&#xff0c;每天还会和大家分享新鲜有趣的英语知识。喜欢看美剧追动画和立志学好英语的小伙伴千万别错过…

C#编码规范

1、连接数据库并返回DataTable View Code public DataTable GetDataByTable() { DataSet dataset new DataSet(); DataTable datatable null; SqlConnection sqlConnection new SqlConnection("Data Source.;Initial CatalogCalamity_Dat…

api中文文档 mws_中英文排版规范化 API

此文章对开放数据接口 API 之「中英文排版规范化 API」进行了功能介绍、使用场景介绍以及调用方法的说明&#xff0c;供用户在使用数据接口时参考之用。 1. 产品功能此次开放了中英文排版规范化在线接口&#xff0c;用于自动中英文排版、标点符号格式化&#xff0c;中英混排格式…

计算机管理无法连接虚拟磁盘服务,虚拟磁盘服务错误怎么操作【图文教程】

虚拟磁盘我们大家都可以看到但实际上是不存在的&#xff0c;虚拟的就是假的。虚拟磁盘是通过将一些网络文件或是内存利用科技手段伪造成磁盘&#xff0c;我们通常称它为虚拟磁盘。虚拟磁盘可以给内存增加容量&#xff0c;可以加快磁盘数据交换&#xff0c;提高计算机运行速度。…

记一次 .NET 某机械臂智能机器人控制系统MRS CPU爆高分析

一&#xff1a;背景 1. 讲故事这是6月中旬一位朋友加wx求助dump的故事&#xff0c;他的程序 cpu爆高➕UI卡死&#xff0c;问如何解决&#xff0c;截图如下&#xff1a;在拿到这个dump后&#xff0c;我发现这是一个关于机械臂的MRS程序&#xff0c;哈哈&#xff0c;在机械臂这种…

linux 信号_Linux中的信号处理机制 [四]

信号与线程Unix的信号机制在诞生之初&#xff0c;生活在只有进程(process)的相对单纯的环境中。自从Unix世界有了线程(thread)的概念&#xff0c;信号就被赋予了发往进程中某个特定线程的能力&#xff0c;当然&#xff0c;这也增加了整个信号机制实现的复杂度。本系列的前面三篇…

HP NIC Teaming技术探讨

NIC Teaming技术将2个或更多个网卡(HP NIC Teaming最多可达8个)捆绑在一起使用&#xff0c;以达到增加总的带宽(Load Balance&#xff0c;负载均衡)或者线路容错(Fault Tolerance)的目的。由2个或多个网卡组成一个逻辑网络端口Teamport&#xff0c;IP地址和网络设置绑定在这个逻…

仙居(一)

仙居&#xff08;一&#xff09; 仙居——仙人居住的地方。 景星岩全景 (看大图请点击) 其他图片在整理中&#xff0c;待我慢慢把图片和故事都理出来。转载于:https://www.cnblogs.com/hzy5901/archive/2011/03/31/5871611.html

我的电脑不联网,很安全!黑客:你还有风扇呢

全世界只有3.14 % 的人关注了爆炸吧知识转自&#xff1a;机器之心参与&#xff1a;张倩、蛋酱、杜伟从1988年第一个网络蠕虫病毒诞生以来&#xff0c;「互联网危机四伏」的观念就已经深入人心。如果只是这样&#xff0c;不给电脑联网、禁止使用任何可移动储存介质&#xff0c;数…

移动终端测试进化论

2019独角兽企业重金招聘Python工程师标准>>> 移动终端测试进化论 本案例主要以5年的现身经历&#xff0c;阐述针对移动互联网终端测试&#xff0c;从最基础的原始时代如何进入到现代化时代&#xff1b;从单一到多点密集&#xff1b;从对产品的质量保障进化到对产品信…

计算机视觉领域还有那些坑,深度学习/计算机视觉常见的8个错误总结及避坑指南...

人类并不是完美的&#xff0c;我们经常在编写软件的时候犯错误。有时这些错误很容易找到&#xff1a;你的代码根本不工作&#xff0c;你的应用程序会崩溃。但有些 bug 是隐藏的&#xff0c;很难发现&#xff0c;这使它们更加危险。在处理深度学习问题时&#xff0c;由于某些不确…

MySQL从原理到实践,一篇从头到尾讲清楚

前两天&#xff0c;我跟一个面试官聊天&#xff0c;发现一个普遍现象&#xff0c;不少候选人&#xff0c;对数据库的认知&#xff0c;还处在比较基础的阶段&#xff0c;以为会写“增删改查”、做表关联就足够了&#xff0c;那些工作中经常出现的问题&#xff0c;却支支吾吾答不…

库卡机器人是s7编程_「西门子1200PLC教程」19.S7-1200入门实例

头条号私信回复1&#xff0c;可免费获取海量资源下载链接本文任务&#xff1a;电动机启保停控制练习按下瞬时启动按钮I0.6&#xff0c;电动机Q0.0启动&#xff1b;按下瞬时停止按钮I0.7&#xff0c;电动机Q0.0停止。目录1.组态设备2.编写程序3.下载项目4.监视运行情况1.组态设备…

戴尔新版bios设置中文_戴尔电脑装机过程

戴尔电脑装机过程U盘制作过程&#xff1a;一&#xff1a;启动盘安装 win 101、准备工具&#xff1a;一块空的 8G 以上的 U 盘、一套官网下载的 win 10 操作系统 2、制作启动盘&#xff1a;将下载好的操作系统直接解压到U盘里面就完成了 二&#xff1a;pe 盘安装 win 101、准备工…

云计算

一 云计算 1 定义1&#xff09;新兴的互联网服务&#xff0c;该服务即是由成千上万的超级计算机构成的超强的计算机处理能力。2&#xff09;用户可以通过购买这种服务来满足本地对计算机运算能力需求。3&#xff09;互联网服务商一般会通过与某些需要计算机运算能力的软件的集成…

一组超炫酷的动图,感受那让人窒息的数学之美!

全世界只有3.14 % 的人关注了爆炸吧知识在蒋迅博客上看到的一组图片&#xff0c;转自imgur。本文来源&#xff1a;蒋迅的博客原文连接&#xff1a;http://blog.sciencenet.cn/blog-420554-923731.html

《飞机大战》安卓游戏开发源码(三)

为什么80%的码农都做不了架构师&#xff1f;>>> 本文章属于原创性文章&#xff0c;珍惜他人劳动成果&#xff0c;转载请注明出处&#xff1a;http://www.pm-road.com/index.php/2014/11/06/161/ 前言&#xff1a;最 近闲来无事&#xff0c;而且也是因为刚接触安卓不…