《手写Spring渐进式源码实践》实践笔记(第十六章 三级缓存解决循环依赖)

news2024/11/28 4:11:20

文章目录

  • 第十六章 通过三级缓存解决循环依赖
    • 背景
      • 技术背景
        • Spring循环依赖
        • 循环依赖类型
        • 三级缓存解决循环依赖
      • 业务背景
    • 目标
    • 设计
      • 一级缓存实现方案
        • 设计思路
        • 代码实现
        • 测试结果
      • 三级缓存实现方案
    • 实现
      • 代码结构
      • 类图
      • 实现步骤
    • 测试
      • 事先准备
      • 属性配置文件
      • 测试用例
      • 测试结果:
    • 总结


第十六章 通过三级缓存解决循环依赖

背景

技术背景

Spring循环依赖

在Spring框架中,两个或多个Bean之间相互依赖,形成一个环状依赖的情况, 就是循环依赖。例如,Bean A依赖Bean B,同时Bean B也依赖Bean A,这样就形成了一个循环依赖。循环依赖通常会导致Bean无法正确地被实例化,从而导致应用程序无法正常启动或者出现异常,因此是一种需要尽量避免的情况。

循环依赖类型

根据依赖注入的方式不同,循环依赖可以分为以下几种类型:

  1. 构造器循环依赖:这是指两个或多个Bean通过构造器参数相互依赖。例如,A的构造器需要B作为参数,而B的构造器又需要A作为参数,这就形成了一个闭环。Spring无法解决这种循环依赖,因为构造器注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成。
  2. 属性循环依赖:这是指两个或多个Bean通过属性相互依赖。例如,A有一个setter方法用于注入B,而B也有一个setter方法用于注入A。对于单例(singleton)作用域下的Bean,如果通过setter方式注入属性,Spring通常可以解决这种循环依赖。
三级缓存解决循环依赖

Spring解决循环依赖的主要机制是通过三级缓存。以下是三级缓存解决循环依赖的流程:

  1. 一级缓存:保存所有已经完成了实例化、属性注入、初始化等所有流程的Bean实例。
  2. 二级缓存:保存所有早期创建的Bean对象,这些Bean对象还没有完成依赖注入,但已经提前完成了AOP增强(如果有的话)。
  3. 三级缓存:保存的是singletonBean的生产工厂,即创建单例Bean的工厂。这个工厂用于在需要时生成Bean的实例,并将其放入二级缓存中。

当Spring尝试初始化一个Bean(例如Bean A)时,如果A依赖另一个Bean(例如Bean B),而B又依赖A,就会触发循环依赖的解决流程:

  1. 实例化A,并将A的对象工厂放入三级缓存。
  2. 注入A的依赖属性B,此时一级缓存和二级缓存中都没有B,因此开始初始化B。
  3. 实例化B,并将B的对象工厂放入三级缓存。
  4. 注入B的依赖属性A,此时一级缓存中没有A,二级缓存中也没有A,但在三级缓存中找到了A的对象工厂。
  5. 使用A的对象工厂创建A的实例(如果需要AOP增强,则创建增强后的实例),并将A放入二级缓存。
  6. 完成B的属性注入(此时B依赖的A是二级缓存中的实例),并将B放入一级缓存。
  7. 继续A的属性注入(此时A依赖的B是一级缓存中的实例),完成A的初始化,并将A放入一级缓存。

通过这种方式,Spring能够解决单例作用域下的循环依赖问题。

此外,还有其他一些方法可以帮助解决或避免Spring中的循环依赖问题:

  1. 使用@Lazy注解:对依赖的Bean使用@Lazy注解,可以将其标记为延迟加载。这样,当Bean首次被使用时才会被完全初始化,从而避免在初始化阶段就发生循环依赖。
  2. 重构代码:如果可能的话,通过重构代码来消除循环依赖。例如,可以将一些共享的功能提取到一个新的Bean中,并让A和B都依赖这个新的Bean。
  3. 避免使用构造器注入:对于可能发生循环依赖的Bean,可以考虑使用setter注入或字段注入代替构造器注入。因为构造器注入要求所有依赖在Bean实例化时就已经存在,而setter注入或字段注入则允许在Bean实例化后再设置依赖。

业务背景

