Spring循环依赖问题

先看面试中的连环炮:

  • 1.什么是循环依赖?
  • 2.如何检测是否存在循环依赖?
  • 3.如何解决循环依赖?
  • 4.多例的情况下,循环依赖问题为什么无法解决?
  • 5.单例的情况下,虽然可以解决循环依赖,但会不会存在其他问题?
  • 6.为什么采用三级缓存解决循环依赖?如果直接将早期bean丢到二级缓存可以么?

什么是循环依赖?

这个很好理解,多个bean之间相互依赖,形成一个闭环。

public class A{
  B b;
}
public class B{
  C c;
}
public class C{
  A a;
}

如何检测是否存在循环依赖?

检测循环依赖比较简单,使用一个列表来记录正在创建中的bean,bean创建之前,先去记录中看一下自己是否已经在列表中了,如果在,说明存在循环依赖,如果不在,则将其加入到这个列表,bean创建完毕之后,将其再从这个列表中移除。
源码方面Spring创建单例bean的时候,会调用下面方法,在AbstractBeanFactory中:

protected void beforeSingletonCreation(String beanName){
  if( !this.inCreationCheckExclusions.contains(beanName) &&
       !this.singletonsCurrentlyIncreation.add(beanName)){
    throw new BeanCurrentlyIncreationException(beanName);
  }
}

singletonsCurentlyIncreation就是用来记录目前正在创建中的bean名称列表Set<String>,this.singletonsCurrentlyInCreation.add(beanName)返回false,说明beanName已经在列表中了,此时会抛循环依赖的异常BeanCurrentlyIncreationException
对应的源码:

public BeanCurrentlyInCreationException(String beanName){
   super(beanName, "Requested bean is currently in creation: Is there an unresolvable circular reference?");
}

上面是单例bean检车循环依赖的源码,接下来看看非单例的情况:
propotype情况为例,源码位于org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法中:

//检查正在创建的bean列表(ThreadLocal)中是否存在beanName,如果存在,说明存在循环依赖,抛出循环依赖异常
if( isPropertypeCurrentlyCreation(beanName) ){
  throw new BeanCurrentlyInCreationException(beanName);
}

//判断scope是不是propertype
if( mbd.isPropertype() ){
  Object propertypeInstance = null;
  try {
    //将beanName方法正在创建的列表中
    beforePropertypeCreation(beanName);
    propertypeInstance = createBean(beanName, mbd, args);
  }finally{
    //将beanName从正在创建的列表中移除
  }
}

Spring如何解决循环依赖的问题

Spring创建bean主要的几个步骤:
1.实例化bean,即调用构造器创建bean实例
2.填充属性,注入依赖的bean,比如通过set方式,@Autowired注解方式等
3.bean初始化,比如调用init方法等
从上面可以看出,注入依赖的对象,有两种情况:
1.构造器的方式注入依赖
2.填充属性注入,set和注解
先来看用构造器方式注入依赖的bean,下面两个bean循环依赖:

@Component
public class ServiceA{
  private ServiceB serviceB;
  public ServiceA(ServiceB serviceB){
   this.serviceB = serviceB;
  }
}

