手写简单的spring(2):实现循环依赖


前言

相信凡是写Java的同学都知道或者背过Spring实现循环依赖的方式吧,无非是三个缓存工厂,然后每个工厂放object的什么形态,bla-bla之类的。之前我也写过一篇博客,图解过 。不过,如果我们自己来实现,该如何写呢?接着我们上一篇的博客,继续探索一下。


一、前期辅助

1.1 最朴素的想法

既然我们需要实现一个循环依赖,那么我们首先来写个demo

public class CycleService1{

    private CycleService2 cycleService2;

}

public class CycleService2{

    private CycleService1 cycleService1;

}

那我们必然想实现的是CycleService1在注入CycleService2的时候,能够获取到CycleService1,但是此时其实CycleService1还只是一个属性为空的object,暂且称之为earlyObject,还没有放入到我们的BEAN_MAP,那么最朴素直观的想法就是,如果我们暂时把这个earlyObject放到某个容器NON_FIELD_BEAN_MAP中,最后等这个earlyObject的属性设置好了之后,再把它从earlyObject从NON_FIELD_BEAN_MAP 中移动到BEAN_MAP中。
那我们对之前的MyContainer进行一下简单的改造

@Slf4j
public class MyContainer1 {

    /**
     * 保存所有需要处理的类信息:包括类以及他们需要设置的属性
     */
    private static Map<Class, MetaNode> META_NODE_MAP = new HashMap<>();

    /**
     * 保存最终生成的实例
     */
    private static Map<Class, Object> BEAN_MAP = new HashMap<>();

    private static Map<Class, Object> NON_FIELD_BEAN_MAP = new HashMap<>();

    public static void registerClass(Class clazz, List<Field> fieldList) {
        if (!META_NODE_MAP.containsKey(clazz)) {
            MetaNode metaNode = new MetaNode();
            metaNode.setClazz(clazz);
            Map<String, Field> fieldMap = CollectionUtils.emptyIfNull(fieldList).stream()
                    .collect(Collectors.toMap(Field::getName, Function.identity()));
            metaNode.setFieldMap(fieldMap);
            META_NODE_MAP.putIfAbsent(clazz, metaNode);
        }
    }

    public static Object getBean(Class clazz) {
        Object o = BEAN_MAP.get(clazz);
        if (Objects.nonNull(o)) {
            return o;
        }
        return NON_FIELD_BEAN_MAP.get(clazz);
    }

    public static void main(String[] args) {
        try {
            System.out.println(BEAN_MAP.values());
            System.out.println("init==========");
            // 1. 注册需要处理的class
            registerClass();

            // 2. 遍历所有需要处理的class,然后进行 instance 和 inject
            for (Class clazz : MapUtils.emptyIfNull(META_NODE_MAP).keySet()) {
                if (BEAN_MAP.containsKey(clazz)) {
                    continue;
                }
                Object instance = complete(clazz);
                NON_FIELD_BEAN_MAP.entrySet().removeIf(it -> it.getKey().equals(clazz));
                BEAN_MAP.putIfAbsent(clazz, instance);
            }


            System.out.println(BEAN_MAP.values());
            System.out.println("done==========");
        } catch (Exception e) {
            log.error("Oops, there is a error", e);
        }

    }

    private static Object complete(Class clazz) throws Exception {
        MetaNode metaNode = META_NODE_MAP.get(clazz);
        if (Objects.isNull(metaNode)) {
            return null;
        }
        // 1. 实例化
        Object instance = instance(clazz);
        if (MapUtils.isNotEmpty(metaNode.getFieldMap())) {
            // 2. 属性设置
            injectField(instance, metaNode.getFieldMap());
        }
        // 3. aop
        instance = wrapIfNecessary(clazz, instance);
        return instance;
    }

    private static void registerClass() throws Exception{
        registerClass(CycleService1.class, Collections.singletonList(CycleService1.class.getDeclaredField("cycleService2")));
        registerClass(CycleService2.class, Collections.singletonList(CycleService2.class.getDeclaredField("cycleService1")));
    }

    private static Object instance(Class clazz) throws Exception{
        Object o = clazz.newInstance();
        NON_FIELD_BEAN_MAP.putIfAbsent(clazz, o);
        return o;
    }

    private static void injectField(Object instance, Map<String, Field> fieldMap) throws Exception {
        for (Map.Entry<String, Field> fieldEntry : MapUtils.emptyIfNull(fieldMap).entrySet()) {
            Field field = fieldEntry.getValue();
            Class<?> type = field.getType();
            Object toInject = getBean(type);
            if (Objects.isNull(toInject)) {
                toInject = complete(type);
                BEAN_MAP.putIfAbsent(type, toInject);
            }
            field.setAccessible(true);
            field.set(instance, toInject);
        }

    }