按照目前我们实现的 Spring 框架,是可以满足一个基本需求的,但如果你配置了A、B两个Bean对象互相依赖,那么立马会抛出 java.lang.StackOverflowError,为什么呢?因为A创建时需要依赖B创建,而B的创建又依赖于A创建,就这样死循环了。

而这个循环依赖基本也可以说是 Spring 中非常经典的实现了,所要解决的场景主要有以下三种情况:

  1. 循环依赖主要分为这三种,自身依赖于自身(A->A)、互相循环依赖(A->B, B->A)、多组循环依赖(A->B, B->C , C->A)。
  2. 但无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。
  3. 所以除了构造函数注入和原型注入外, Spring还提供了setter 循环依赖注入解决方案。

目标

按照 Spring 框架的设计,实现三个缓存解决循环依赖,这三个缓存分别存放了成品对象半成品对象(未填充属性值)代理对象,分阶段存放对象内容,来解决循环依赖问题。

设计

一级缓存实现方案

设计思路

首先,我们需要知道一个核心的原理,就是用于解决循环依赖就必须是三级缓存呢,二级行吗?一级可以不?其实都能解决,只不过 Spring 框架的实现要保证几个事情,如只有一级缓存处理流程没法拆分,复杂度也会增加同时半成品对象可能会有空指针异常。而将半成品与成品对象分开,处理起来也更加优雅、简单、易扩展。另外 Spring 的两大特性中不仅有 IOC 还有 AOP,也就是基于字节码增强后的方法,该存放到哪,而三级缓存最主要,要解决的循环依赖就是对 AOP 的处理,但如果把 AOP 代理对象的创建提前,那么二级缓存也一样可以解决。但是,这就违背了 Spring 创建对象的原则,Spring 更喜欢把所有的普通 Bean 都初始化完成,再处理代理对象的初始化

不过,没关系我们可以先尝试仅适用一级缓存来解决循环依赖,通过这样的方式从中学习到处理循环依赖的最核心原理,整体设计结构如下图:

image-20241105180055291

  • 如果仅以一级缓存解决循环依赖,那么在实现上可以通过在A对象 newInstance 创建且未填充属性后,直接放入缓存中。

  • A对象的属性填充B对象时,如果缓存中不能获取到B对象,则开始创建B对象,同样创建完成后,把B对象填充到缓存中去。

  • 接下来就开始对B对象的属性进行填充,恰好这会可以从缓存中拿到半成品的A对象,那么这个时候B对象的属性就填充完了。

  • 最后返回来继续完成A对象的属性填充,把实例化后并填充了属性的B对象赋值给A对象的b属性,这样就完成了一个循环依赖操作。

代码实现
  1. 使用一级缓存存放对象的方式,就是这样简单的实现过程,只要是创建完对象,立马塞到缓存里去。这样就可以在其他对象创建时候获取到属性需要填充的对象了。
public class CircleTest {


    /**
     * 一级缓存,存对象.
     */
    private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    private static <T> T getBean(Class<T> beanClass) throws Exception {
        String beanName = beanClass.getSimpleName().toLowerCase();
        if (singletonObjects.containsKey(beanName)) {
            return (T) singletonObjects.get(beanName);
        }
        // 实例化对象入缓存
        Object obj = beanClass.newInstance();
        singletonObjects.put(beanName, obj);

        // 属性填充补全对象
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Class<?> fieldClass = field.getType();
            String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
            field.set(obj, singletonObjects.containsKey(fieldBeanName) ? singletonObjects.get(fieldBeanName) : getBean(fieldClass));
            field.setAccessible(false);
        }
        return (T) obj;
    }


    public static void main(String[] args) throws Exception {
        System.out.println(getBean(B.class).getA());
        System.out.println(getBean(A.class).getB());
    }
}


class A {

    private B b;

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }
}

class B {

    private A a;

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
    }
}

测试结果
 public static void main(String[] args) throws Exception {
        System.out.println(getBean(B.class).getA());
        System.out.println(getBean(A.class).getB());
    }
}

执行结果:

cn.suwg.springframework.test.A@6e0be858
cn.suwg.springframework.test.B@61bbe9ba

Process finished with exit code 0

image-20241105180736978

  1. 从测试效果和截图依赖过程中可以看到,一级缓存也可以解决简单场景的循环依赖问题。
  2. 其实 getBean,是整个解决循环依赖的核心内容,A 创建后填充属性时依赖 B,那么就去创建 B,在创建 B 开始填充时发现依赖于 A,但此时 A 这个半成品对象已经存放在缓存到singletonObjects 中了,所以 B 可以正常创建,再通过递归把 A 也创建完整了。