@Component
public class ServiceB {
    private ServiceA serviceA;
    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

构造器的情况比较容易理解,实例化ServiceA的时候,需要有serviceB,而实例化ServiceB的时候需要有serviceA,构造器循环依赖是无法解决的,大家可以尝试一下使用编码的方式创建上面2个对象,是无法创建成功的!

再来看看非构造器的方式注入相互依赖的bean,以set方式注入为例,下面是2个单例的bean:serviceA和serviceB:

@Component
public class ServiceA {
    private ServiceB serviceB;
    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

@Component
public class ServiceB {
    private ServiceA serviceA;
    @Autowired
    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

如果我们采用硬编码的方式创建上面2个对象,过程如下:

//创建serviceA
ServiceA serviceA = new ServiceA();
//创建serviceB
ServiceB serviceB = new ServiceB();
//将serviceA注入到serviceB中
serviceB.setServiceA(serviceA);
//将serviceB注入到serviceA中
serviceA.setServiceB(serviceB);

由于单例bean在spring容器中只存在一个,所以spring容器中肯定是有一个缓存来存放所有已创建好的单例bean;获取单例bean之前,可以先去缓存中找,找到了直接返回,找不到的情况下再去创建,创建完毕之后再将其丢到缓存中。可以使用一个map来存储单例bean,比如下面这个:

Map<String, Object> singletonObjects = new ConcurrentHashMap(256);

下面继续来看一下spring中set方法创建上面2个bean的过程:

1.spring轮询准备创建2个bean:serviceA和serviceB
2.spring容器发现singletonObjects中没有serviceA
3.调用serviceA的构造器创建serviceA实例
4.serviceA准备注入依赖的对象,发现需要通过setServiceB注入serviceB
5.serviceA向spring容器查找serviceB
6.spring容器发现singletonObjects中没有serviceB
7.调用serviceB的构造器创建serviceB实例
8.serviceB准备注入依赖的对象,发现需要通过setServiceA注入serviceA
9.serviceB向spring容器查找serviceA
10.此时又进入步骤2了

上面就形成了死循环,怎么才能终结呢?
可以在第3步后加一个操作:将实例化好的serviceA丢到singletonObjects中,此时问题就解决了。
Spring中也采用类似的方式,稍微有点区别,上面使用了一个缓存,而spring内部采用了3级缓存来解决这个问题,我们一起来细看一下:

/** 第一级缓存:单例bean的缓存 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 第二级缓存:早期暴露的bean的缓存 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** 第三级缓存:单例bean工厂的缓存 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

看一下Spring中的具体过程:开始的时候,获取serviceA,调用下面的代码:

//org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
        //1.查看缓存中是否已经有这个bean了
        Object sharedInstance = this.getSingleton(beanName);
        if (sharedInstance != null && args == null) {
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }else{
                //若缓存不存在,准备创建这个bean
                if( mbd.isSingleton() ){
                        //2.进入创建bean的创建过程
                        sharedInstance = getSingleton(beanName, ()->{
                                try {
                                      return createBean(beanName, mbd, args);
                                }
                                catch (BeansException ex) {
                                        throw ex;
                                }
                        });
                        bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }
        }
        return (T) bean;
}
  • 1:查看缓存中是否已经有了这个bean了:
public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

进入getSingleton方法,会尝试从3级缓存中查找bean,注意下面第二个参数,为ture的时候,才会从第3级中查找,否则只会查找1、2级缓存:

//allowEarlyReference:是否允许从三级缓存singletonFactories中通过getObject拿到bean
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //1.先从一级缓存中找
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            //2.从二级缓存中找
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                //3.二级缓存中没找到 && allowEarlyReference为true的情况下,从三级缓存中找
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    //三级缓存返回的是一个工厂,通过工厂来获取创建bean
                    singletonObject = singletonFactory.getObject();
                    //将创建好的bean丢到二级缓存中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    //从三级缓存移除
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

刚开始,3个缓存中肯定是找不到的,会返回null,接着会执行下面代码准备创建serviceA

if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> { //@1
        try {
            return createBean(beanName, mbd, args);
        }
        catch (BeansException ex) {
            destroySingleton(beanName);
            throw ex;
        }
    });
}

进入@1getSingleton方法,精简代码如下:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            //单例bean创建之前调用,将其加入正在创建的列表中,上面有提到过,主要用来检测循环依赖用的
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;

            try {
                //调用工厂创建bean
                singletonObject = singletonFactory.getObject();//@1
                newSingleton = true;
            }
            finally {
                 //单例bean创建之前调用,主要是将其从正在创建的列表中移除
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                //将创建好的单例bean放入缓存中
                addSingleton(beanName, singletonObject);//@2
            }
        }
        return singletonObject;
    }
}

上面@1和@2是关键代码,先来看一下@1,这个是一个ObjectFactory类型的,从外面传入的,如下:

if (mbd.isSingleton()) {
      sharedInstance = this.getSingleton(beanName, () -> {
            try {
                    return this.createBean(beanName, mbd, args);
            } catch (BeansException var5) {
                    this.destroySingleton(beanName);
                    throw var5;
            }
        });
        bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} 

createBean最终会调用下面的方法:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
beanInstance = this.doCreateBean(beanName, mbdToUse, args);

代码如下:

BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
    //通过反射调用构造器实例化serviceA
    instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//变量bean:表示刚刚同构造器创建好的bean示例
final Object bean = instanceWrapper.getWrappedInstance();

//判断是否需要暴露早期的bean,条件为(是否是单例bean && 当前容器允许循环依赖 && bean名称存在于正在创建的bean名称清单中)
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                  isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    //若earlySingletonExposure为true,通过下面代码将早期的bean暴露出去
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));//@1
}

这里需要理解一下什么是早期bean:刚刚实例化好的bean就是早期的bean,此时bean还未进行属性天从,初始化等操作
@1:通过addSingletonFactory用于将早期的bean暴露出去,主要是丢到第三级缓存中:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        //第1级缓存中不存在bean
        if (!this.singletonObjects.containsKey(beanName)) {
            //将其丢到第3级缓存中
            this.singletonFactories.put(beanName, singletonFactory);
            //后面的2行代码不用关注
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

执行完上面的方法后,serviceA酒杯都到第三级缓存中了。

后续的过程serviceA开始注入依赖的对象,发现需要注入serviceB,会从容器中获取serviceB,而serviceB的获取又会走上面同样的过程实例化serviceB,然后将serviceB提前暴露出去,然后serviceB开始注入依赖的对象,serviceB发现自己需要注入serviceA,此时去容器中找serviceA,找serviceA会先去缓存中找,会执行
getSingleton("serviceA", true)``,代码如下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //1.先从一级缓存中找
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            //2.从二级缓存中找
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                //3.二级缓存中没找到 && allowEarlyReference为true的情况下,从三级缓存中找
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    //三级缓存返回的是一个工厂,通过工厂来获取创建bean
                    singletonObject = singletonFactory.getObject();
                    //将创建好的bean丢到二级缓存中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    //从三级缓存移除
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

上面的方法走完之后,serviceA会被放入二级缓存earlySingletonObjects中,会将serviceA返回,此时serviceB中的serviceA注入成功,serviceB继续完成创建,然后将自己返回给serviceA,此时serviceA通过set方法将serviceB注入。
serviceA创建完毕之后,会调用addSingleton方法将其加入到缓存中,这块代码如下:

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        //将bean放入第1级缓存中
        this.singletonObjects.put(beanName, singletonObject);
        //将其从第3级缓存中移除
        this.singletonFactories.remove(beanName);
        //将其从第2级缓存中移除
        this.earlySingletonObjects.remove(beanName);
    }
}

到此,serviceA和serviceB之间的循环依赖注入就完成了。

总结:

1.从容器中获取serviceA
2.容器尝试从3个缓存中找serviceA,找不到
3.准备创建serviceA
4.调用serviceA的构造器创建serviceA,得到serviceA实例,此时serviceA还未填充属性,未进行其他任何初始化的操作
5.将早期的serviceA暴露出去:即将其丢到第3级缓存singletonFactories中
6.serviceA准备填充属性,发现需要注入serviceB,然后向容器获取serviceB
7.容器尝试从3个缓存中找serviceB,找不到
8.准备创建serviceB
9.调用serviceB的构造器创建serviceB,得到serviceB实例,此时serviceB还未填充属性,未进行其他任何初始化的操作
10.将早期的serviceB暴露出去:即将其丢到第3级缓存singletonFactories中
11.serviceB准备填充属性,发现需要注入serviceA,然后向容器获取serviceA
12.容器尝试从3个缓存中找serviceA,发现此时serviceA位于第3级缓存中,经过处理之后,serviceA会从第3级缓存中移除,然后会存到第2级缓存中,同时将其返回给serviceB,此时serviceA通过serviceB中的setServiceA方法被注入到serviceB中。
13.serviceB继续执行后续的一些操作,最后完成创建工作,然后会调用addSingleton方法,将自己丢到第1级缓存中,并将自己从第2和第3级缓存中移除
14.serviceB将自己返回给serviceA
15.serviceA通过setServiceB方法将serviceB注入进去
16.serviceA继续执行后续的一些操作,最后完成创建工作,然后会调用addSingleton方法,将自己丢到第1级缓存中,并将自己从第2和第3级缓存中移除

循环依赖无法解决的情况

只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中。
那下面几种情况就需要注意:

情况1

条件
serviceA:多例
serviceB:多例
结果
此时不管是任何方式都是无法解决循环依赖的问题,最终都会报错,因为每次去获取依赖的bean都会重新创建。

情况2

条件
serviceA:单例
serviceB:多例
结果

  • 若使用构造器的方式相互注入,是无法完成注入操作的,会报错。
  • 若采用set方式注入,所有bean都还未创建的情况下,若去容器中获取serviceB,会报错,为什么?我们来看一下过程:
1.从容器中获取serviceB
2.serviceB由于是多例的,所以缓存中肯定是没有的
3.检查serviceB是在正在创建的bean名称列表中,没有
4.准备创建serviceB
5.将serviceB放入正在创建的bean名称列表中
6.实例化serviceB(由于serviceB是多例的,所以不会提前暴露,必须是单例的才会暴露)
7.准备填充serviceB属性,发现需要注入serviceA
8.从容器中查找serviceA
9.尝试从3级缓存中找serviceA,找不到
10.准备创建serviceA
11.将serviceA放入正在创建的bean名称列表中
12.实例化serviceA
13.由于serviceA是单例的,将早期serviceA暴露出去,丢到第3级缓存中
14.准备填充serviceA的属性,发现需要注入serviceB
15.从容器中获取serviceB
16.先从缓存中找serviceB,找不到
17.检查serviceB是在正在创建的bean名称列表中,发现已经存在了,抛出循环依赖的异常

如果此处不是去获取serviceB,而是先去获取serviceA呢,会不会报错?

探讨:为什么需要三级缓存?

问题:如果只是用2级缓存,直接将港式梨花好的bean暴露出去是否可以?

答:不行,早期暴露给其他依赖者的bean和最终暴露的bean不一致的问题。若将刚刚实例化好的bean直接丢到二级缓存中暴露出去,如果后期这个bean对象被更改了,比如可能在上面加了一些拦截器,将其包装为一个代理了,那么暴露出去的bean和最终的这个bean就不一样的,将自己暴露出去的时候是一个原始对象,而自己最终却是一个代理对象,最终会导致被暴露出去的和最终的bean不是同一个bean的,将产生意向不到的效果,而三级缓存就可以发现这个问题,会报错。

案例演示:

下面来2个bean,相互依赖,通过set方法相互注入,并且其内部都有一个m1方法,用来输出一行日志。
Service1

@Component
public class Service1 {
    public void m1() {
        System.out.println("Service1 m1");
    }
    private Service2 service2;
    @Autowired
    public void setService2(Service2 service2) {
        this.service2 = service2;
    }
}

Service2

@Component
public class Service2 {
    public void m1() {
        System.out.println("Service2 m1");
        this.service1.m1();//@1
    }
    private Service1 service1;
    @Autowired
    public void setService1(Service1 service1) {
        this.service1 = service1;
    }
    public Service1 getService1() {
        return service1;
    }
}

注意上面的@1,service2的m1方法中会调用service1的m1方法。
需求:
在service1上面加个拦截器,要求在调用service1的任何方法之前需要先输出一行日志:你好,service1
实现:
只能赠一个Bean的后置处理器来对service1对应的bean进行处理,将其封装成一个代理暴露出去:

@Component
public class MethodBeforeInterceptor implements BeanPostProcessor{
    @Nullable
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      if("serivice1".equals(beanName)){
        //代理创建工厂,需要传入被代理目标
        ProxyFactory proxyFactory = new ProxyFactory();
        //添加一个方法前置通知,会在方法执行之前调用通知中的before()方法
        proxyFactory.addAdvice(new MethodBeforeAdvice(){
          @Override
          public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
            System.out.println("你好,service1");
          }
        });
        //返回代理对象
        return proxyFactory.getProxy();
      }
      return bean;
    }
}

上面的postProcessAfterInitialization方法内部会在service1初始化之后调用,内部会对service1这个bean进行处理,返回一个代理对象,通过代理来访问service1的方法,访问service1中的任何方法之前,会先输出:你好,service1
配置类

@Component
public class MainConfig{
}

测试

@Test
public void test23(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig3.class);
    context.refresh();
}

运行,出错:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'service1': Bean with name 'service1' has been injected into other beans [service2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:624)

可以看出是·AbstractAutowireCapableBeanFactory.java:624出错:

if (earlySingletonExposure) {
    //@1
    Object earlySingletonReference = getSingleton(beanName, false);
    if (earlySingletonReference != null) {
        //@2
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
        }
        //@3
        else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
                if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                    actualDependentBeans.add(dependentBean);
                }
            }
            if (!actualDependentBeans.isEmpty()) {
                throw new BeanCurrentlyInCreationException(beanName,
                                                           "Bean with name '" + beanName + "' has been injected into other beans [" +
                                                           StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                                                           "] in its raw version as part of a circular reference, but has eventually been " +
                                                           "wrapped. This means that said other beans do not use the final version of the " +
                                                           "bean. This is often the result of over-eager type matching - consider using " +
                                                           "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
            }
        }
    }
}

上面代码主要用来判断当有循环依赖的情况下,早期暴露给别人使用的bean是否和最终的bean不一样的情况下,会抛出一个异常。
通过代码级别的来解释上面代码:
@1:调用getSingleton(beanName, false)方法,这个方法用来从3个级别的缓存中获取bean,但是注意了,这个地方第二个参数是false,此时只会尝试从第1级和第2级缓存中获取bean,如果能够获取到,说明了什么?说明了第2级缓存中已经有这个bean了,而什么情况下第2级缓存中会有bean?说明这个bean从第3级缓存中已经被别人获取过,然后从第3级缓存移到了第2级缓存中,说明这个早期的bean被别人通过getSingleton(beanName, true)获取过
@2:这个地方用来判断早期暴露的bean和最终spring容器对这个bean走完创建过程之后是否还是同一个bean,上面我们的service1被代理了,所以这个地方会返回false,此时会走到@3
@3:allowRawInjectionDespiteWrapping这个参数用来控制是否允许循环依赖的情况下,早期暴露给被人使用的bean在后期是否可以被包装,通俗点理解就是:是否允许早期给别人使用的bean和最终bean不一致的情况,这个值默认是false,表示不允许,也就是说你暴露给别人的bean和你最终的bean需要是一致的。
而上面代码注入到service2中的service1是早期的service1,而最终spring容器中的service1变成一个代理对象了,早期的和最终的不一致了,而allowRawInjectionDespiteWrapping又是false,所以报异常了。
那么如何解决这个问题:
很简单,将allowRawInjectionDespiteWrapping设置为true就可以了,下面改一下代码如下:

@Test
public void test4() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    //创建一个BeanFactoryPostProcessor:BeanFactory后置处理器
    context.addBeanFactoryPostProcessor(beanFactory -> {
        if (beanFactory instanceof DefaultListableBeanFactory) {
            //将allowRawInjectionDespiteWrapping设置为true
            ((DefaultListableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
        }
    });
    context.register(MainConfig3.class);
    context.refresh();
    System.out.println("容器初始化完毕");
}

上面将allowRawInjectionDespiteWrapping设置为true了,是通过一个BeanFactoryPostProcessor来实现的,在bean创建之前用来干预BeanFactory的创建过程,可以用来修改BeanFactory中的一些配置。
输出:容器初始化完毕
然后我们看看加在service1上的拦截器有没有起效:

//获取service1
Service1 service1 = context.getBean(Service1.class);
//获取service2
Service2 service2 = context.getBean(Service2.class);
System.out.println("----A-----");
service2.m1(); //@1
System.out.println("----B-----");
service1.m1(); //@2
System.out.println("----C-----");
System.out.println(service2.getService1() == service1);

查看输出:

容器初始化完毕
----A-----
Service2 m1
Service1 m1
----B-----
你好,service1
Service1 m1
----C-----
false

发现service2.m1方法中调用了service1.m1,拦截器没有起效,但是单独调用service1.m1方法,拦截器起效了,说明service2中注入的service1不是代理对象,而是早期的service1,注入时service1还不是一个代理对象。
再看看最后一行输出为false,说明service2中的service1确实和spring容器中的service1不是一个对象了。
既然最终service1是一个代理对象,那么你提前暴露出去的时候,注入到service2的时候,你也必须得是个代理对象啊,需要确保给别人和最终是同一个对象。
看暴露早期的bean的源码:
addSingletonFactory(beanName, ()->getEarlyBeanReference(beanName, mbd, bean));
注意看ferEarlyBeanReference方法:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

从3级缓存中获取bean的时候,会调用上面这个方法来获取bean,这个方法内部会看一下容器中是否有SmartInstantiationAwareBeanPostProcessor处理器,依次调用处理器中的getEarlyBeanReference方法。那么我们可以自己定义一个:
SmartInstantiationAwareBeanPostProcessor,然后在其getEarlyBeanReference中来创建代理,我们将MethodBeforeInterceptor代码改成这样:

@Component
public class MethodBeforeInterceptor implements SmartInstantiationAwareBeanPostProcessor {
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        if ("service1".equals(beanName)) {
            //代理创建工厂,需传入被代理的目标对象
            ProxyFactory proxyFactory = new ProxyFactory(bean);
            //添加一个方法前置通知,会在方法执行之前调用通知中的before方法
            proxyFactory.addAdvice(new MethodBeforeAdvice() {
                @Override
                public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
                    System.out.println("你好,service1");
                }
            });
            //返回代理对象
            return proxyFactory.getProxy();
        }
        return bean;
    }
}

测试,输出得要我们想要的结果。

单例bean解决循环依赖,还存在什么问题?

循环依赖的情况下,由于注入的是早期的bean,此时早期的bean中还未被填充属性,初始化等各种操作,也就是说此时bean并没有被完全初始化完毕,此时若直接拿去使用,可能存在有问题的风险。
(转自路人甲Java)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270