    private static Object wrapIfNecessary(Class clazz, Object object) {
        Class[] interfaces = clazz.getInterfaces();
        if (Objects.isNull(interfaces) || interfaces.length == 0) {
            return object;
        }

        Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("it is a proxy doing its job:" + method.getName());
                Object invoke = method.invoke(object, args);
                return invoke;
            }
        });

        return proxy;

    }

}

我们的改造分为3步:

1. instance里面在对对象进行了基本的raw bean生成之后,放入到NON_FIELD_BEAN_MAP
2. 增加一个getBean方法,先从BEAN_MAP中获取对象,再从NON_FIELD_BEAN_MAP中获取,所以在注入属性的时候调用该方法
3. 在最后阶段,从NON_FIELD_BEAN_MAP中remove掉对应的earlyObject,在BEAN_MAP中加入object

1.2 真的这么丝滑么

如果这么简单,那么spring为什么做得比我们复杂?
思考下面这个问题:
如果需要注入的对象是需要进行aop的呢?
我们目前貌似只实现了最基本的容器产生bean,以及这些bean之间的依赖注入,如果CycleService1和CycleService2都需要进行aop呢?上面的代码还能顺利运行么?
答案是 NO!!!

二、当注入属性是proxy

2.1 为什么?

1.2 我们提到了直接进行proxy是不行的,为什么呢?其实是因为我们的demo是基于jdk proxy来做的proxy,所以生成的proxy其实是raw class的接口instance了,我们在spring容器里的bean的class不再是 raw class了,所以注入的时候会报错。

2.2 第一种改造

2.2.1 循环依赖属性修改

为了不影响前面的demo,我们又设置了新的循环依赖类,属性注入的是接口类

public class CycleService3 implements MyInterface3{

    private MyInterface4 cycleService4;

    @Override
    public void doSomething() {
        System.out.println("this is CycleService3:" + this.getClass() +"; my cycleService4 is:" + cycleService4.getClass());
    }
}

public class CycleService4 implements MyInterface4{

    private MyInterface3 cycleService3;

    @Override
    public void doSomething() {
        System.out.println("this is CycleService4:" + this.getClass() +"; my cycleService3 is:" + cycleService3.getClass());
    }
}

2.2.2 class元信息修改

因为涉及到proxy,而且这个proxy还得被其他类作为属性注入,所以我们的类元信息需要显式表现出类的接口。第一版demo之所以没有,也是因为不需要被依赖注入。

public class MetaNode {

    private Class clazz;

    private Map<String, Field> fieldMap;

	// 类实现了的接口类
    private List<Class> interfaces;
}

2.2.3 所以proxy的发生时机

因为属性注入必须注入的是proxy bean,那么在属性注入的时候就要获取到依赖的类的proxy类。
具体实现如下:

public class MyContainer3 {

    /**
     * 保存所有需要处理的类信息:包括类以及他们需要设置的属性
     */
    private static Map<Class, MetaNode> META_NODE_MAP = new HashMap<>();

    /**
     * 保存最终生成的实例
     */
    private static Map<Class, Object> BEAN_MAP = new HashMap<>();

    private static Map<Class, Object> NON_FIELD_BEAN_MAP = new HashMap<>();

    private static Set<Class> CACHE_PROXYED = new HashSet<>();

    public static void registerClass(Class clazz, List<Field> fieldList, List<Class> interfaces) {
        if (!META_NODE_MAP.containsKey(clazz)) {
            MetaNode metaNode = new MetaNode();
            metaNode.setClazz(clazz);
            Map<String, Field> fieldMap = CollectionUtils.emptyIfNull(fieldList).stream()
                    .collect(Collectors.toMap(Field::getName, Function.identity()));
            metaNode.setFieldMap(fieldMap);
            metaNode.setInterfaces(ListUtils.emptyIfNull(interfaces));
            META_NODE_MAP.putIfAbsent(clazz, metaNode);
        }
    }

    public static Object getBean(Class clazz) {
        Object o = BEAN_MAP.get(clazz);
        if (Objects.nonNull(o)) {
            return o;
        }
        return NON_FIELD_BEAN_MAP.get(clazz);
    }

