Spring 6【单例设计模式、bean标签的scope属性、Spring 循环注入问题】(八)-全面详解(学习总结---从入门到深化)

news2024/11/16 19:58:14

 

目录

十五、单例设计模式

十六、bean标签的scope属性

十七、Spring 循环注入问题


 

十五、单例设计模式

设计模式:根据面向对象五大设计思想衍生出的23种常见代码写法,每种写法可以专门解决一类问题。

单例设计模式:保证某个类在整个应用程序中只有一个实例。

单例设计默认有很多种写法,我们讲解其中两种:饿汉式、懒汉式。

重要提示: 单例设计模式必须达到用纸手写的能力。

1.饿汉式 

package com.tong.singleton;
/*
单例:希望类只有一个
核心思想:
1. 构造方法私有
2. 对外提供一个能够获取对象的方法。
饿汉式:
优点:实现简单
缺点:无论是否使用当前类对象,加载类时一定会实例化。
*/
public class Singleton {
     // 之所以叫做饿汉式:因为类加载时就创建了对象
     private static Singleton singleton = new Singleton();
     private Singleton(){}
     public static Singleton getInstance(){
           return singleton;
   }
}

2.懒汉式

package com.tong.singleton;
/**
* 核心思想:
* 1. 构造方法私有
* 2. 对外提供一个能够获取对象的方法。
*
* 懒汉式优点和缺点:
* 优点:
* 按需创建对象。不会在加载类时直接实例化对象。
* 缺点:
* 写法相对复杂
* 多线程环境下,第一次实例化对象效率低。
*/
public class Singleton2 {
    //懒汉式:不会立即实例化
    private static Singleton2 singleton2;
    private Singleton2() {}
    public static Singleton2 getInstance() {
       if (singleton2 == null) {// 不是第一次访问的线程,直接通过if判断条件不成立。直接
             return
          synchronized (Singleton2.class) {
             if(singleton2==null) {// 防止多个线程已经执行到synchronized
                   singleton2 = new Singleton2();
                }
           }
        }
        return singleton2;
    }
}

十六、bean标签的scope属性

1. 官方默认支持的scope属性值

Spring中 的scope控制的是Bean的作用域。也可以用注解@Scope("singleton")控制。

通过调整scope属性的取值,可以控制bean的有效范围。

一共有6个可取值,官方截图如下:

翻译过来:

singleton:默认值。bean是单例的,每次获取Bean都是同一个对象。 

prototype:原型,每次获取bean都重新实例化。

 

request:每次请求重新实例化对象,同一个请求中多次获取时单例的。

session:每个会话内bean是单例的。

application:整个应用程序对象内bean是单例的。

websocket:同一个websocket对象内对象是单例的。

里面的singleton和prototype在Spring最基本的环境中就可以使用,不需要web环境。 

但是里面的request、session、application、websocket都只有在web环境才能使用。

<bean id="people" class="com.tong.pojo.People" scope="singleton"></bean>

 衍生问题:Spring 中 Bean是否是线程安全的?

如果bean的scope是单例的,bean不是线程安全的。

如果bean的scope是prototype,bean是线程安全的。

2.ThreadLocal 复习

ThreadLocal是JDK 1.2 出现类。通过ThreadLocal可以给每个线程提供一个局部变量。只要线程对象不 变,可以随时获取。

一个ThreadLocal可以存储一个Object类型值。具体可以是一个String,一个List或一个Map,具体存储值 类型可以使用泛型进行控制。 

@Test
void testThreadLocal(){
    ThreadLocal<String> tl = new ThreadLocal<String>();
    tl.set("smallming");
    new Thread(){
        @Override
        public void run() {
              System.out.println("其他线程:"+tl.get());
          }
        }.start();
        System.out.println(tl.get());
  }

 在ThreadLocal中有很多子类。

其中NamedThreadLocal是一个允许给这个局部变量起名字的实现类。使用全局final属性记录这个名 字。

 

如果ThreadLocal类及子类设置的泛型需要赋予初始值,可以重写initilaValue()方法。 

 

示例代码:

@Test
void test2(){
     ThreadLocal<Map<String,Object>> tl = new NamedThreadLocal<>("名字"){
     @Override
     protected Map<String, Object> initialValue() {
          return new HashMap<>();
       }
  };

 tl.get().put("name","smallming");// 主线程放一个值进去
 new Thread(){
         @Override
         public void run() {
             System.out.println("子线程:"+tl.get().size());
         }
    }.start();
    System.out.println("主线程:"+tl.get().size());
}

 3.SimpleThreadScope 源码分析

bean的作用域是允许自定义的。

想要自定义scope,必须让自定义类实现org.springframework.beans.factory.config.Scope接口。接口 中一共有5个方法。

 

在Spring框架内部,Scope接口有且只有一个实现类SimpleThreadScope,表示在同一个线程内Bean是单例的。

 

// 实现Scope接口,这是自定义Scope的强制语法要求
public class SimpleThreadScope implements Scope {
     // 定义日志对象
     private static final Log logger = LogFactory.getLog(SimpleThreadScope.class);
     // 创建一个ThreadLocal对象
     // 线程变量类型为Map<String,Object>
     private final ThreadLocal<Map<String, Object>> threadScope =
              // ThreadLocal 名称为SimpleThreadScope
              new NamedThreadLocal<>("SimpleThreadScope") {
              @Override
              protected Map<String, Object> initialValue() {
                    // 给线程变量初始化
                    return new HashMap<>();
            }
      };
// 每次从IoC容器获取Bean时会被触发
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
     // 取到线程变量
     Map<String, Object> scope = this.threadScope.get();
    // 从Map中取出值
    Object scopedObject = scope.get(name);
    // 如果值为null,重试实例化
    if (scopedObject == null) {
         // 重新获取对象
         scopedObject = objectFactory.getObject();
         // 把对象放入到map中
         scope.put(name, scopedObject);
      }
    // 返回对象,不返回后续操作空指针
    return scopedObject;
  }
@Override
@Nullable
public Object remove(String name) {
      // 获取线程变量
      Map<String, Object> scope = this.threadScope.get();
      // 把指定name值从Map中移除
      return scope.remove(name);
   }
   
@Override
public void registerDestructionCallback(String name, Runnable callback) {
      // 日志记录
     logger.warn("SimpleThreadScope does not support destruction callbacks. " +
     "Consider using RequestScope in a web environment.");
 }

@Override
@Nullable
public Object resolveContextualObject(String key) {
       // 没做任何处理
       return null;
}
@Override
public String getConversationId() {
      // 会话ID为当前线程的名字。
      return Thread.currentThread().getName();
    }
}

4.自定义Scope具体实现步骤

4.1 有一个类实现Scope接口

因为SimpleThreadScope是Spring框架内置实现Scope实现的实现类,我们就模仿它,写一个类。 SimpleThreadScope 表示的含义是在同一个线程内,多次获取都是单例的。

4.2 注册自定义类,并配置Bean的scope

首先在配置文件注册这个Scope。

<!-- CustomScopeConfigurer 是Spring框架中提供的注册 -->
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
         <!-- key 的值是自定义的,此处叫什么,下面people2的scope中就多一个什么值 -->
         <entry key="thread123">
            <bean  class="org.springframework.context.support.SimpleThreadScope"/>
         </entry>
       </map>
     </property>
</bean>
<!-- 指定people2的scope为thread123 -->
<bean id="people2" class="com.tong.scope.People" scope="thread123">
    <property name="name" value="smallming"></property>
</bean>

 4.3 测试效果

在测试类中,在不同线程内查看获取到的对象是否是同一个对象。

@Autowired
ApplicationContext ac;
@Test
void test2(){
      People p1 = ac.getBean("people2", People.class);
      new Thread(){
           @Override
           public void run() {
                  People p2 = ac.getBean("people2", People.class);
                  System.out.println("p2:"+p2);
                  System.out.println("p1:"+p1);
                  System.out.println("p1==p2:"+(p1==p2)); // 结果为false
             }
       }.start();
       People p3 = ac.getBean("people2", People.class);
       System.out.println("p1==p3:"+(p1==p3)); // 结果为true
}

十七、Spring 循环注入问题

我们前面学习了DI的两种方式,分别是构造注入和设值注入。这两种方式,官方更推荐使用构造注入。

网址:https://docs.spring.io/springframework/docs/current/reference/html/core.html#beans-setter-injection

 

