当前位置: 首页 > news >正文

手写Spring:第17章-通过三级缓存解决循环依赖

文章目录

  • 一、目标:通过三级缓存解决循环依赖
  • 二、设计:通过三级缓存解决循环依赖
    • 2.1 通过三级缓存解决循环依赖
    • 2.2 尝试使用一级缓存解决循环依赖
  • 三、实现:通过三级缓存解决循环依赖
    • 3.1 工程结构
    • 3.2 通过三级缓存解决循环依赖类图
    • 3.3 设置三级缓存
      • 3.3.1 对象工厂
      • 3.3.2 设置三级缓存
    • 3.4 提前暴露对象
      • 3.4.1 实例化感知对象处理
      • 3.4.2 默认自动代理创建者
      • 3.4.3 实现默认bean创建的抽象bean工厂超类
  • 四、测试:通过三级缓存解决循环依赖
    • 4.1 添加测试配置
      • 4.1.1 老公类
      • 4.1.2 媳妇类
      • 4.1.3 婆婆接口
      • 4.1.4 婆婆代理类
      • 4.1.5 切面
      • 4.1.6 Spring属性配置文件
    • 4.2 单元测试
  • 五、总结:通过三级缓存解决循环依赖

一、目标:通过三级缓存解决循环依赖

💡 如何解决类与类之间的循环依赖?

  • 在目前的 Spring 框架中,如果你配置了 A、B 两个 Bean 对象互相依赖,那么立刻会抛出 java.lang.StackOverflowError,为什么?
    • 因为 A 创建时需要依赖 B 创建,而 B 的创建又依赖于 A 创建,就这样死循环了。
  • 循环依赖 基本可以说是 Spring 中经典的实现,所要解决的场景主要三种情况。

在这里插入图片描述

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

二、设计:通过三级缓存解决循环依赖

2.1 通过三级缓存解决循环依赖

💡 设计:通过三级缓存解决循环依赖

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

在这里插入图片描述

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

2.2 尝试使用一级缓存解决循环依赖

在这里插入图片描述

  • 如果仅以一级缓存解决循环依赖,那么在实现上可以通过 A 对象 newInstance 创建且未填充属性后,直接放入缓存中。
  • A 对象的属性填充 B 对象时,如果缓存中不能获取到 B 对象,则开始创建 B 对象,同样创建完成后,把 B 对象填充到缓存中。
  • 接下来就开始对 B 对象的属性进行填充,恰好这会可以从缓存中拿到半成品的 A 对象,那么这个时候 B 对象的属性就填充完了。
  • 最后返回来继续完成 A 对象的属性填充,把实例化后并填充了属性的 B 对象赋值给 A 对象的 b 属性,这样就完成了一个循环依赖操作。

CircleTest.java

package com.lino.springframework.test;import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @description: 循环依赖案例*/
public class CircleTest {private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);public static void main(String[] args) throws Exception {System.out.println(getBean(B.class).getA());System.out.println(getBean(A.class).getB());}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;}
}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;}
}

测试结果

com.lino.springframework.test.A@266474c2
com.lino.springframework.test.B@6f94fa3e

在这里插入图片描述

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

三、实现:通过三级缓存解决循环依赖

3.1 工程结构