    public static void main(String[] args) {
        try {
            System.out.println(BEAN_MAP.values());
            System.out.println("init==========");
            // 1. 注册需要处理的class
            registerClass();

            // 2. 遍历所有需要处理的class,然后进行 instance 和 inject
            for (Map.Entry<Class, MetaNode> entry: MapUtils.emptyIfNull(META_NODE_MAP).entrySet()) {
                if (BEAN_MAP.containsKey(entry.getKey())) {
                    continue;
                }
                Object instance = complete(entry.getKey());
                addToContext(entry.getKey(), instance);
            }


            System.out.println(BEAN_MAP.values());
            System.out.println("done==========");
        } catch (Exception e) {
            log.error("Oops, there is a error", e);
        }

    }

    private static void addToContext(Class clazz, Object instance) {
        Set<Class> clazzSet = new HashSet<>();
        clazzSet.add(clazz);
        if (!clazz.isInterface()) {
            clazzSet.addAll(Arrays.asList(clazz.getInterfaces()));
        }
        NON_FIELD_BEAN_MAP.entrySet().removeIf(it -> clazzSet.contains(it.getKey()));
        BEAN_MAP.putIfAbsent(clazz, instance);
    }

    private static Object complete(Class clazz) throws Exception {
        MetaNode metaNode = META_NODE_MAP.get(clazz);
        if (Objects.isNull(metaNode)) {
            return null;
        }

        // 如果容器里已经有了对应的数据,那么就返回
        if (getBean(metaNode.getClazz()) != null) {
            return getBean(metaNode.getClazz());
        }

        // 1. 实例化
        Object instance = instance(metaNode.getClazz());
        if (MapUtils.isNotEmpty(metaNode.getFieldMap())) {
            // 2. 属性设置
            injectField(instance, metaNode.getFieldMap());
        }

        // 3. aop
        Object proxyed = wrapIfNecessary(metaNode.getClazz(), instance);

        if (instance == proxyed) {
            if (!BEAN_MAP.containsKey(metaNode.getClazz())) {
                throw new Exception(String.format("clazz %s have been aop proxyed more than once", clazz));
            } else {
                return BEAN_MAP.get(metaNode.getClazz());
            }
        }
        return proxyed;
    }

    private static void registerClass() throws Exception{
        registerClass(CycleService3.class, Collections.singletonList(CycleService3.class.getDeclaredField("cycleService4")), Collections.singletonList(MyInterface3.class));
        registerClass(CycleService4.class, Collections.singletonList(CycleService4.class.getDeclaredField("cycleService3")), Collections.singletonList(MyInterface4.class));
    }

    private static Object instance(Class clazz) throws Exception{
        Object o = clazz.newInstance();
        NON_FIELD_BEAN_MAP.putIfAbsent(clazz, o);
        return o;
    }

    private static void injectField(Object instance, Map<String, Field> fieldMap) throws Exception {
        for (Map.Entry<String, Field> fieldEntry : MapUtils.emptyIfNull(fieldMap).entrySet()) {
            Field field = fieldEntry.getValue();
            Class<?> type = field.getType();
            type = findRealInjectClass(type);
            Object toInject = complete(type);
            field.setAccessible(true);
            /**
             * 此处正是使用三级工厂的原因,如果只使用二级工厂
             * 那么此时譬如C1和C2循环依赖,当C2注入了一个proxy的C1之后,没有保存到某个地方
             * 再回到C1的complete过程中,此时会导致最后容器里的proxy-C1和C2注入的proxy-C1不是同一个对象
             * 我此处是让C2注入的proxy-C1直接覆盖了一级工厂(BEAN_MAP)里的对象,
             * 然后在回到C1的complete过程中,直接判断有没有一级工厂里有没有C1,如果有就直接返回,也就不会进行proxy的过程了
             */
            Object o = wrapIfNecessary(type, toInject);
            BEAN_MAP.putIfAbsent(type, o);
            field.set(instance, o);
        }
    }

    public static Class findRealInjectClass(Class clazz) throws Exception {
        if (!clazz.isInterface()) {
            return clazz;
        }
        // 如果是接口,找到其实现类
        List<Class> satisfiedClazzList = new LinkedList<>();
        for (Map.Entry<Class, MetaNode> entry : META_NODE_MAP.entrySet()) {
            if (entry.getValue().getInterfaces().contains(clazz)) {
                satisfiedClazzList.add(entry.getKey());
            }
        }
        // 同一个接口,不允许多个实现类,如果存在多个,spring的处理方式是判断有没有@Primary注解
        if (CollectionUtils.size(satisfiedClazzList) != 1) {
            throw new Exception("can not find satisfiedClazz for class:" + clazz.getSimpleName());
        }

        return satisfiedClazzList.get(0);

    }

