首页 > 其他分享 >解析Spring中的循环依赖问题:再探三级缓存(AOP)

解析Spring中的循环依赖问题:再探三级缓存(AOP)

时间:2024-02-28 09:12:24浏览次数:24  
标签:缓存 对象 Spring beanName 代理 bean AOP

前言

在之前的内容中,我们简要探讨了循环依赖,并指出仅通过引入二级缓存即可解决此问题。然而,你可能会好奇为何在Spring框架中还需要引入三级缓存singletonFactories。在前述总结中,我已经提供了答案,即AOP代理对象。接下来,我们将深入探讨这一话题。

AOP

在Spring框架中,AOP的实现是通过一个名为BeanPostProcessor的类完成的,其中一个关键的BeanPostProcessor就是AnnotationAwareAspectJAutoProxyCreator。值得一提的是,该类的父类是AbstractAutoProxyCreator。在Spring的AOP机制中,通常会使用JDK动态代理或者CGLib动态代理来实现代理对象的生成。因此,如果在某个类的方法上设置了切面,那么最终这个类将需要生成一个代理对象来应用AOP的功能。

一般的执行流程通常是这样的:A类--->生成一个普通对象-->属性注入-->基于切面生成一个代理对象-->将该代理对象存入singletonObjects单例池中。

而AOP可以说是Spring框架中除了IOC之外的另一个重要功能,而循环依赖则属于IOC的范畴。因此,为了让这两个重要功能同时存在于Spring框架中,Spring需要进行特殊处理。

三级缓存

在处理这种情况时,Spring框架利用了第三级缓存singletonFactories。下面我们来看一下关于三级缓存的源代码实现:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // Quick check for existing instance without full singleton lock
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                synchronized (this.singletonObjects) {
                    // Consistent creation of early reference within full singleton lock
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }
        return singletonObject;
    }

首先,singletonFactories中存储的是某个beanName对应的ObjectFactory。在bean的生命周期中,生成完原始对象之后,Spring框架会构造一个ObjectFactory并将其存入singletonFactories中。这个ObjectFactory是一个函数式接口,因此支持Lambda表达式,形式为() -> getEarlyBeanReference(beanName, mbd, bean)。为了更清晰地理解这个过程,我提供一张图片。

image

getEarlyBeanReference

在上述Lambda表达式中,它实际上代表了一个ObjectFactory,执行该Lambda表达式将会调用getEarlyBeanReference方法。下面是getEarlyBeanReference方法的实现:

//AbstractAutowireCapableBeanFactory
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

在整个Spring框架中,值得注意的是,只有AbstractAutoProxyCreator这个类在实现getEarlyBeanReference方法时才具有真正的意义。这个类专门用于处理AOP(面向切面编程)。

// AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
 Object cacheKey = getCacheKey(bean.getClass(), beanName);
 this.earlyProxyReferences.put(cacheKey, bean);
 return wrapIfNecessary(bean, beanName, cacheKey);
}

那么,getEarlyBeanReference方法的具体操作是什么呢?
首先,它会获取一个cachekey,这个cachekey实际上就是beanName。
接着,它会将beanName和bean(即原始对象)存储到earlyProxyReferences中。
接下来,它会调用wrapIfNecessary方法进行AOP操作,这将生成一个代理对象。

那么,什么时候会调用getEarlyBeanReference方法呢?让我们再次回到循环依赖的场景中。

image

在我上一节的基础上,我增加了两句话,以便更好地理解触发缓存机制以解决AOP代理对象生成的时机。

一旦原始对象通过构造方法生成后,会被存储到三级缓存中,并且会与一个lambda表达式关联。然而,在这个阶段,它并不会被执行。

一旦BBean需要ABean时,系统会首先查看三级缓存以确定是否存在缓存。如果存在缓存,则lambda表达式将会被执行,其代码已在前面展示过。该lambda表达式的目的是将代理对象放入earlySingletonObjects中。需要注意的是,此时代理对象并未被放入singletonObjects中。那么代理对象何时会被放入singletonObjects中呢?