spring-step-16
|-src|-main|	|-java|		|-com.lino.springframework|			|-aop|			|	|-aspectj|			|	|	|-AspectJExpressionPointcut.java|			|	|	|-AspectJExpressionPointcutAdvisor.java|			|	|-framework|			|	|	|-adapter|			|	|	|	|-MethodBeforeAdviceInterceptor.java|			|	|	|-autoproxy|			|	|	|	|-DefaultAdvisorAutoProxyCreator.java|			|	|	|-AopProxy.java|			|	|	|-Cglib2AopProxy.java|			|	|	|-JdkDynamicAopProxy.java|			|	|	|-ProxyFactory.java|			|	|	|-ReflectiveMethodInvocation.java|			|	|-AdvisedSupport.java|			|	|-Advisor.java|			|	|-BeforeAdvice.java|			|	|-ClassFilter.java|			|	|-MethodBeforeAdvice.java|			|	|-MethodMatcher.java|			|	|-Pointcut.java|			|	|-PointcutAdvisor.java|			|	|-TargetSource.java|			|-beans|			|	|-factory|			|	|	|-annotation|			|	|	|	|-Autowired.java|			|	|	|	|-AutowiredAnnotationBeanPostProcessor.java|			|	|	|	|-Qualifier.java|			|	|	|	|-Value.java|			|	|	|-config|			|	|	|	|-AutowireCapableBeanFactory.java|			|	|	|	|-BeanDefinition.java|			|	|	|	|-BeanFactoryPostProcessor.java|			|	|	|	|-BeanPostProcessor.java|			|	|	|	|-BeanReference.java|			|	|	|	|-ConfigurableBeanFactory.java|			|	|	|	|-InstantiationAwareBeanPostProcessor.java|			|	|	|	|-SingletonBeanRegistry.java|			|	|	|-support|			|	|	|	|-AbstractAutowireCapableBeanFactory.java|			|	|	|	|-AbstractBeabDefinitionReader.java|			|	|	|	|-AbstractBeabFactory.java|			|	|	|	|-BeabDefinitionReader.java|			|	|	|	|-BeanDefinitionRegistry.java|			|	|	|	|-CglibSubclassingInstantiationStrategy.java|			|	|	|	|-DefaultListableBeanFactory.java|			|	|	|	|-DefaultSingletonBeanRegistry.java|			|	|	|	|-DisposableBeanAdapter.java|			|	|	|	|-FactoryBeanRegistrySupport.java|			|	|	|	|-InstantiationStrategy.java|			|	|	|	|-SimpleInstantiationStrategy.java|			|	|	|-xml|			|	|	|	|-XmlBeanDefinitionReader.java|			|	|	|-Aware.java|			|	|	|-BeanClassLoaderAware.java|			|	|	|-BeanFactory.java|			|	|	|-BeanFactoryAware.java|			|	|	|-BeanNameAware.java|			|	|	|-ConfigurableListableBeanFactory.java|			|	|	|-DisposableBean.java|			|	|	|-FactoryBean.java|			|	|	|-HierarcgicalBeanFactory.java|			|	|	|-InitializingBean.java|			|	|	|-ListableBeanFactory.java|			|	|	|-ObjectFactory.java|			|	|	|-PropertyPlaceholderConfigurer.java|			|	|-BeansException.java|			|	|-PropertyValue.java|			|	|-PropertyValues.java|			|-context|			|	|-annotation|			|	|	|-ClassPathBeanDefinitionScanner.java|			|	|	|-ClassPathScanningCandidateComponentProvider.java|			|	|	|-Scope.java|			|	|-event|			|	|	|-AbstractApplicationEventMulticaster.java|			|	|	|-ApplicationContextEvent.java|			|	|	|-ApplicationEventMulticaster.java|			|	|	|-ContextclosedEvent.java|			|	|	|-ContextRefreshedEvent.java|			|	|	|-SimpleApplicationEventMulticaster.java|			|	|-support|			|	|	|-AbstractApplicationContext.java|			|	|	|-AbstractRefreshableApplicationContext.java|			|	|	|-AbstractXmlApplicationContext.java|			|	|	|-ApplicationContextAwareProcessor.java|			|	|	|-ClassPathXmlApplicationContext.java|			|	|-ApplicationContext.java|			|	|-ApplicationContextAware.java|			|	|-ApplicationEvent.java|			|	|-ApplicationEventPublisher.java|			|	|-ApplicationListener.java|			|	|-ConfigurableApplicationContext.java|			|-core.io|			|	|-ClassPathResource.java|			|	|-DefaultResourceLoader.java|			|	|-FileSystemResource.java|			|	|-Resource.java|			|	|-ResourceLoader.java|			|	|-UrlResource.java|			|-stereotype|			|	|-Component.java|			|-util|			|	|-ClassUtils.java|			|	|-StringValueResolver.java|-test|-java|-com.lino.springframework.test|-bean|	|-Husband.java|	|-HusbandMother.java|	|-IMother.java|	|-SpouseAdvice.java|	|-Wife.java|-ApiTest.java|-CircleTest.java|-resources|-spring.xml