    private static Object wrapIfNecessary(Class clazz, Object object) {
        if (CACHE_PROXYED.contains(clazz)) {
            return object;
        }
        Class[] interfaces = clazz.getInterfaces();
        if (Objects.isNull(interfaces) || interfaces.length == 0) {
            return object;
        }

        Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("it is a proxy doing its job:" + method.getName());
                Object invoke = method.invoke(object, args);
                return invoke;
            }
        });

        CACHE_PROXYED.add(clazz);
        return proxy;

    }
}

大家可以发现逻辑上面多了几个功能:

  1. findRealInjectClass,这个方法是当需要注入的属性传入的class是接口的时候,我们需要查找到其真正的实现类,否则无法进行实例化
  2. 我们在主体流程第三步的wrapIfNecessary之后进行了一个proxy和instance的比较。此处比较的意义呢,就是防止同一个instance被spring进行了多次proxy,对于默认单例的spring容器这是不可接受的
  3. 有没有发现,我们只使用了两个缓存工厂便简单实现了循环依赖??其实是因为我们在某个类因为被其他类依赖注入并proxy之后,直接放入了spring的最终容器BEAN_MAP中,而没有放到一个新的二级缓存工厂中。spring使用二级缓存工厂的出发点应该是因为觉得只有生命周期完成的bean才有资格放入到最终容器中

2.3 如果先proxy再inject呢?

刚刚2.2提到我们在某个类因为被其他类依赖注入并proxy之后,直接放入了spring的最终容器BEAN_MAP中,因此猜想出spring容器使用到二级缓存工厂的原因。
也许有人问了,那如果我们把主体流程改为:1. instance; 2. proxy; 3. inject。同时在instance之后不往一级缓存工厂里put,只有proxy之后才put,是不是也能满足只有生命周期完成的bean才会放到最终容器中?

public class MyContainer5 {

    /**
     * 保存所有需要处理的类信息:包括类以及他们需要设置的属性
     */
    private static Map<Class, MetaNode> META_NODE_MAP = new HashMap<>();

    /**
     * 保存最终生成的实例
     */
    private static Map<Class, Object> BEAN_MAP = new HashMap<>();

    private static Map<Class, Object> EARLY_BEAN_MAP = new HashMap<>();

    private static Set<Class> CACHE_PROXYED = new HashSet<>();

    public static void registerClass(Class clazz, List<Field> fieldList, List<Class> interfaces) {
        if (!META_NODE_MAP.containsKey(clazz)) {
            MetaNode metaNode = new MetaNode();
            metaNode.setClazz(clazz);
            Map<String, Field> fieldMap = CollectionUtils.emptyIfNull(fieldList).stream()
                    .collect(Collectors.toMap(Field::getName, Function.identity()));
            metaNode.setFieldMap(fieldMap);
            metaNode.setInterfaces(ListUtils.emptyIfNull(interfaces));
            META_NODE_MAP.putIfAbsent(clazz, metaNode);
        }
    }

    public static Object getBean(Class clazz) {
        Object o = BEAN_MAP.get(clazz);
        if (Objects.nonNull(o)) {
            return o;
        }
        o = EARLY_BEAN_MAP.get(clazz);
        return o;
    }

    public static void main(String[] args) {
        try {
            System.out.println(BEAN_MAP.values());
            System.out.println("init==========");
            // 1. 注册需要处理的class
            registerClass();

            // 2. 遍历所有需要处理的class,然后进行 instance 和 inject
            for (Map.Entry<Class, MetaNode> entry: MapUtils.emptyIfNull(META_NODE_MAP).entrySet()) {
                if (BEAN_MAP.containsKey(entry.getKey())) {
                    continue;
                }
                Pair<Class, Object> instancePair = complete(entry.getKey());
                addToContext(instancePair.getKey(), instancePair.getValue());
            }

            System.out.println(BEAN_MAP.values());
            System.out.println("done==========");
        } catch (Exception e) {
            log.error("Oops, there is a error", e);
        }

    }

    private static void addToContext(Class clazz, Object instance) {
        Set<Class> clazzSet = new HashSet<>();
        clazzSet.add(clazz);
        if (!clazz.isInterface()) {
            clazzSet.addAll(Arrays.asList(clazz.getInterfaces()));
        }
        EARLY_BEAN_MAP.entrySet().removeIf(it -> it.getKey().equals(clazz));
        BEAN_MAP.putIfAbsent(clazz, instance);
    }