三级缓存实现方案

有了上述一级缓存方案,解决循环依赖的处理内容,再理解循环依赖就没那么复杂了。接下来我们带着这个感觉去思考如果有对象不只是简单的对象,还有代理对象,还有AOP应用,要怎么处理这样的依赖问题。整体设计结构如下图:

image-20241105181935198

  1. 关于循环依赖在我们目前的 Spring 框架中扩展起来也并不会太复杂,主要就是对于创建对象的提前暴露,如果是工厂对象则会使用 getEarlyBeanReference 逻辑提前将工厂🏭对象存放到三级缓存中。等到后续获取对象的时候实际拿到的是工厂对象中 getObject,这个才是最终的实际对象。
  2. 在创建对象的 AbstractAutowireCapableBeanFactory#doCreateBean 方法中,提前暴露对象以后,就可以通过接下来的流程,getSingleton 从三个缓存中以此寻找对象,一级、二级如果有则直接取走,如果对象是三级缓存中则会从三级缓存中获取后并删掉工厂对象,把实际对象放到二级缓存中。
  3. 最后是关于单例的对象的注册操作,这个注册操作就是把真实的实际对象放到一级缓存中,因为此时它已经是一个成品对象了。

实现

代码结构

image-20241105182058764

源码实现:https://github.com/swg209/spring-study/tree/main/step16-bean-circle-dependency

类图

image-20241106105406362

  1. 在整个类图中,循环依赖的核心功能实现主要包括 DefaultSingletonBeanRegistry 提供三级缓存:singletonObjects、earlySingletonObjects、singletonFactories,分别存放成品对象、半成品对象和工厂对象。同时包装三个缓存提供方法:getSingleton、registerSingleton、addSingletonFactory,这样使用方就可以分别在不同时间段存放和获取对应的对象了。
  2. 在 AbstractAutowireCapableBeanFactory 的 doCreateBean 方法中,提供了关于提前暴露对象的操作,addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean));,以及后续获取对象和注册对象的操作, exposedObject = getSingleton(beanName);、registerSingleton(beanName, exposedObject);,经过这样的处理就可以完成对复杂场景循环依赖的操作。
  3. 另外在 DefaultAdvisorAutoProxyCreator 提供的切面服务中,也需要实现接口 InstantiationAwareBeanPostProcessor 新增的 getEarlyBeanReference 方法,便于把依赖的切面对象也能存放到三级缓存中,处理对应的循环依赖。