3.2 通过三级缓存解决循环依赖类图

在这里插入图片描述

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

3.3 设置三级缓存

3.3.1 对象工厂

ObjectFactory.java

package com.lino.springframework.beans.factory;import com.lino.springframework.beans.BeansException;/*** @description: 对象工厂*/
public interface ObjectFactory<T> {/*** 获取对象** @return 泛型对象* @throws BeansException 异常*/T getObject() throws BeansException;
}

3.3.2 设置三级缓存

DefaultSingletonBeanRegistry.java

package com.lino.springframework.beans.factory.support;import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.factory.DisposableBean;
import com.lino.springframework.beans.factory.ObjectFactory;
import com.lino.springframework.beans.factory.config.SingletonBeanRegistry;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;/*** @description: 通用的注册表实现*/
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {protected static final Object NULL_OBJECT = new Object();/*** 一级缓存,普通对象*/private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();/*** 二级缓存,提前暴露对象,没有完全实例化的对象*/protected final Map<String, Object> earlySingletonObjects = new HashMap<>();/*** 三级缓存,存放代理对象*/private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();private final Map<String, DisposableBean> disposableBeans = new HashMap<>();@Overridepublic 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;}@Overridepublic void registerSingletonBean(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);}public void destroySingletons() {Set<String> keySet = this.disposableBeans.keySet();Object[] disposableBeanNames = keySet.toArray();for (int i = disposableBeanNames.length - 1; i >= 0; i--) {Object beanName = disposableBeanNames[i];DisposableBean disposableBean = disposableBeans.remove(beanName);try {disposableBean.destroy();} catch (Exception e) {throw new BeansException("Destroy method on bean with name '" + beanName + "' threw an exception", e);}}}
}
  • 在用于提供单例对象注册操作的 DefaultSingletonBeanRegistry 类中,共有三个缓存对象的属性:
    • singletonObjectsearlySingletonObjectssingletonFactories
    • 用于存放不同类型的对象:单例对象、早期的半成品单例对象、单例工厂对象。
  • 紧接着在这三个缓存对象下提供了获取、添加和注册不同对象的方法,包括:getSingletonregisterSingletonBeanaddSingletonFactory
    • 后面的两个方法都比较简单,主要是 getSingleton 的操作,它是一层层处理不同时期的单例对象,直至拿到有效的对象。

3.4 提前暴露对象

3.4.1 实例化感知对象处理

InstantiationAwareBeanPostProcessor.java

package com.lino.springframework.beans.factory.config;import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.PropertyValues;/*** @description: 实例化感知对象处理*/
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {.../*** 在 Spring 中由 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference 提供** @param bean     对象* @param beanName 对象名称* @return 二级缓存对象*/default Object getEarlyBeanReference(Object bean, String beanName) {return bean;}
}
  • 添加 getEarlyBeanReference 缓存二级缓存对象方法

3.4.2 默认自动代理创建者

DefaultAdvisorAutoProxyCreator.java

package com.lino.springframework.aop.framework.autoproxy;import com.lino.springframework.aop.*;
import com.lino.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import com.lino.springframework.aop.framework.ProxyFactory;
import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.PropertyValues;
import com.lino.springframework.beans.factory.BeanFactory;
import com.lino.springframework.beans.factory.BeanFactoryAware;
import com.lino.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import com.lino.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;/*** @description: 默认自动代理创建者*/
public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {private DefaultListableBeanFactory beanFactory;private final Set<Object> earlyProxyReferences = Collections.synchronizedSet(new HashSet<>());@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = (DefaultListableBeanFactory) beanFactory;}@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {return null;}@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {return true;}private boolean isInfrastructureClass(Class<?> beanClass) {return Advice.class.isAssignableFrom(beanClass) || Pointcut.class.isAssignableFrom(beanClass) || Advisor.class.isAssignableFrom(beanClass);}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (!earlyProxyReferences.contains(beanName)) {return wrapIfNecessary(bean, beanName);}return bean;}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());advisedSupport.setProxyTargetClass(true);// 返回代理对象return new ProxyFactory(advisedSupport).getProxy();}return bean;}@Overridepublic Object getEarlyBeanReference(Object bean, String beanName) {earlyProxyReferences.add(beanName);return wrapIfNecessary(bean, beanName);}@Overridepublic PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException {return pvs;}
}

