[Spring]用100行代码来模拟解决Spring的循环依赖问题

12 篇文章 0 订阅
7 篇文章 0 订阅

Abstract

好久没写文章了, 最近喜欢上了springboot, 也发现这个开发起来确实很方便和快捷。 包括spring kafka, jdbc, & actuator. 一天, 比较惊讶于spring如何优雅的解决循环依赖的问题的, 因为自己代码有时候也没注意, @Autowired 到处都是, 但是发现spring竟然可以正常工作,于是好奇的想了解下其实现。 所以就有了这个文章。

Spring如何解决循环依赖的?

官方文档:here

Circular dependencies
If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.
For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a BeanCurrentlyInCreationException.
One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.
Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken/egg scenario).

这里面提到2点:
(1) 确实可能存在鸡生蛋 蛋生鸡的问题。 如果有 则抛出异常 BeanCurrentlyInCreationException
(2) 可以试用set/post process来解决(不使用构造器依赖)。

然后百度了一下, 这篇文章写的很不错:here
总结来说就是: 递归调用+一个巧妙地early exposed map.

动手实现一个简单的解决循环依赖的版本

我们主要的目录结构如下: 完整源码在here
在这里插入图片描述BeanA & BeanB 两个测试bean。

package com.example.beans.beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BeanA {
    @Autowired
    private BeanB b;

    public BeanA() {
        System.out.println("A's no args construtor");
    }

    public void print(){
        System.out.println("bean a post construct is called");
        System.out.println("Is bean b is null in bean a 's post " + (b == null) + " bean a in current b(" + b.getBeana());
        b.printMe();
    }
}

package com.example.beans.beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BeanB {

    @Autowired
    private BeanA beana;

    public BeanA getBeana() {
        return beana;
    }

    public BeanB() {
        System.out.println("B's no args construtor");
        System.out.println("Is a null in b's constructor???" + (beana == null));
    }

    public void printMe(){
        System.out.println("Is a null in b's print me???" + (beana == null));
        System.out.println("print me is called in Bean B");
    }
}

这两个主要是BeanA 和BeanB相互引用。

MySpringImpl里面有我们的主要实现。

package com.example.beans.impl;

import com.example.beans.beans.BeanA;
import com.example.beans.beans.BeanB;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class MySpringImpl {

    public static void main(String[] args) throws Exception{
        String testPackage = "com.example.beans.beans";
        Context c = new Context(testPackage);
        System.out.println("Below is A==============");
        BeanA beanA = (BeanA) c.getBean(testPackage + "." + "BeanA");
        beanA.print();
        System.out.println("Below is b==============");
        BeanB beanB = (BeanB) c.getBean(testPackage + "." + "BeanB");
        beanB.printMe();
    }

    public static  class Context {
        private Map<String, Object> earlyExposed = new HashMap<>();
        private Map<String, Object> created = new HashMap<>();
        private Map<String,  ObjectFactory> objectFactoryMap = new HashMap<>();
        private Set<String> creating = new HashSet<>();

        private Map<String, Class> defines = new HashMap<>();

        public Context(String pack) {
            // scans all packages under this classes....
            try {
                Class[] clzList = ReflectionUtils.getClasses(pack);
                // assume all has a @Service tag
                // init all objects
                for (Class c : clzList) {
                    defines.put(c.getName(), c);

                }
            }
            catch (Exception e) {
                e.printStackTrace();
            }

        }

        public Object getBean(String beanName) throws Exception {
            Object o = getSingleton(beanName);
            if (o == null) {
               o = getSingleton(beanName, new ObjectFactory() {

                    @Override
                    public Object getObject(String c) {
                        try {
                            return creatBean(beanName);
                        }
                        catch (Exception e) {
                            e.printStackTrace();
                            throw new IllegalArgumentException(e);
                        }
                    }
                });

            }
            return o;
        }

        private Object creatBean(String beanName) throws Exception {
            Object shared = Class.forName(beanName).newInstance();
            if (creating.contains(beanName)) {
                objectFactoryMap.put(beanName, new ObjectHolder(shared));
            }
            // get declared objects
            List<Field> fs = ReflectionUtils.getFieldsByAnnotation(Class.forName(beanName), Autowired.class);
            for (Field f : fs) {
                f.setAccessible(true);
                Object v = getBean(f.getType().getName());
                f.set(shared, v);
            }
            creating.remove(beanName);
            objectFactoryMap.remove(beanName);
            return shared;
        }

        private Object getSingleton(String beanName, ObjectFactory objectFactory) throws Exception {
            return objectFactory.getObject(beanName);
        }
        private Object getSingleton(String beanName) throws Exception {
            if (!defines.containsKey(beanName)) {
                throw new IllegalArgumentException("not register - " + beanName);
            }
            Object target = created.get(beanName);
            if (target != null) {
                return target;
            }

            if (creating.contains(beanName)) {
                target = earlyExposed.get(beanName);
                if (target == null) {
                    ObjectFactory objectFactory = objectFactoryMap.get(beanName);
                    if (objectFactory != null) {
                        target = objectFactory.getObject(beanName);
                        objectFactoryMap.remove(beanName);
                        earlyExposed.put(beanName, target);
                    }
                }
            } else {
                creating.add(beanName);
            }
            return target;
        }


    }

    public interface ObjectFactory {
        Object getObject(String c);
    }

    public static class ObjectHolder implements ObjectFactory {
        private Object o;
        public ObjectHolder(Object o) {
            this.o = o;
        }

        @Override
        public Object getObject(String c) {
            return o;
        }
    }

}