实现步骤

  1. 创建对象工厂 ObjectFactory

    定义工厂对象,方便获取工厂代理。

    public interface ObjectFactory<T> {
    
        T getObject() throws BeansException;
    }
    
    
  2. 调整实例化策略

    AbstractAutoWireCapableBeanFactory

    • 调整实例化策略为 SimpleInstantiationStrategy 因为代理的代理类,这里没有再做过多逻辑的判断。
    public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
    
          private InstantiationStrategy instantiationStrategy = new SimpleInstantiationStrategy();
    
     ......
    }
      
    

    DefaultAdvisorAutoProxyCreator

    • AOP advisor自动代理创建时,advisedSupport的proxyTargetClass属性是否开启Cglib代理设置为true。
    • 不开启 CGLIB 代理会报错的原因是,如果目标类没有实现任何接口,JDK 动态代理将无法创建代理对象。JDK 动态代理只能代理实现了接口的类,而 CGLIB 代理可以代理没有实现接口的类。 在你的代码中,如果 advisedSupport.setProxyTargetClass(false) 则会使用 JDK 动态代理。如果目标类没有实现任何接口,代理对象将无法创建,从而导致报错。
    • 通过将 advisedSupport.setProxyTargetClass(true), 启用了 CGLIB 代理,这样即使目标类没有实现任何接口,代理对象也能正确创建,从而避免报错。
      
      public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {
     	......
      
      protected Object wrapIfNecessary(Object bean, String beanName) {
            if (isInfrastructureClass(bean.getClass())) {
                return bean;
            }
    
            Collection<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values();
    
            for (AspectJExpressionPointcutAdvisor advisor : advisors) {
                ClassFilter classFilter = advisor.getPointcut().getClassFilter();
                //过滤匹配类
                if (!classFilter.matches(bean.getClass())) {
                    continue;
                }
                AdvisedSupport advisedSupport = new AdvisedSupport();
    
                TargetSource targetSource = new TargetSource(bean);
                advisedSupport.setTargetSource(targetSource);
                advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
                advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());
                //这里需要修改为true,使用cglib代理.
                advisedSupport.setProxyTargetClass(true);
    
                //返回代理对象.
                return new ProxyFactory(advisedSupport).getProxy();
            }
            return bean;
        }
    }
    
  3. 设置三级缓存

    DefaultSingletonBeanRegistry

    1. 在用于提供单例对象注册的操作的 DefaultSingletonBeanRegistry 类中,共有三个缓存对象的属性;singletonObjects、earlySingletonObjects、singletonFactories,如它们的名字一样,用于存放不同类型的对象(单例对象、早期的半成品单例对象、单例工厂对象)。
    2. 紧接着在这三个缓存对象下提供了获取、添加和注册不同对象的方法,包括:getSingleton、registerSingleton、addSingletonFactory,其实后面这两个方法都比较简单,主要是 getSingleton 的操作,它是在一层层处理不同时期的单例对象,直至拿到有效的对象
    public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
    
        // 一级缓存,普通对象
        private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    
        // 二级缓存,提前暴漏对象,没有完全实例化的对象
        protected final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>();
    
        // 三级缓存,存放代理对象
        private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>();
    
        private final Map<String, DisposableBean> disposableBeans = new LinkedHashMap<>();
    
        @Override
        public Object getSingleton(String beanName) {
            Object singletonObject = singletonObjects.get(beanName);
            if (null == singletonObject) {
                singletonObject = earlySingletonObjects.get(beanName);
                // 判断二级缓存中是否有对象,这个对象就是代理对象,因为只有代理对象才会放到三级缓存中
                if (null == singletonObject) {
                    ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        // 把三级缓存中的代理对象中的真实对象获取出来,放入二级缓存中
                        earlySingletonObjects.put(beanName, singletonObject);
                        singletonFactories.remove(beanName);
                    }
                }
            }
            return singletonObject;
        }
    
        public void registerSingleton(String beanName, Object singletonObject) {
            singletonObjects.put(beanName, singletonObject);
            earlySingletonObjects.remove(beanName);
            singletonFactories.remove(beanName);
        }
    
        protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory){
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
            }
        }
    
        public void registerDisposableBean(String beanName, DisposableBean bean) {
            disposableBeans.put(beanName, bean);
        }
    
    }
    
  4. 提前暴露对象

    AbstractAutowireCapableBeanFactory

    1. 调整实例化策略为 SimpleInstantiationStrategy 因为代理的代理类,这里没有再做过多逻辑的判断。
    2. 在 AbstractAutowireCapableBeanFactory#doCreateBean 的方法中主要是扩展了对象的提前暴露addSingletonFactory了,和单例对象的获取getSingleton以及注册操作registerSingleton。
    3. 这里提到一点 getEarlyBeanReference 就是定义在如 AOP 切面中这样的代理对象,可以参考源码中接口 InstantiationAwareBeanPostProcessor#getEarlyBeanReference 方法的实现。
    public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
    
          private InstantiationStrategy instantiationStrategy = new SimpleInstantiationStrategy();
    
      
        protected Object doCreateBean(String beanName, BeanDefinition beanDefinition, Object[] args) {
            Object bean = null;
            try {
                // 实例化 Bean
                bean = createBeanInstance(beanDefinition, beanName, args);
    
                // 处理循环依赖,将实例化后的Bean对象提前放入缓存中暴露出来
                if (beanDefinition.isSingleton()) {
                    Object finalBean = bean;
                    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean));
                }
    
                // 实例化后判断
                boolean continueWithPropertyPopulation = applyBeanPostProcessorsAfterInstantiation(beanName, bean);
                if (!continueWithPropertyPopulation) {
                    return bean;
                }
                // 在设置 Bean 属性之前,允许 BeanPostProcessor 修改属性值
                applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition);
                // 给 Bean 填充属性
                applyPropertyValues(beanName, bean, beanDefinition);
                // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法
                bean = initializeBean(beanName, bean, beanDefinition);
            } catch (Exception e) {
                throw new BeansException("Instantiation of bean failed", e);
            }
    
            // 注册实现了 DisposableBean 接口的 Bean 对象
            registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);
    
            // 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPE
            Object exposedObject = bean;
            if (beanDefinition.isSingleton()) {
                // 获取代理对象
                exposedObject = getSingleton(beanName);
                registerSingleton(beanName, exposedObject);
            }
            return exposedObject;
    
        }
    
        protected Object getEarlyBeanReference(String beanName, BeanDefinition beanDefinition, Object bean) {
            Object exposedObject = bean;
            for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) {
                if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {
                    exposedObject = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).getEarlyBeanReference(exposedObject, beanName);
                    if (null == exposedObject) return exposedObject;
                }
            }
    
            return exposedObject;
        }
    
       // ...
    }
    