3.4.3 实现默认bean创建的抽象bean工厂超类

AbstractAutowireCapableBeanFactory.java

package com.lino.springframework.beans.factory.support;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.PropertyValue;
import com.lino.springframework.beans.PropertyValues;
import com.lino.springframework.beans.factory.*;
import com.lino.springframework.beans.factory.config.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;/*** @description: 实现默认bean创建的抽象bean工厂超类*/
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {private InstantiationStrategy instantiationStrategy = new SimpleInstantiationStrategy();@Overrideprotected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {// 判断是否返回代理 Bean 对象Object bean = resolveBeforeInstantiation(beanName, beanDefinition);if (null != bean) {return bean;}return doCreateBean(beanName, beanDefinition, args);}protected Object doCreateBean(String beanName, BeanDefinition beanDefinition, Object[] args) {Object bean = null;try {// 实例化Beanbean = 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_PROTOTYPEObject exposedObject = bean;if (beanDefinition.isSingleton()) {// 获取代理对象exposedObject = getSingleton(beanName);registerSingletonBean(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;}...
}
  • AbstractAutowireCapableBeanFactory#doCreateBean 的方法中主要是扩展了对象的提前暴露 addSingletonFactory,和单例对象的获取 getSingleton,以及注册操作 registerSingletonBean
  • getEarlyBeanReference 方法就是定义在如 AOP 切面中这样的代理对象。

四、测试:通过三级缓存解决循环依赖

4.1 添加测试配置

4.1.1 老公类

Husband.java

package com.lino.springframework.test.bean;/*** @description: 老公类*/
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;}
}

4.1.2 媳妇类

Wife.java

package com.lino.springframework.test.bean;/*** @description: 媳妇类*/
public class Wife {private Husband husband;private IMother mother;public String queryHusband() {return "Wife.husband、Mother.callMother: " + mother.callMother();}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;}
}

4.1.3 婆婆接口

IMother.java

package com.lino.springframework.test.bean;/*** @description: 婆婆接口*/
public interface IMother {String callMother();
}

4.1.4 婆婆代理类

HusbandMother.java

package com.lino.springframework.test.bean;import com.lino.springframework.beans.factory.FactoryBean;
import java.lang.reflect.Proxy;/*** @description: 老公婆婆类*/
public class HusbandMother implements FactoryBean<IMother> {@Overridepublic IMother getObject() throws Exception {return (IMother) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IMother.class},(proxy, method, args) -> "婚后媳妇妈妈的职责被婆婆代理了!" + method.getName());}@Overridepublic Class<?> getObjectType() {return IMother.class;}@Overridepublic boolean isSingleton() {return true;}
}

4.1.5 切面

SpouseAdvice.java

package com.lino.springframework.test.bean;import com.lino.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;/*** @description: 拦截器*/
public class SpouseAdvice implements MethodBeforeAdvice {@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {System.out.println("关怀小两口(切面):" + method);}
}

4.1.6 Spring属性配置文件

spring.xml

