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

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

本文内容

  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@e874448com.javacode2018.lesson001.demo4.BeanScopeModel@e874448com.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 */@Testpublic 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@289d1c02create BeanScopeModel,{sope=prototype},{this=com.javacode2018.lesson001.demo4.BeanScopeModel@22eeefeb}com.javacode2018.lesson001.demo4.BeanScopeModel@22eeefebcreate 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@4049d530Thread[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@87a76daThread[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. 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!

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

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

如果觉得文章还不错,

大家可以扫码点个关注,

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

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

  1. spring 单例 获取多例的位_Spring 获取单例流程(一)

    读完这篇文章你将会收获到 在 getBean 方法中, Spring 处理别名以及 factoryBean 的 name Spring 如何从多级缓存中根据 beanName 获取 bean Spri ...

  2. Spring系列第10篇:primary可以解决什么问题?

    存在的问题以及解决方案 直接上案例,通过案例来看技术是如何使用的: package com.javacode2018.lesson001.demo8;public class NormalBean { ...

  3. Spring系列第20篇:@Conditional通过条件来控制bean的注册

    面试阿里p7被问到的问题(当时我只知道第一个): @Conditional是做什么的? @Conditional多个条件是什么逻辑关系? 条件判断在什么时候执行? ConfigurationCondi ...

  4. Spring系列第9篇:depend-on到底是干什么的?

    本文主要讨论一下bean的创建和销毁的顺序,如何来干预bean的创建和销毁的顺序. 无依赖bean创建和销毁的顺序 我们先来看一下没有任何依赖的bean的创建和销毁的顺序. 下面的xml中定义了3个b ...

  5. Spring从Bean获取的实例从单例变成多例(IOC依赖注入)

    用Spring框架进行一对多赋值时,在给List增加Bean的实例总是输出最后一个. 工程结构: Department.java Employee.java Application.xml <? ...

  6. 转载 spring单例bug

    https://www.cnblogs.com/fengzheng/p/14171443.html 这个 bug 让我更加理解 Spring 单例了 我是风筝,公众号「古时的风筝」,一个兼具深度与广度 ...

  7. Spring(07)——单例注入多例之lookup-method

    2019独角兽企业重金招聘Python工程师标准>>> Spring(07)--单例注入多例之lookup-method 博客分类: spring 7 单例注入多例之lookup-m ...

  8. 这个 bug 让我更加理解 Spring 单例了

    谁还没在 Spring 里栽过跟头呢,从哪儿跌倒,就从哪儿睡一会儿,然后再爬起来. 讲点儿武德 这是由一个真实的 bug 引起的,bug 产生的原因就是忽略了 Spring  Bean 的单例模式.来 ...

  9. Spring单例Bean与单例模式的区别

    Spring单例Bean与单例模式的区别在于它们关联的环境不一样,单例模式是指在一个JVM进程中仅有一个实例,而Spring单例是指一个Spring Bean容器(ApplicationContext ...

最新文章

  1. R计算数组的累计加和(Cumulative Sums)
  2. Ubuntu15.04安装IPSec/L2TP
  3. 九种将元器件从PCB上拆焊下的方法
  4. django Cookie,Session和自定义分页
  5. 学习笔记92—python 画横竖分界线
  6. 使用Visual Studio Code 运行编写第一个html文件
  7. 2017.2.19 loli测试
  8. POJ 3186 Treats for the Cows dp
  9. keytool生成证书_基于 TrueLicense 的项目证书验证
  10. java 折线_用Java绘制简单的折线图
  11. Python的map() 函数
  12. wpf之MVVM绑定背景色
  13. 导弹拦截(数据加强版)
  14. 光波波长划分和无线电波频段划分
  15. 论文阅读:MPViT : Multi-Path Vision Transformer for Dense Prediction
  16. Android WIFI认证的流程
  17. GlusterFS概述
  18. 实用的费曼学习法 | 一些思考
  19. Open FIFO for write returns “No such device or address“
  20. 十种获取被动收入的方法

热门文章

  1. 教你自制.NET Core Global Tools
  2. [aspnetcore.apidoc]一款很不错的api文档生成工具
  3. IdentityServer4与ocelot实现认证与客户端统一入口
  4. C#使用Xamarin开发可移植移动应用进阶篇(9.混淆代码,防止反编译)
  5. RabbitMQ系列教程之三:发布\/订阅(Publish\/Subscribe)
  6. 理想的互联网服务后台框架的九个要点
  7. .NET开发者如何使用MyCat
  8. 酷派手机android版本,系统版本迎来升级
  9. sublime text 3 安装、添加命令行启动、汉化、注册码
  10. linux c之动态打开链接库(dlopen dlsym dlclose)