但是有一种情况确实无法使用构造注入,就是循环依赖的问题。 循环依赖就是多个Bean相互依赖,形成一个闭环。

 

下面我们先看看Spring 官方中对构造注入时出现循环注入的解释。

当两个类都是用构造注入时,没有等当前类实例化完成就需要注入另一个类,而另一个类没有实例化完 整还需要注入当前类,所以这种情况是无法解决循环注入问题的的。会出现 BeanCurrentlyInCreationException异常。 

 

其实Spring循环注入问题并不是我们开发者去解决的,而是Spring本身会根据我们的代码进行解决。其 中有的情况能解决,有的会直接报异常。汇总如下:

第一种:两个Bean都是用构造注入时,且scope为singleton是有循环注入异常的。

第二种:两个Bean都是用构造注入时,且scope为prototype是有循环注入异常的。

第三种:如果Bean的scope属性为prototype时,使用设值注入是有循环注入异常的。

第四种:如果Bean的scope属性都为singleton时,使用设值注入Spring没有循环注入异常。

第五种:如果一个Bean的scope为singleton,另一个Bean的scope为prototype,都使用设置注入时没有循环注入异常。

第六种:如果一个Bean使用设值注入,且scope为singleton,另一个Bean使用构造注入,是没有循环注入异常的。 

通过这六种情况可以看出来,只要一个Bean使用设值注入,并且scope为singleton,就没有循环注入异 常。

下面通过代码给小伙伴们演示一下构造注入时循环注入的效果。

在搭建好Spring环境的项目中新建两个类: 先新建com.tong.circular.Teacher类代表老师 

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private Student student;
}

 然后在新建个com.tong.circular.Student类,代表学生

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private Teacher teacher;
}

在Spring的配置文件applicationContext.xml中设置两个Bean的循环注入

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

   <bean id="student" class="com.tong.circular.Student">
       <constructor-arg name="teacher" ref="teacher"></constructor-arg>
   </bean>
   <bean id="teacher" class="com.tong.circular.Teacher">
       <constructor-arg name="student" ref="student"></constructor-arg>
   </bean>
</beans>

最后在测试类com.tong.test.CircularTest中编写测试代码

@SpringJUnitConfig
@ContextConfiguration("classpath:applicationContext-circular.xml")
public class CircularTest {
     @Autowired
     Teacher teacher;
     @Test
     void test(){
          System.out.println(teacher);
   }
}

运行测试类后会发现IDEA控制台出现异常。最后一个Cased by的异常类型是Caused by: org.springframework.beans.factory.BeanCreationException,代表着发生了循环注入问题。

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'student': Requested bean is currently in creation: Is there an unresolvable circular reference? 

这种方式可以理解,因为在Java代码中下面代码就编译错误了。

Teacher teacher = new Teacher(student);// 编译错误
Student student = new Student(teacher);

 第二种情况,两个Bean的scope都是prototype类型,依然使用构造注入。运行后依然出现 BeanCurrentlyInCreationException

<bean id="student" class="com.tong.circular.Student" scope="prototype">
      <constructor-arg name="teacher" ref="teacher"></constructor-arg>
</bean>
<bean id="teacher" class="com.tong.circular.Teacher" scope="prototype">
      <constructor-arg name="student" ref="student"></constructor-arg>
</bean>

第三种情况,使用设值注入。如果Bean的scope属性为prototype时,循环注入的效果。 我们先把配置修改一下,注入的方式修改为设置注入,并设置Bean的scope="prototype"

 

<bean id="student" class="com.tong.circular.Student" scope="prototype">
     <property name="teacher" ref="teacher"></property>
</bean>
<bean id="teacher" class="com.tong.circular.Teacher" scope="prototype">
     <property name="student" ref="student"></property>
</bean>

运行测试类,发现依然会产生循环注入问题。控制台还是出现 BeanCurrentlyInCreationException异常。

第四种情况,使用设值注入。Bean的scope属性为singleton时,循环注入的效果。

只需要修改配置文件中,把<bean> 的scope属性设置为singleton就可以了。

<bean id="student" class="com.tong.circular.Student" scope="singleton">
     <property name="teacher" ref="teacher"></property>
</bean>
<bean id="teacher" class="com.tong.circular.Teacher" scope="singleton">
     <property name="student" ref="student"></property>