<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="husband" class="com.lino.springframework.test.bean.Husband"><property name="wife" ref="wife"/></bean><bean id="husbandMother" class="com.lino.springframework.test.bean.HusbandMother"/><bean id="wife" class="com.lino.springframework.test.bean.Wife"><property name="husband" ref="husband"/><property name="mother" ref="husbandMother"/></bean><!--AOP 配置,验证三级缓存    --><bean class="com.lino.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/><bean id="beforeAdvice" class="com.lino.springframework.test.bean.SpouseAdvice"/><bean id="methodInterceptor" class="com.lino.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor"><property name="advice" ref="beforeAdvice"/></bean><bean id="pointcutAdvisor" class="com.lino.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor"><property name="expression" value="execution(* com.lino.springframework.test.bean.Wife.*(..))"/><property name="advice" ref="methodInterceptor"/></bean></beans>

4.2 单元测试

ApiTest.java

@Test
public void test_autoProxy() {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());
}

测试结果

老公的媳妇:Husband.wife
关怀小两口(切面)public java.lang.String com.lino.springframework.test.bean.Wife.queryHusband()
媳妇的老公:Wife.husband、Mother.callMother: 婚后媳妇妈妈的职责被婆婆代理了!callMother

在这里插入图片描述

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

五、总结:通过三级缓存解决循环依赖

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

相关文章:

手写Spring:第17章-通过三级缓存解决循环依赖

文章目录 一、目标&#xff1a;通过三级缓存解决循环依赖二、设计&#xff1a;通过三级缓存解决循环依赖2.1 通过三级缓存解决循环依赖2.2 尝试使用一级缓存解决循环依赖 三、实现&#xff1a;通过三级缓存解决循环依赖3.1 工程结构3.2 通过三级缓存解决循环依赖类图3.3 设置三…...

C#使用proto

写多了go代码&#xff0c;被go mod tidy惯坏了&#xff0c;还以为全天下的都很好用呢&#xff0c;结果发现并不是这样。尤其是项目组的proto还是又封了个工具直接就能跑得&#xff0c;导致以为没那么复杂的事情变得复杂了起来。是有两套生成的规则&#xff0c;时间有点晚&#…...

Java基础知识面试题(一)(英语答案)

加油 前言Java中的基本数据类型包括以下几种:String和StringBuilder的区别是什么?什么是面向对象编程(OOP)?如何在Java中创建一个类?什么是继承?如何在Java中实现继承?什么是多态性?如何在Java中实现多态性?什么是封装和继承?什么是接口(Interface)?如何在Java中…...

基于csv数据建立线性回归模型并预测进行评估模型表现案例实现

一、数据处理 1.加载csv数据进行查看 import pandas as pd data pd.read_csv("generated_data.csv") print(data)2.将上述数据的x和y进行分离开&#xff0c;便于后续进行坐标建立 x data.loc[:,x] y data.loc[:,y] print(x,y)3.先使用matplotlib进行显示数据 …...

MySQL学习问题记录

文章目录 MySQL学习问题记录1、查询记录自动根据id排序&#xff1f; MySQL学习问题记录 1、查询记录自动根据id排序&#xff1f; step1&#xff1a;建表 表项信息&#xff1a; 写入数据顺序id为10 2 7 1。查寻时返回记录顺序为1 2 7 10&#xff1f; 更新一条数据后仍然按照…...

YMatrix 5.0 与天翼云完成产品兼容性认证

近日&#xff0c;北京四维纵横数据技术有限公司与天翼云宣布完成产品兼容性认证。经过双方严格的测试验证&#xff0c;超融合数据库 YMatrix 5.0 与天翼云兼容性良好&#xff0c;可基于天翼云稳定运行。 数据库系统作为基础软件的核心&#xff0c;自主可控势在必行。在此背景下…...

蓝桥杯官网练习题(旋转)

题目描述 图片旋转是对图片最简单的处理方式之一&#xff0c;在本题中&#xff0c;你需要对图片顺时针旋转 90 度。 我们用一个 nm 的二维数组来表示一个图片&#xff0c;例如下面给出一个 34 的 图片的例子&#xff1a; 1 3 5 7 9 8 7 6 3 5 9 7 这个图片顺时针旋转 90 …...

Jtti:Linux如何开机启动bootstrap