这个时候你可能已经明白了earlySingletonObjects的用途。由于只获取了A原始对象的代理对象,这个代理对象并不完整,因为A原始对象尚未进行属性填充。因此,在这种情况下,我们不能直接将A的代理对象放入singletonObjects中。因此,我们只能将代理对象放入earlySingletonObjects,这样依次类推。

在Spring框架中,在循环依赖场景下,当Bean B创建完成后,Bean A继续其生命周期。在Bean A完成属性注入后,根据其自身逻辑进行AOP操作。此时,我们知道Bean A的原始对象已经经历了AOP处理,因此对于Bean A本身而言,不需要再次进行AOP。那么,如何确定一个对象是否已经经历了AOP呢?

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

没错,这个earlyProxyReferences确实提前缓存了对象是否已经被代理过,这样就避免了重复的AOP处理。

举例反证

那么问题来了,当A对象创建时,它可以是原始对象,但当B对象创建时,却成功创建了A的代理对象。然后再回头给A对象进行属性注入和初始化,这些操作似乎与代理对象无关?

这个问题涉及到了 Spring 中动态代理的实现。无论是使用cglib代理还是jdk动态代理生成的代理类,代理时都会将目标对象 target 保存在最终生成的代理 $proxy 中。你可以将代理对象看作是对原始对象地址的一层包装,最终仍然会回到原始对象上。因此,对原始bean的进一步完善实际上也就是对代理对象的完善。

还有一个需要注意的问题,当A创建时,由于earlyProxyReferences缓存的原因,并没有创建代理对象,因此此时A仍然保持为原始对象。我们知道,当bean创建完成后,它将被放入一级缓存中,但如果在此之后被其他对象引用,那不就会出现问题吗?别人引用的都是原始对象了,而不是代理对象,但是请不要着急,因为在实例化之后,有一行代码可以解决这个问题。