</bean>

 运行后没有循环注入异常了,但是出现StackOverflowError.是因为@Data生成的toString()中循环包含对方对象。

java.lang.StackOverflowError

修改两个类的,在关联属性都添加上不被toString()输出。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    @ToString.Exclude
    private Teacher teacher;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    @ToString.Exclude
    private Student student;
}

再次运行,程序可以正常输出。

这种方式之所以可以成功运行是因为单例默认下有三级缓存(DefaultSingletonBeanRegistry),可以暂时 缓存没有被实例化完成的Bean。这样就不用考虑Bean实例化时先后问题,也就不会出现循环注入问题 了。

第四种情况,使用设值注入。一个类scope="singleton",另外一个类scope="prototype"。

<bean id="student" class="com.tong.circular.Student" scope="prototype">
    <property name="teacher" ref="teacher"></property>
</bean>
<bean id="teacher" class="com.tong.circular.Teacher" scope="singleton">
    <property name="student" ref="student"></property>
</bean>

 这时发现,两个类都可以被成功注入。 第五种情况,一个类使用构造注入,另一个类使用设置注入并且scope="singleton"

<bean id="student" class="com.tong.circular.Student" scope="singleton">
     <property name="teacher" ref="teacher"></property>
</bean>
<bean id="teacher" class="com.tong.circular.Teacher" >
     <constructor-arg name="student" ref="student"></constructor-arg>
</bean>

 通过这些演示后小伙伴们知道了只要Bean的scope="singleton"就不会出现循环注入问题。那么在平时 我们进行代码编写时,尽量避开循环注入。如果实在无法避开,类中涉及到两个类的相互引用。例如: 双向多对一、双向一对一的关系中就必须有双向引用。这时最好使用设值注入,并且scope设置为 singleton。

 

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

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

相关文章

一文搞懂加密和接口签名小知识

最近在做的接口自动化测试工程中&#xff0c;一些接口需要签名&#xff0c;涉及到了加解密的一些知识&#xff0c;顺手梳理了下&#xff0c;分享给大家。 主要分为四个部分介绍&#xff1a; 一、基础概念 二、加密算法介绍 三、接口签名 四、实例分析 一、基础概念 加密是…

Leetcode 27 移除元素 代码逐行讲解

Leetcode 27 移除元素 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考…

Go语言中的结构体详解

关于 Golang 结构体 Golang 中没有“类”的概念&#xff0c;Golang 中的结构体和其他语言中的类有点相似。和其他面向对 象语言中的类相比&#xff0c;Golang 中的结构体具有更高的扩展性和灵活性。 Golang 中的基础数据类型可以表示一些事物的基本属性&#xff0c;但是当我们…

【Python】二维离散小波变换(2D-DWT)实现

文章目录 小波变换程序实现子带数学公式 小波变换 小波变换&#xff08;Wavelet Transform&#xff09;是一种数学信号处理技术&#xff0c;用于将信号或图像分解为不同频率的小波成分&#xff0c;从而可以在不同时间尺度上分析信号的特征。小波变换具有许多重要的特性&#x…

安装Python之后 安装库报错 There was an error checking the latest version of pip.

报错代码 & 图片如下 Looking in indexes: https://pypi.tuna.tsicmdnghua.edu.cn/simple WARNING: Retrying (Retry(total4, connectNone, readNone, redirectNone, statusNone)) after connection broken by NewConnectionError(<pip._vendor.urllib3.connection.HT…

Matplotlib_概述_绘制图象

⛳绘制基础 在使用 Matplotlib 绘制图形时&#xff0c;其中有两个最为常用的场景。一个是画点&#xff0c;一个是画线。 pyplot 基本方法的使用如下表所示 方法名说明title()设置图表的名称xlabel()设置 x 轴名称ylabel()设置 y 轴名称xticks(x, ticks, rotation)设置 x 轴的…

【蓝图】p44简单解密机关

p44简单解密机关 p44简单解密机关文字提示开门文字提示开灯For Each Loop和For Each Loop With Break区别For Each LoopFor Each Loop With Break小操作&#xff1a;改变走线Execute Console Command(执行控制台命令) p44简单解密机关 文字提示开门 创建Actor蓝图类&#xff…

软件测试生命周期