在Linux中&#xff0c;"bootstrap"通常不是一个单独的启动项&#xff0c;而是指引导过程的一部分。引导过程涉及到启动引导加载程序&#xff0c;加载内核&#xff0c;初始化系统并启动各种服务。启动过程中不会直接启动"bootstrap"&#xff0c;而是通过引导…...

qt之事件循环与线程的关系

先说重点&#xff0c;先了解几个重要的概念&#xff0c; 事件调度器&#xff0c;该调度器的具体实现与操作系统相关&#xff0c;不同的操作系统具有不同的实现&#xff0c;例如linux系统下该调度器的实现为QEventDispatcherUNIX&#xff0c;而window下的他们的实现为QEventDis…...

Python 变量的定义和数据类型的转换

变量 变量的定义 基本语法&#xff1a;变量名 值 变量名是给对象贴一个用于访问的标签&#xff0c;给对象绑定名字的过程也称为赋值&#xff0c;赋值符号 “” 变量名自定义&#xff0c;要满足标识符命名规则。 Python中&#xff0c;不需要事先声明变量名及其类型&#xff…...

Android Java JVM常见问答分析与总结

一、JVM是什么 JVM是JavaVirtualMachine&#xff08;Java虚拟机&#xff09;的缩写&#xff0c;JVM是一种用于计算设备的规范&#xff0c;它是一个虚构出来的计算机&#xff0c;是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 JVM的重要性 JVM这块是一个偏向于概念模…...

【业务功能篇102】springboot+mybatisPlus分页查询,统一返回封装规范

业务场景&#xff1a; 随着业务代码量增多&#xff0c;很多接口查询的分页写法各种各样&#xff0c;为了使项目工程代码易于维护&#xff0c;我们统一规范&#xff0c;相对没有那么复杂的接口&#xff0c;我们统一都在java的service实现类中&#xff0c;去完成分页查询的接口逻…...

中国手机新进程:折叠屏出海的荣耀,5G中回归的华为

最近&#xff0c;“华为5G回归”“自研麒麟芯片回归”的消息引爆网络。网友开心庆贺之余&#xff0c;也纷纷猜测&#xff0c;华为强势归来&#xff0c;哪家友商最慌&#xff1f; “华为的回归&#xff0c;让竞争充满了更多的可能性和更多的魅力”&#xff0c;与华为渊源颇深的…...

安装RabbitMQ的各种问题(包括已注册成windows服务后,再次重新安装,删除服务重新注册遇到的问题)

一、安装Erlang&#xff08;傻瓜式安装&#xff09; 安装完成之后&#xff0c;配置环境变量&#xff1a; 1.新建系统变量名为&#xff1a;ERLANG_HOME 变量值为erlang安装地址 2. 双击系统变量path&#xff0c;点击“新建”&#xff0c;将%ERLANG_HOME%\bin加入到path中。 …...

多线程与高并发——并发编程(6)

文章目录 六、并发集合1 ConcurrentHashMap1.1 存储结构1.2 存储操作1.2.1 put方法1.2.2 putVal方法-散列算法1.2.3 putVal方法-添加数据到数组&初始化数组1.2.4 putVal方法-添加数据到链表1.3 扩容操作1.3.1 treeifyBin方法触发扩容1.3.2 tryPresize方法-针对putAll的初始…...

Elasticsearch——Docker单机部署安装

文章目录 1 简介2 Docker安装与配置2.1 安装Docker2.2 配置Docker镜像加速器2.3 调整Docker资源限制 3 准备Elasticsearch Docker镜像3.1 下载Elasticsearch镜像3.2 自定义镜像配置3.3执行Docker Compose 4 运行Elasticsearch容器4.1 创建Elasticsearch容器4.2 修改配置文件4.3…...

基于AHP模型指标权重分析python整理

一 背景介绍 日常会有很多定量分析的场景&#xff0c;然而也会有一些定性分析的场景针对定性分析的场景&#xff0c;预测者只能通过主观判断分析能力来推断事物的性质和发展趋势然而针对个人的直觉和虽然能够有一定的协助判断效果&#xff0c;但是很难量化到指标做后期的复用 …...