测试

事先准备

相互依赖的类 Husband、Wife、 代理类HusbandMother、AOP切面SpouseAdvice

Husband

public class Husband {

    private Wife wife;


    public String queryWife() {
        return "Husband.wife";
    }

    public Wife getWife() {
        return wife;
    }

    public void setWife(Wife wife) {
        this.wife = wife;
    }
}

Wife

public class Wife {

    private Husband husband;

    private IMother mother; // 婆婆

    public String queryHusband() {
        return "Wife.husband.Mother.takeCare:" + mother.takeCare();
    }

    public Husband getHusband() {
        return husband;
    }

    public void setHusband(Husband husband) {
        this.husband = husband;
    }

    public IMother getMother() {
        return mother;
    }

    public void setMother(IMother mother) {
        this.mother = mother;
    }
}

HusbandMother (代理类)

public class HusbandMother implements FactoryBean<IMother> {
    @Override
    public IMother getObject() throws Exception {

        return (IMother) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class[]{IMother.class},
                (proxy, method, args) -> "婚后媳妇妈妈的职责被婆婆代理了!" + method.getName());
    }

    @Override
    public Class<?> getObjectType() {
        return IMother.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

SpouseAdvice(切面类)

public class SpouseAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("关怀小两口(切面):" + method);
    }
}

属性配置文件

spring.xml

  1. 配置husband依赖wife,配置wife依赖husband和mother,最后是关于AOP切面的依赖使用。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	         http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="husband" class="cn.suwg.springframework.test.bean.Husband">
        <property name="wife" ref="wife"/>
    </bean>

    <bean id="husbandMother" class="cn.suwg.springframework.test.bean.HusbandMother"/>


    <bean id="wife" class="cn.suwg.springframework.test.bean.Wife">
        <property name="husband" ref="husband"/>
        <property name="mother" ref="husbandMother"/>
    </bean>


    <!--    AOP配置,可以验证三级缓存.-->
    <bean class="cn.suwg.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

    <bean id="beforeAdvice" class="cn.suwg.springframework.test.bean.SpouseAdvice"/>

    <bean id="methodInterceptor"
          class="cn.suwg.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor">
        <property name="advice" ref="beforeAdvice"/>
    </bean>

    <bean id="pointcutAdvisor" class="cn.suwg.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
        <property name="expression" value="execution(* cn.suwg.springframework.test.bean.Wife.*(..))"/>
        <property name="advice" ref="methodInterceptor"/>
    </bean>

</beans>

测试用例

public class ApiTest {

    @Test
    public void testCircular() {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
        Husband husband = applicationContext.getBean("husband", Husband.class);
        Wife wife = applicationContext.getBean("wife", Wife.class);
        System.out.println("老公的媳妇:" + husband.queryWife());
        System.out.println("媳妇的老公:" + wife.queryHusband());
    }

}

测试结果:

image-20241105183051485

image-20241105183104981

  • 从测试结果可以看到,无论是简单对象依赖 还是代理工程对象或者 AOP 切面对象都可以在三级缓存下解决循环依赖的问题了。
  • 此外从运行截图 DefaultSingletonBeanRegistry#getSingleton 中也可以看到凡事需要三级缓存存放工厂对象的类,都会使用到 getObject 获取真实对象,并随后存入半成品对象 earlySingletonObjects 中以及移除工厂对象。