    private static Pair<Class, Object> complete(Class clazz) throws Exception {
        clazz = findRealInjectClass(clazz);
        MetaNode metaNode = META_NODE_MAP.get(clazz);
        if (Objects.isNull(metaNode)) {
            return null;
        }

        // 如果容器里已经有了对应的数据,那么就返回
        if (getBean(metaNode.getClazz()) != null) {
            return new Pair(clazz, getBean(metaNode.getClazz()));
        }

        // 1. 实例化
        Object instance = instance(metaNode.getClazz());
        // 2. aop
        Object proxyed = wrapIfNecessary(metaNode.getClazz(), instance);

        EARLY_BEAN_MAP.put(clazz, proxyed);

        if (MapUtils.isNotEmpty(metaNode.getFieldMap())) {
            // 3. 属性设置
            injectField(instance, metaNode.getFieldMap());
        }

        return new Pair<>(clazz, proxyed);
    }

    private static void registerClass() throws Exception{
        registerClass(CycleService3.class, Collections.singletonList(CycleService3.class.getDeclaredField("cycleService4")), Collections.singletonList(MyInterface3.class));
        registerClass(CycleService4.class, Collections.singletonList(CycleService4.class.getDeclaredField("cycleService3")), Collections.singletonList(MyInterface4.class));
    }

    private static Object instance(Class clazz) throws Exception{
        Object o = clazz.newInstance();
        return o;
    }

    private static void injectField(Object instance, Map<String, Field> fieldMap) throws Exception {
        for (Map.Entry<String, Field> fieldEntry : MapUtils.emptyIfNull(fieldMap).entrySet()) {
            Field field = fieldEntry.getValue();
            Class<?> type = field.getType();
            Pair<Class, Object> toInjectData = complete(type);
            field.setAccessible(true);
            // 此处的写法有点ugly,因为 findRealInjectClass 会调用多次
            BEAN_MAP.put(toInjectData.getKey(), toInjectData.getValue());
            field.set(instance, toInjectData.getValue());
        }
    }

    public static Class findRealInjectClass(Class clazz) throws Exception {
        if (!clazz.isInterface()) {
            return clazz;
        }
        // 如果是接口,找到其实现类
        List<Class> satisfiedClazzList = new LinkedList<>();
        for (Map.Entry<Class, MetaNode> entry : META_NODE_MAP.entrySet()) {
            if (entry.getValue().getInterfaces().contains(clazz)) {
                satisfiedClazzList.add(entry.getKey());
            }
        }
        if (CollectionUtils.size(satisfiedClazzList) != 1) {
            throw new Exception("can not find satisfiedClazz for class:" + clazz.getSimpleName());
        }

        return satisfiedClazzList.get(0);

    }

    private static Object wrapIfNecessary(Class clazz, Object object) {
        if (CACHE_PROXYED.contains(clazz.getName())) {
            return object;
        }
        Class[] interfaces = clazz.getInterfaces();
        if (Objects.isNull(interfaces) || interfaces.length == 0) {
            return object;
        }

        Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("it is a proxy doing its job:" + method.getName());
                Object invoke = method.invoke(object, args);
                return invoke;
            }
        });

        CACHE_PROXYED.add(clazz);
        return proxy;

    }
}

经过实践,确实可行。那我们不免需要发出疑问:为什么spring不是这么干的?
笔者猜测两个原因:

  1. spring最初版本和我们的第一版思路类似,定下了先inject再proxy的基调,所以后期即使迭代也不修改主体流程,毕竟spring框架不像咱们的demo这么简单,可能一处简单的修改需要大动筋骨
  2. 其实spring的aop功能是可插拔的,IOC和依赖注入才是其主体功能,毕竟spring有个注解@EnableAspectJAutoProxy,所以可能是不想把proxy的步骤放入到其主体流程中。

总结

不过还是建议读者可以在了解了原理之后进行手动实现,毕竟这样,才会对spring的一些实现及原理有更多思考。
talk is cheap,show me the code
感觉这篇博客最大的作用是解释了spring的循环依赖能不能用两个缓存工厂实现。如果使用两个缓存工厂,共有两个方案,方案如下:

  1. 提前把还没有进行inject,但是已经proxy了的bean放入到spring的最终容器中
  2. spring的主体流程里面先proxy再inject

至于为什么不用上述这两种方案,笔者也写了一些猜想和思考。欢迎讨论~

示例代码

代码地址:我的GitHub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值