if (earlySingletonExposure) {
            Object earlySingletonReference = getSingleton(beanName, false);
            if (earlySingletonReference != null) {
                if (exposedObject == bean) {
                    exposedObject = earlySingletonReference;
                }
......省略代码

在实例化原始对象后,他会首先从三级缓存中检查是否存在缓存对象。这是因为在创建B对象时,已经将A的代理对象放入二级缓存。因此,取出的对象是代理对象。接着,当进行 exposedObject == bean 的比较时,发现它们不相同。因此,以代理对象为准并将其返回。最终,最外层存储的将是代理对象。

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

总结

在上一个章节中我们提到了今天要讨论三级缓存,让我们根据上面提到的三级缓存内容,做一个详尽的总结:

  • singletonObjects:缓存经过了完整生命周期的bean。

  • earlySingletonObjects缓存了未经过完整生命周期的bean。当某个bean出现循环依赖时,该bean会被提前放入earlySingletonObjects中。如果该bean需要经过AOP,那么代理对象将会被放入earlySingletonObjects;否则,原始对象将被放入其中。然而,无论是代理对象还是原始对象,它们的生命周期都尚未完全结束。因此视为未经过完整生命周期的bean。

  • singletonFactories缓存的是一个ObjectFactory,这个ObjectFactory实际上是一个Lambda表达式。在每个Bean的生成过程中,当原始对象实例化完成后,会提前基于原始对象生成一个Lambda表达式,并将其保存到三级缓存中。这个Lambda表达式可能会被使用,也可能不会。如果当前Bean不存在循环依赖,那么这个Lambda表达式将不会被使用,当前的Bean将按照正常的生命周期执行完毕,并将自身放入singletonObjects中。但是,如果在依赖注入的过程中发现了循环依赖(即当前正在创建的Bean被其他Bean所依赖),则会从三级缓存中取出Lambda表达式,并执行它以获取一个对象,然后将得到的对象放入二级缓存中。需要特别注意的是,如果当前Bean需要AOP处理,则执行Lambda表达式后得到的将是代理对象;否则,直接得到的是原始对象。

当涉及Spring框架中动态代理的实现机制时,除了已经提到的earlySingletonObjects和singletonFactories这两个缓存外,还有一个重要的缓存值得一提,那就是earlyProxyReferences。这个缓存的作用在于记录某个原始对象是否已经进行过AOP(面向切面编程)处理。

至此,整个循环依赖解决完毕。

标签:缓存,对象,Spring,beanName,代理,bean,AOP
From: https://www.cnblogs.com/guoxiaoyu/p/18038690

相关文章

  • SpringMVC系列之(十二)拦截器
    拦截器过滤器拦截器拦截器链1.过滤器与拦截器的比较过滤器是servlet规范中的一部分,任何Javaweb工程都可使用;拦截器是SpringMVC自己的,只能在SpringMVC工程中使用。过滤器可以对所有要访问的资源拦截;拦截器只能拦截访问的Controller方法,如果访问的是jsp、html、css、imag......
  • SpringMVC系列之(十一)异常处理
    异常处理1.未进行异常处理的异常传递流程2.SpringMVC异常处理流程3.SpringMVC异常处理开发步骤编写自定义异常类(做提示信息的)编写异常处理器配置异常处理器(跳转到错误提示页面)4.实现Controller中的方法packagecn.itcast.controller;importcn.itcast.domain.U......
  • SpringMVC系列之(十)文件上传
    文件上传1.文件上传的前提form表单的ectype属性(表单请求正文的类型)取值必须是multipart/form-data,默认值是application/x-www-form-urlencodedmethod属性值为post提供文件选择域<inputtype="file"/>2.文件上传的原理ectype=application/x-www-form-urlencoded(默认值)表......
  • SpringMVC系列之(九)响应数据和结果视图
    响应数据和结果视图1.返回值分类这里的返回值指的是Controller中的方法的返回值。1.1String该字符串为逻辑视图名,通过视图解析器解析为物理视图地址。注:底层调用的是ModelAndView1.1.1实际开发中的应用实体类index.jsp<%--CreatedbyIntelliJIDEA.User:商务......
  • SpringMVC系列之(八)HiddentHttpMethodFilter过滤器
    HiddentHttpMethodFilter过滤器1.应用场景由于浏览器自身只支持发送get/post请求,其他请求方式并不支持,该过滤器用于模拟发送各种请求方式的请求(get/post/put/delete)Spring3.0提供2.浏览器发送PUT/DELETE请求的其他实现方式插件WebClient提供的静态方法HiddentHttpMetho......
  • SpringMVC系列之(六)自定义类型转换器
    自定义类型转换器SpringMVC在进行请求参数绑定时,自动进行了类型转换,前端传来的参数都是字符串,而控制器中的方法在接收时,可能会用到其他数据类型(如:Date、Integer、Double等),以日期类型为例,前端传的日期格式多样,SpringMVC自动转换类型的格式(2011/11/11)支持不了这么多类型,故有的日期......
  • SpringMVC系列之(五)POST请求中文乱码
    POST请求中文乱码1.配置解决中文乱码的过滤器web.xml中增加如下代码<filter><filter-name>characterEncodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><para......
  • SpringMVC系列之(四)请求参数的绑定
    请求参数的绑定获取前端传过来的参数的过程,如:Servlet的getParameter方法SpringMVC中的请求参数绑定:通过反射实现,SpringMVC中的请求参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的。1.支持的数据类型基本数据类型和字符串实体类型(JavaBean)集合数据类......
  • SpringMVC系列之(三)常用注解
    常用注解1.RequestMappingRequestMapping可以放在类上和方法上,放在类上表示一级目录,或表示某一个具体的模块属性path和value属性的作用相同method决定方法的请求方式params:请求必须包含的参数headers:请求必须包含的请求头以上的属性出现多个,需要同时满足2.RequestPa......
  • SpringMVC系列之(一)入门程序
    Web层,表现层入门程序1.需求描述页面上设置一个链接,点击链接后跳转到成功页面。2.搭建环境从骨架创建项目解决创建项目过慢的问题项目创建后,完善目录结构,创建相应的目录并设置目录类型源代码目录(java)和资源目录(resources)注:我这里已经设置过了,所以就不......