除去注释, 大概只有100行代码。 建议单步Debug这个代码来加深对spring的理解(结合前文)。 如果想通了发现也不是很复杂。

总结

调用构造器, 放入到earlyExposedMap, 然后递归调用对象依赖的属性方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
循环依赖是指两个或多个类之间相互依赖,互相引用,导致无法正常初始化和加载。在Spring容器中,循环依赖的情况是比较常见的,Spring提供了多种方式来解决循环依赖问题。 以下是一个简单的模拟Spring循环依赖的例子: ```java public class A { private B b; public A() {} public void setB(B b) { this.b = b; } } public class B { private A a; public B() {} public void setA(A a) { this.a = a; } } public class Main { public static void main(String[] args) { A a = new A(); B b = new B(); a.setB(b); b.setA(a); } } ``` 在上述例子中,类A和类B相互依赖,即A中包含B的实例,B中也包含A的实例。在Main类中,我们创建了A和B的实例,并且相互引用。但是,由于A和B的依赖关系是相互的,这样的初始化过程会导致死循环和栈溢出。 为了解决这个问题Spring提供了三种方式: 1. 构造函数注入:使用构造函数注入可以避免循环依赖问题,因为它强制要求所有依赖项在实例化时都必须提供。在这种情况下,Spring容器将在创建对象时处理循环依赖。 ```java public class A { private B b; public A(B b) { this.b = b; } } public class B { private A a; public B(A a) { this.a = a; } } ``` 2. Setter注入:使用Setter方法注入可以避免循环依赖问题,因为它允许对象在创建后注入依赖项。在这种情况下,Spring容器将在创建对象后处理循环依赖。 ```java public class A { private B b; public A() {} public void setB(B b) { this.b = b; } } public class B { private A a; public B() {} public void setA(A a) { this.a = a; } } ``` 3. 接口注入:使用接口注入可以避免循环依赖问题,它是一种特殊的Setter注入方式,它允许对象注入接口的实现。在这种情况下,Spring容器将在创建对象后处理循环依赖。 ```java public interface IA { void setB(B b); } public interface IB { void setA(A a); } public class A implements IA { private B b; public A() {} @Override public void setB(B b) { this.b = b; } } public class B implements IB { private A a; public B() {} @Override public void setA(A a) { this.a = a; } } ``` 以上是三种解决循环依赖的方式,但是在实际应用中,我们应该尽量避免循环依赖的情况,避免出现不必要的麻烦。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值