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

 

目录

十五、单例设计模式

十六、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。

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PLC的高端版本通常具有以下特点:

高速处理能力&#xff1a;高端PLC通常具有更快的处理速度和更高的运行频率&#xff0c;可以处理更复杂的控制逻辑和更多的输入/输出信号。 大容量存储&#xff1a;高端PLC通常具有更大的存储容量&#xff0c;可以保存更多的程序和数据&#xff0c;以满足更复杂的应用需求。 多种…

uniapp 选择城市定位 根据城市首字母分类排序

获取城市首字母排序&#xff0c;按字母顺序排序 <template><view class"address-wrap" id"address"><!-- 搜索输入框-end --><template v-if"!isSearch"><!-- 城市列表-start --><view class"address-sc…

基于SSM实现个人随笔分享平台:创作心灵,分享自我

项目简介 本文将对项目的功能及部分细节的实现进行介绍。个人随笔分享平台基于 SpringBoot SpringMVC MyBatis 实现。实现了用户的注册与登录、随笔主页、文章查询、个人随笔展示、个人随笔查询、写随笔、草稿箱、随笔修改、随笔删除、访问量及阅读量统计等功能。该项目登录模…

【C语言day08】

int n5; int a[n][n2] 数组定义下角标不能为变量 注&#xff1a;C99标准中支持了使用变量本题考查的是二维数组的元素访问&#xff0c;A选项是 正确的&#xff0c;X[i]就是第i行的数组名&#xff0c;数组名表示首元素的地址&#xff0c;X[i]表示第i行的第一个元素的地址&#…

Qgis二次开发-QgsMapLayer(加载矢量、栅格图层)

1.简介 QgsMapLayer是所有地图层类型的基类&#xff0c;这是所有地图层类型(矢量&#xff0c;栅格)的基类&#xff0c;首先定义一个QgsMapCanvas地图画布&#xff0c;然后画布上添加图层&#xff0c;使用以下方法设置图层集合。 //设置当前图层集合 void setLayers (const QL…

【c语言进阶】字符函数和字符串函数知识总结

字符函数和字符串函数 前期背景求字符串长度函数strlen函数strlen函数三种模拟实现 长度不受限制的字符串函数strcpy函数strcpy函数模拟实现strcat函数strcat函数模拟实现strcmp函数strcmp函数模拟实现 长度受限制的字符串函数strncpy函数strncpy函数模拟实现strncat函数strnca…

【Qt】QML-02:QQuickView用法

1、先看demo QtCreator自动生成的工程是使用QQmlApplicationEngine来加载qml文件&#xff0c;下面的demo将使用QQuickView来加载qml文件 #include <QGuiApplication> #include <QtQuick/QQuickView>int main(int argc, char *argv[]) {QGuiApplication app(argc,…

electron dialog.showMessageBox使用案例

electron 版本&#xff1a;25.3.1 index.html <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>Hello World!</title><meta http-equiv"Content-Security-Policy" content"script-src self unsa…

MySQL绿色安装和配置

1、 从地址http://dev.mysql.com/downloads/mysql/中选择windows的版本下载。 2、 mysql各个版本的简介 &#xff08;1&#xff09; MySQL Community Server 社区版本&#xff0c;开源免费&#xff0c;但不提供官方技术支持。 &#xff08;2&#xff09; MySQL Enterprise Ed…

失去SSL证书,会对网站安全造成什么影响?

作为网络世界中的“身份证”&#xff0c;SSL证书可以在网络世界中证明你是一个真实可信的企业或个人网站&#xff0c;而不是一个钓鱼网站。且在网站的服务器上部署SSL证书后&#xff0c;可以使网站与访问者之间通过SSL协议建立安全的加密连接&#xff0c;确保在Web服务器和浏览…

【Unity细节】关于拉进镜头场景后场景资源消失的问题的解决

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏&#xff1a;unity细节和bug ⭐关于拉进镜头场景资源消失的问题的解决⭐ 文章目录 ⭐关于拉进镜头场景资源消失…

No100.精选前端面试题,享受每天的挑战和学习(事件循环)

文章目录 1. 请解释一下JavaScript中的事件循环&#xff08;Event Loop&#xff09;是什么&#xff0c;并描述其工作原理。2. 请解释一下JavaScript中的宏任务&#xff08;macro-task&#xff09;和微任务&#xff08;micro-task&#xff09;的区别3. 在事件循环中&#xff0c;…

移动IP的原理

目的 使得移动主机在各网络之间漫游时&#xff0c;仍然能保持其原来的IP地址不变 工作步骤 代理发现与注册 主机A&#xff1a;主机A移动到外地网络后&#xff0c;通过“代理发现协议”&#xff0c;与外地代理建立联系&#xff0c;并从外地代理获得一个转交地址&#xff0c;…

非线性质量弹簧阻尼器的神经网络仿真研究(Matlab代码Simulink仿真实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

浅谈性能测试中的基准测试

在性能测试中有一种测试类型叫做基准测试。这篇文章&#xff0c;就聊聊关于基准测试的一些事儿。 1、定义 通过设计合理的测试方法&#xff0c;选用合适的测试工具和被测系统&#xff0c;实现对某个特定目标场景的某项性能指标进行定量的和可对比的测试。 2、特质 ①、可重…

FPGA——verilog实现格雷码与二进制的转换

文章目录 一、格雷码简介二、二进制转格雷码三、格雷码转二进制四、仿真 一、格雷码简介 格雷码是一种循环二进制码或者叫作反射二进制码。跨时钟域会产生亚稳态问题&#xff08;CDC问题&#xff09;&#xff1a;从时钟域A过来的信号难以满足时钟域B中触发器的建立时间和保持时…

【ROS第一讲】一、创建工作空间

【ROS第一讲】一、创建工作空间 一、工作空间1.src&#xff1a;2.build&#xff1a;3.devel&#xff1a;4.install: 二、创建工作空间1.工作空间的编译2.配置环境变量&#xff1a; 三、创建功能包 一、工作空间 1.src&#xff1a; 放置所有功能包源码的空间 2.build&#xf…

vue中tab隐藏display:none(v-show无效,v-if有效)

目录 背景 原因&#xff1a;display: table-cell>display:none 解决&#xff1a; 方法A.获取元素设置display&#xff08;适用于 简单场景&#xff09; 方法B.自定义tabs​​​​​​​ &#xff08;适用于 复杂场景&#xff09; 背景 内联样式(style“ ”) /this.$…

redis群集(主从复制)

---------------------- Redis 主从复制 ---------------------------------------- 主从复制&#xff0c;是指将一台Redis服务器的数据&#xff0c;复制到其他的Redis服务器。前者称为主节点(Master)&#xff0c;后者称为从节点(Slave)&#xff1b;数据的复制是单向的&#xf…

Docker 如何助您成为数据科学家

一、说明 在过去的 5 年里&#xff0c;我听到了很多关于 docker 容器的嗡嗡声。似乎我所有的软件工程朋友都在使用它们来开发应用程序。我想弄清楚这项技术如何使我更有效率&#xff0c;但我发现网上的教程要么太详细&#xff1a;阐明我作为数据科学家永远不会使用的功能&#…