本章简要介绍了软件开发项目中常用的生命周期模型&#xff0c;并解释了测试在每个模型中扮演的角色。它讨论了各种测试级别和测试类型之间的区别&#xff0c;并解释了这些在开发过程中的应用位置和方式。 大多数软件开发项目是按照事先选择的软件开发生命周期模型来计划和执行…

win11任务栏不合并 终于回归啦

25915.1000 win11任务栏不合并 终于回归啦&#xff01;&#xff01;&#xff01; 下载地址&#xff1a;https://uup.rg-adguard.net/

Jenkins搭建最简教程

纠结了一小会儿&#xff0c;到底要不要写这个&#xff0c;最终还是决定简单记录一下&#xff0c;因为Jenkins搭建实在是太简单了&#xff0c;虽然也有坑&#xff0c;但是坑主要在找稳定的版本上。 先学一个简称&#xff0c;LTS (Long Term Support) 属实是长见识了&#xff0c…

Excel透视表与python实现

目录 一、Excel透视表 1、源数据 2、数据总分析 3、数据top分析 二、python实现 1、第一张表演示 2、第二张表演示 一、Excel透视表 1、源数据 1&#xff09;四个类目&#xff0c;每类50条数据 2&#xff09;数据内容 2、数据总分析 1&#xff09;选择要分析的字段&…

live-server本地起node服务解决跨域问题

一、初始化node,构建package.json NPM 全局安装live-server npm install -g live-server在当前项目文件夹下cmd运行&#xff1a; npm init -y此时会在根目录下生成一个package.json文件。 二.生成代理脚本 在根文件夹新建一个build.js文件&#xff08;名字可以自定义) var …

银行项目性能压测?关键链路性能压力测试,测试老鸟总结...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 随着银行业数字化…

Python将COCO格式实例分割数据集转换为YOLO格式实例分割数据集

Python将COCO格式实例分割数据集转换为YOLO格式实例分割数据集 前言相关介绍COCO格式实例分割数据集转换为YOLO格式实例分割数据集coco格式对应的json文件&#xff0c;以test.json为例格式转换代码&#xff0c;内容如下 前言 由于本人水平有限&#xff0c;难免出现错漏&#xf…

MySQL基础(五)主从复制及读写分离

目录 前言 一、概述 &#xff08;一&#xff09;、MySQL Replication &#xff08;二&#xff09;、MySQL复制类型 &#xff08;三&#xff09;、MySQL支持的复制方式 二、部署MySQL主从异步复制 &#xff08;一&#xff09;、master&#xff08;主&#xff09; &#x…

一起来学shiny把(3)—-添加控件

什么是shiny&#xff1f;Shiny是一个R包&#xff0c;可让您轻松地直接从 R 构建交互式 Web 应用程序&#xff08;应用程序&#xff09;。本系列是个长教程&#xff0c;带你由浅入深学习shiny。 上一节我们在文章《R语言系列教程—–一起来学shiny吧&#xff08;2&#xff09;》…

【C++11】——右值引用、移动语义

目录 1. 基本概念 1.1 左值与左值引用 1.2 右值和右值引用 1.3 左值引用与右值引用 2. 右值引用实用场景和意义 2.1 左值引用的使用场景 2.2 左值引用的短板 2.3 右值引用和移动语义 2.3.1 移动构造 2.3.2 移动赋值 2.3.3 编译器做的优化 2.3.4 总结 2.4 右值引用…

开源盛行:为什么学习国产达梦数据库?

开源盛行&#xff1a;为什么学习国产达梦数据库&#xff1f; 武汉达梦数据库股份有限公司成立于2000年&#xff0c;是国内领先的数据库产品开发服务商&#xff0c;国内数据库基础软件产业发展的关键推动者。公司为客户提供各类数据库软件及集群软件、云计算与大数据等一系列数据…

【数据结构】·顺序表函数实现·赶紧学起来呀

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

element-ui使用动态渲染下拉选择框el-select已经选择的下拉框的值不可以重复选择让其disabled

调接口拿到下拉框数据的数据的时候将其disabled全为true 但是如果编辑的时候就需要与详情接口对比&#xff0c;如果有id一致就将disabled为true if (res.code 0) {if (this.dialogtitle "新增合同") {res.data.map((v) > {v.nameUnitVoList.forEach((item) >…