总结

  • Spring 中所有的功能都是以解决 Java 编程中的特性而存在的,就像我们本章节处理的循环依赖,如果没有 Spring 框架的情况下,可能我们也会尽可能避免写出循环依赖的操作,因为在没有经过加工处理后,这样的依赖关系肯定会报错的。那么这也就是程序从能用到好用的升级
  • 在解决循环依赖的核心流程中,主要是提前暴露对象的设计,以及建立三级缓存的数据结构来存放不同时期的对象,如果说没有如切面和工厂中的代理对象,那么二级缓存也就可以解决了,哪怕是只有一级缓存。但为了设计上的合理和可扩展性,所以创建了三级缓存来放置不同时期的对象。
  • 排查问题时,可以先自己debug看看情况,对于自己难以排查到问题,可以适当借助大模型工具,提供一些排查思路,可以更快定位到问题。

参考书籍:《手写Spring渐进式源码实践》

书籍源代码:https://github.com/fuzhengwei/small-spring

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2235463.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java8新特性/java

1.lambda表达式 区别于js的箭头函数&#xff0c;python、cpp的lambda表达式&#xff0c;java8的lambda是一个匿名函数&#xff0c;java8运行把函数作为参数传递进方法中。 语法格式 (parameters) -> expression 或 (parameters...) ->{ statements; }实战 替代匿名内部类…

ubuntu双屏只显示一个屏幕另一个黑屏

简洁的结论&#xff1a; 系统环境 ubuntu22.04 nvidia-535解决方案 删除/etc/X11/xorg.conf 文件 记录一下折腾大半天的问题。 ubuntu系统是22.04,之前使用的时候更新驱动导致桌面崩溃&#xff0c;重新安装桌面安装不上&#xff0c;请IT帮忙&#xff0c;IT一番操作过后也表示…

Oracle OCP认证考试考点详解082系列15

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 71. 第71题&#xff1a; 题目 解析及答案&#xff1a; 对于数据库&#xff0c;使用数据库配置助手&#xff08;DBCA&#xff09;可以执行…

为什么说SQLynx是链接国产数据库的最佳选择?

第一是因为SQLynx提供了广泛的国产数据库支持&#xff0c;除了市面上的主流数据库MYSQL、Oracle、PostgreSQL 、SQL Server、SQLite、MongoDB外、还支持达梦人大金仓等国产数据源&#xff01; 近几年随着国产数据库市场的不断发展和成熟&#xff0c;越来越多的企业和机构开始选…

一个基于强大的 PostgreSQL 数据库构建的无代码数据库平台,快速构建应用程序,而无需编写一行代码(带私活源码)

随着企业和个人开发需求的不断增加&#xff0c;无代码平台成为了现代开发的重要组成部分&#xff0c;帮助那些没有技术背景的用户也能轻松创建和管理数据库应用程序。今天&#xff0c;我将向大家推荐一个非常出色的开源项目——Teable&#xff0c;它不仅支持无代码开发&#xf…

软件开发项目管理:实现目标的实用指南

由于软件项目多数是复杂且难以预测的&#xff0c;对软件开发生命周期的深入了解、合适的框架以及强大的工作管理平台是必不可少的。项目管理系统在软件开发中通常以监督为首要任务&#xff0c;但优秀的项目计划、管理框架和软件工具可以使整个团队受益。 软件开发项目管理的主要…

计算网络信号

题目描述&#xff1a; 网络信号经过传递会逐层衰减&#xff0c;且遇到阻隔物无法直接穿透&#xff0c;在此情况下需要计算某个位置的网络信号值。注意&#xff1a;网络信号可以绕过阻隔物 array[m][n]的二维数组代表网格地图&#xff0c; array[i][j]0代表i行j列是空旷位置&…

ESP8266 自定义固件烧录-Tcpsocket固件

一、固件介绍 固件为自定义开发的一个适配物联网项目的开源固件&#xff0c;支持网页配网、支持网页tcpsocket服务器配置、支持串口波特率设置。 方便、快捷、稳定&#xff01; 二、烧录说明 固件及工具打包下载地址&#xff1a; https://download.csdn.net/download/flyai…

数据结构与算法——Java实现 52.力扣98题——验证二叉搜索树

我将一直向前&#xff0c;带着你给我的淤青 —— 24.11.5 98. 验证二叉搜索树 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的…

[mysql]DDL,DML综合案例,