用python实现基本数据结构【02/4】

*说明 如果需要用到这些知识却没有掌握&#xff0c;则会让人感到沮丧&#xff0c;也可能导致面试被拒。无论是花几天时间“突击”&#xff0c;还是利用零碎的时间持续学习&#xff0c;在数据结构上下点功夫都是值得的。那么Python 中有哪些数据结构呢&#xff1f;列表、字典、集…...

蓝牙Mesh专有DFU

蓝牙Mesh专有DFU Mesh专有DFU协议介绍特征DFU模式和类型角色并发传输混合设备的网络传输速率后台操作传输分区内存映射安全DFU固件IDApplication firmware IDSoftDevice firmware IDBootloader firmware ID 设备页面格式内容 Mesh专有DFU协议介绍 设备固件更新(Device Firmwar…...

浅谈综合管廊智慧运维管理平台应用研究

贾丽丽 安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;为提升综合管廊运维管理水平&#xff0c;实现管理的数字化转型&#xff0c;采用综合监测系统、BIMGIS 可视化系统、智能机器人巡检、结构安全监测等技术&#xff0c;搭建实时监控、应急管理、数据分析等多功能…...

Httpservletrequest与Httpservletresponse

目录 一、Httpservletrequest 1.1什么是Httpservletrequest 1.2Httpservletrequest中的方法 二、Httpservletresponse 1.1什么是Httpservletresponse 1.2Httpservletresponse的方法 一、Httpservletrequest 1.1什么是Httpservletrequest HttpServletRequest&#xff08;…...

文件上传之图片码混淆绕过(upload的16,17关)

目录 1.upload16关 1.上传gif loadup17关&#xff08;文件内容检查&#xff0c;图片二次渲染&#xff09; 1.上传gif&#xff08;同上面步骤相同&#xff09; 2.条件竞争 1.upload16关 1.上传gif imagecreatefromxxxx函数把图片内容打散&#xff0c;&#xff0c;但是不会…...

Jetsonnano B01 笔记5:IIC通信

今日继续我的Jetsonnano学习之路&#xff0c;今日学习的是IIC通信&#xff0c;并尝试使用Jetson读取MPU6050陀螺仪数据。文章提供源码。文章主要是搬运的官方PDF说明&#xff0c;这里结合自己实际操作作笔记。 目录 IIC通信&#xff1a; IIC硬件连线&#xff1a; 安装IIC库文…...

【网络爬虫笔记】爬虫Robots协议语法详解

Robots协议是指一个被称为Robots Exclusion Protocol的协议。该协议的主要功能是向网络蜘蛛、机器人等搜索引擎爬虫提供一个标准的访问控制机制&#xff0c;告诉它们哪些页面可以被抓取&#xff0c;哪些页面不可以被抓取。本文将进行爬虫Robots协议语法详解&#xff0c;同时提供…...

MATLAB 2022b 中设置关闭 MATLAB 之前进行询问

在 MATLAB 2022b 中可以进行设置&#xff0c;在关闭 MATLAB 之前进行询问&#xff0c;防止意外关闭 MATLAB。如图&#xff1a;...

在SpringBoot框架下,接口有读个实现类,在不改变任何源码的情况下,SpringBoot怎么知道给接口注入哪个实现类的依赖呢?

在Spring Boot框架下&#xff0c;当一个接口有多个实现类时&#xff0c;Spring Boot 默认情况下不知道要注入哪个实现类的依赖。因此&#xff0c;你需要使用一些方法来明确告诉Spring Boot应该注入哪个实现类的依赖。 以下是一些常用的方法&#xff1a; 1.使用Qualifier注解&a…...

探索数据库管理的利器 - PHPMyAdmin

有一个项目&#xff0c;后端由博主独自负责&#xff0c;最近需要将项目交接给另一位同事。在项目初期&#xff0c;博主直接在数据库中使用工具创建了相关表格&#xff0c;并在完成后利用PhpMyAdmin生成了一份数据字典&#xff0c;供团队使用。然而&#xff0c;在随后的开发过程…...

大数据技术原理与应用学习笔记第1章

黄金组合访问地址&#xff1a;http://dblab.xmu.edu.cn/post/7553/ 1.《大数据技术原理与应用》教材 官网&#xff1a;http://dblab.xmu.edu.cn/post/bigdata/ 2.大数据软件安装和编程实践指南 官网林子雨编著《大数据技术原理与应用》教材配套大数据软件安装和编程实践指…...

算法从未放弃你,放弃你的只有你自己

在人生的旅程中&#xff0c;我们常常会遇到各种挫折和困难。有些人在面对困境时&#xff0c;会选择放弃&#xff0c;将责任归咎于命运或外部环境。然而&#xff0c;算法教给我们一个重要的道理&#xff1a;永远不要放弃 当我们遇到问题或挑战时&#xff0c;算法可以帮助我们找到…...

[Linux 基础] linux基础指令(1)

文章目录 1、Linux下基本指令1.ls指令2.pwd指令3.cd指令4.touch指令5.mkdir指令6.rmdir指令 && rm指令7.man指令8.cp指令9.mv指令10.cat指令11.more指令12.less指令 Linux学习笔记从今天开始不断更新了。第一篇我们从基础指令开始学起。 1、Linux下基本指令 好多人都说…...

校园网网站分页党群建设/个人微信管理系统

在网上找到了一句得到删除数据库中所有外键约束的语句的sql语句 但是发现这只是一句查询&#xff0c;要执行的话&#xff0c;还得复制出来执行&#xff0c;比较麻烦 于是写了个sp来自动执行&#xff0c;比较方便 代码如下&#xff1a; Sql代码 CREATE PROCEDURE sp_drop_all_f…...

湖南免费网站建设/中小企业管理培训课程

&#xff08;原标题&#xff1a;湖南省妇幼保健院自主开发国内首个妇幼移动就医平台&#xff09; 湖南省妇幼保健院自主开发国内首个妇幼移动就医平台—— 妇幼&#xff0c;就医方便在掌上 记者 段涵敏 通讯员 周文奕 “手机装了‘掌上妇幼’后&#xff0c;我再也不用到窗口…...

网站提交至google/谷歌自然排名优化

这里介绍的就是 MSF 的一些简单的基本使用&#xff0c;创始人 HD 摩尔曾说谁为 MS 写教程其实就是自作自受&#xff0c;纯属找虐&#xff0c;因为真的变化太快了&#xff0c;所以这里所写使一些基本功能&#xff0c;其余的需要自己去摸索… !!! 但虽然这样&#xff0c;还是有人…...

做服装最好的网站建设/新东方英语培训机构官网

2019独角兽企业重金招聘Python工程师标准>>> ​ 简介 红帽在2019年5月7号红帽正式发布了rhel8&#xff0c;在发布rhel8之前红帽还换了logo&#xff0c;从要脸变成了不要脸&#xff0c;这么重大的改变&#xff0c;作为一个运维自然要关注一下 下载 下载地址如下 http…...

益阳市住房和建设局 网站/全网热搜榜

2019独角兽企业重金招聘Python工程师标准>>> targetSdkVersion如果是23以下&#xff0c;调用ActivityCompat.requestPermissions()&#xff0c;会弹出权限选择对话框&#xff0c;但是选择拒绝授权&#xff0c;onRequestPermissionsResult中的返回值却是PERMISSION_G…...

2015年做那些网站能致富/产品推广渠道

1.Jdk Linux(Centos)安装Java JDK及卸载 2.Tomcat (开机自启动) Linux(Centos)安装tomcat并且部署Java Web项目 3.CentOS7.3搭建ApachePHP7web SVNMariaDB Web服务器&#xff08;2017-10-24&#xff09; CentOS7.3搭建ApachePHP7web SVNMariaDB Web服务器&#xff08;2017-10-2…...