spring循环依赖和设计模式

1 循环依赖

循环依赖就是 在 A中引用了B,在B中引用了C,在C中引用了A。
导致无限循环的一个场景。 循环依赖通常是有糟糕的编程引起的。

循环依赖会导致内存的溢出,因为会不停的创建 A B C,同时,内存也不会回收,因为相互引用。

2 解决办法

单例

如果在A B C中有一个是单例,则不会出现问题,因为单例在全局只会有一个,假如A是单例的,那么A创建好之后,C再去引用A时,不会再去创建一个新的A,所以并不会出现问题。

spring 帮助我们解决属性上的循环依赖问题

spring只能解决属性的循环依赖问题,不能解决构造器的循环依赖问题。

spring通过三级缓存解决了属性循环依赖的问题,一级缓存:里面存放了初始化好的实例。三级缓存:里面存放了没有初始化好的实例。spring通过可以返回三级缓存中的实例,然后从三级缓存中移除,存入到二级缓存中,使得属性的循环依赖问题得到解决。

这里参考了这篇文章写的案例:链接: https://www.jianshu.com/p/b07862d4824c

演示代码 重点,编写一个类-SimpleContainer,模仿Spring底层处理循环依赖。如果理解这个代码,再去看Spring处理循环依赖的逻辑就会很简单。

package com.tech.ioc;

import java.beans.Introspector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 演示Spring中循环依赖是如何处理的,只是个简版,真实的Spring依赖处理远比这个复杂。
 * 但大体思路都相同。另外这个Demo很多情况都未考虑,例如线程安全问题,仅供参考。
 * @author wang hai xin
 *
 * **/
public class SimpleContainer {

    /***
     * 用于存放完全初始化好的Bean,Bean处于可状态
     * 这个Map定义和Spring中一级缓存命名一致
     * */
    private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    /***
     * 用于存放刚创建出来的Bean,其属性还没有处理,因此存放在该缓存中的Bean还不可用。
     * 这个Map定义和Spring中三级缓存命名一致
     * */
    private final Map<String, Object> singletonFactories = new HashMap<>(16);

    public static void main(String[] args) {
        SimpleContainer container = new SimpleContainer();
        ComponentA componentA = container.getBean(ComponentA.class);
        componentA.say();
    }

    public <T> T getBean(Class<T> beanClass) {
        String beanName = this.getBeanName(beanClass);
        // 首先根据beanName从缓存中获取Bean实例
        Object bean = this.getSingleton(beanName);
        if (bean == null) {
            // 如果未获取到Bean实例,则创建Bean实例
            return createBean(beanClass, beanName);
        }
        return (T) bean;
    }
    /***
     * 从一级缓存和二级缓存中根据beanName来获取Bean实例,可能为空
     * */
    private Object getSingleton(String beanName) {
        // 首先尝试从一级缓存中获取
        Object instance = singletonObjects.get(beanName);
        if (instance == null) { // Spring 之所以能解决循环依赖问题,也是靠着这个singletonFactories
            instance = singletonFactories.get(beanName);
        }
        return instance;
    }

    /***
     * 创建指定Class的实例,返回完全状态的Bean(属性可用)
     *
     * */
    private <T> T createBean(Class<T> beanClass, String beanName) {
        try {
            Constructor<T> constructor = beanClass.getDeclaredConstructor();
            T instance = constructor.newInstance();
            // 先将刚创建好的实例存放到三级缓存中,如果没有这一步,Spring 也无法解决三级缓存
            singletonFactories.put(beanName, instance);
            Field[] fields = beanClass.getDeclaredFields();
            for (Field field : fields) {
                Class<?> fieldType = field.getType();
                field.setAccessible(true); 
                // 精髓是这里又调用了getBean方法,例如正在处理ComponentA.componentB属性,
                // 执行到这里时就会去实例化ComponentB。因为在getBean方法首先去查缓存,
                // 而一级缓存和三级缓存中没有ComponentB实例数据,所以又会调用到当前方法,
                // 而在处理ComponentB.componentA属性时,又去调用getBean方法去缓存中查找,
                // 因为在前面我们将ComponentA实例放入到了三级缓存,因此可以找到。
                // 所以ComponentB的实例化结束,方法出栈,返回到实例化ComponentA的方法栈中,
                // 这时ComponentB已经初始化完成,因此ComponentA.componentB属性赋值成功!
                field.set(instance, this.getBean(fieldType));
            }
            // 最后再将初始化好的Bean设置到一级缓存中。
            singletonObjects.put(beanName, instance);
            return instance;
        } catch (Exception e) {
            e.printStackTrace();
        }
        throw new IllegalArgumentException();
    }

    /**
     * 将类名小写作为beanName,Spring底层实现和这个差不多,也是使用javaBeans的
     * {@linkplain Introspector#decapitalize(String)}
     **/
    private String getBeanName(Class<?> clazz) {
        String clazzName = clazz.getName();
        int index = clazzName.lastIndexOf(".");
        String className = clazzName.substring(index);
        return Introspector.decapitalize(className);
    }
}

当然 spring的源码要比这个案例复杂,考虑更多的场景,不过思路是一样的。

原型模式

原型模式:旨在创建重复对象,又能保证性能。
实现克隆操作:通过实现clonebale接口,可以实现浅克隆。通过序列化和反序列化,可以实现深克隆。
优点:1.性能提高 2.逃避构造函数的约束
缺点:1.对于克隆方法要对类的功能进行通盘考虑,对于全新类可能比较简单,但是对于已有类,特别类引用不支持串行化的间接对象或者含有循环结构的时候
2.必须实现cloneable接口
如果不是单例模式,使用了原型模式,就会出现循环依赖的死循环中。
原型模式会检查引用,如果引用对象没有实例化,去实例化引用对象,导致死循环。

单例模式

单例模式:全局只有一个
分为饿汉式和懒汉式
饿汉式则是在项目初始化时就初始化这个类,并保证全局只有一个

饿汉式:演示代码


/**
 * @program: solution
 * @description: 单例 懒汉式
 * @author: Wang Hai Xin
 * @create: 2022-12-19 10:22
 **/
public class T1 {

    /*懒汉式,则是声明一个私有化的变量,然后直接实例化对象给到这个变量*/
    private static T1 t = new T1();

    /*将构造方法私有化*/
    private T1(){

    }
/*获取唯一可用对象*/
    public static T1 init(){
        return t;
    }

//    这里忽略功能代码
}


问题: 如果这个类的内容特别多,使用也不是很多的情况下,使用饿汉式会消耗性能。
懒汉式则可以一定程度上解决这个问题

懒汉式汉式:演示代码


/**
 * @program: solution
 * @description: 单例模式 懒汉式
 * 解决问题: 如果这个类的内容特别多,使用也不是很多的情况下,使用饿汉式会消耗性能。
 * 懒汉式则可以一定程度上解决这个问题
 * @author: Wang Hai Xin
 * @create: 2022-11-03 18:34
 **/
public class T {
    /*懒汉式,并不会直接将这个私有变量赋值,而是在确实需要使用的时候才会去赋值
    * 同时,使用volatile关键字 防止指令的重排序,防止在new对象的时候,对象还没有初始化完成
    * 但是已经将t指向了实例化对象空间
    * */
    private static  volatile T t;
    public T init(){
        /*这里写init的其它逻辑*/

        /*双重锁,最大程度上减少被锁住的线程*/
        if (t == null) {
            synchronized (T.class){
                if (t == null) {
                   t =  new T();
                }
            }
        }
       return t;
    }
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑白极客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值