综合案例 题目如下 目录 综合案例 ​编辑 ​编辑 # 1、创#1建数据库test01_library # 2、创建表 books&#xff0c;表结构如下&#xff1a; # 3、向books表中插入记录库存 # 4、将小说类型(novel)的书的价格都增加5。 # 5、将名称为EmmaT的书的价格改为40&#xff0c;并将…

书生实战营第四期-基础岛第三关-浦语提示词工程实践

一、基础任务 任务要求&#xff1a;利用对提示词的精确设计&#xff0c;引导语言模型正确回答出“strawberry”中有几个字母“r”。 1.提示词设计 你是字符计数专家&#xff0c;能够准确回答关于文本中特定字符数量的问题。 - 技能&#xff1a; - &#x1f4ca; 分析文本&…

国药准字生发产品有哪些?这几款不错

头秃不知道怎么选的朋友们看这&#xff0c;基本上市面上火的育发精华我都用了个遍了&#xff0c;陆陆续续也花了有大几w了&#xff0c;都是真金白银总结出来的&#xff0c;所以必须要给掉发人分享一些真正好用的育发产品&#xff0c;大家可以根据自己实际情况来选择。 1. 露卡菲…

构建基于 DCGM-Exporter, Node exporter,PROMETHEUS 和 GRAFANA 构建算力监控系统

目录 引言工具作用概述DCGM-ExporterNode exporterPROMETHEUSGRAFANA小结 部署单容器DCGM-ExporterNode exporterPROMETHEUSGRAFANANode exporterDCGM-Exporter 多容器Node exporterDCGM-ExporterDocker Compose 参考 引言 本文的是适用对象&#xff0c;是希望通过完全基于Doc…

Java入门14——动态绑定(含多态)

大家好&#xff0c;我们今天来学动态绑定和多态&#xff0c;话不多说&#xff0c;开始正题~ 但是要学动态绑定之前&#xff0c;我们要学习一下向上转型&#xff0c;方便后续更好地理解~ 一、向上转型 1.什么是向上转型 网上概念有很多&#xff0c;但其实通俗来讲&#xff0c…

Request 和 Response 万字详解

文章目录 1.Request和Response的概述2.Request对象2.1 Request 继承体系2.2 Request获取请求数据2.2.1 获取请求行数据2.2.2 获取请求头数据2.2.3 获取请求体数据2.2.4 获取请求参数的通用方式 2.3 解决post请求乱码问题 掌握内容讲解内容小结 2.4 Request请求转发 3.HTTP响应详…

Qt QCustomplot 在采集信号领域的应用

文章目录 一、常用的几种开源库:1、QCustomPlot:2、QChart:3、Qwt:QCustomplot 在采集信号领域的应用1、应用实例时域分析频谱分析2.数据筛选和处理其他参考自然界中的物理过程、传感器和传感器网络、电路和电子设备、通信系统等都是模拟信号的来源。通过可视化模拟信号,可以…

【初阶数据结构与算法】沉浸式刷题之顺序表练习(顺序表以及双指针两种方法)

文章目录 顺序表练习1.移除数组中指定的元素方法1&#xff08;顺序表&#xff09;方法2&#xff08;双指针&#xff09; 2.删除有序数组中的重复项方法1&#xff08;顺序表&#xff09;方法2&#xff08;双指针&#xff09; 3.双指针练习之合并两个有序数组方法1&#xff08;直…

机器学习—矩阵乘法

矩阵只是一个或两个数组。 如何在向量之间取点积&#xff1f; 让我们用列向量[1,2]*[[3,4]&#xff0c;如果z是这两个向量之间的点积&#xff0c;然后通过乘以第一个元素来计算z&#xff0c;通过这里的第一个元素&#xff0c;所以是1*3加第二个元素乘以第二个元素即2*4&#…

使用 ADB 在某个特定时间点点击 Android 设备上的某个按钮

前提条件 安装 ADB&#xff1a;确保你已经在计算机上安装了 Android SDK&#xff08;或单独的 ADB&#xff09;。并将其添加到系统环境变量中&#xff0c;以便你可以在命令行中运行 adb。 USB调试&#xff1a;确保 Android 设备已启用 USB 调试模式。这可以在设备的“设置” -…

数据结构之二叉树前序,中序,后序习题分析(递归图)

1.比较相同的树 二叉树不能轻易用断言&#xff0c;因为树一定有空 2.找结点值 3.单值二叉树 4.对称二叉树 5.前序遍历