Spring 提供了十分强大的依赖注入功能,使得我们不再需要手动去管理对象的依赖项。然而,在实际的使用场景中,可能会遇到类似下面的依赖异常:

Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'author' defined in file [/home/lxh/JavaProject/sample/target/classes/com/example/sample/component/Author.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'book' defined in file [/home/lxh/JavaProject/sample/target/classes/com/example/sample/component/Book.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'author': Requested bean is currently in creation: Is there an unresolvable circular reference?

一般来讲,这种情况的出现都是由于 Bean 类的设计不合理导致的,通常的解决方案都是选择重新设计 Bean 类的组织结构来打破循环依赖。然而,在某些特定的情况下,可能没有办法重新设计这些类组织结构(比如旧有代码的遗留问题等),这种情况有几种特殊的处理方式。但是在介绍这些处理方式之前,让我们先来了解一下 Spring 中对于循环依赖的处理

IOC 处理流程

在开始之前,明确一下常用的几个概念:

  • 一级缓存

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

    这个缓存的目的是存储已经创建好的 Bean 实例,通过这个缓存可以保证得到的 Bean 是单例的

  • 二级缓存

    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

    这个缓存被称为 “二级缓存”,主要的目的是存储通过三级缓存的工厂对象直接创建的 Bean 实例

  • 三级缓存

    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    三级缓存并不直接缓存 Bean 实例,而是存储一个创建 Bean 实例的工厂对象

假设现在存在这样的关系:

@Component
public class Author {
@Resource
private Book book;
} @Component
public class Book {
@Resource
private Author author;
}

回想一下 Spring 中获取 Bean 的流程,详情可以参考:Spring IOC 容器源码分析,获取 Bean 的大概流程如下(没有代理的情况):

  1. 获取 Bean 类型的实例对象(可以理解为 new 关键字创建的对象)
  2. 填充 Bean 实例的相关属性(即 @Resource@Autowire 注解标记的属性)
  3. 应用 BeanFactory 的后置方法和注册销毁方法

首先对于实例化对象来讲,对应的执行流程如下:

值得一提的是,对于构造函数的注入来讲,如果只存在唯一的注入构造函数,那么即使不添加 @Autowired 注解同样会将这个构造函数视为注入的构造函数

在实例化 Bean 对象之后,会添加一个 ObjectFactory 到三级缓存中,对应的源码如下:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// 省略前置的相关代码
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 省略后置的相关代码
} protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory); // 将这个 ObjectFactory 工厂对象放入三级缓存
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
} protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
/*
这里的主要目的是给予需要代理的切面一个机会去代理相关的 Bean 实例,由于我们假设现在不存在代理,因此会直接返回当前的 Bean 实例对象
*/
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}

现在需要明确的是,在实例化了一个 Bean 的实例对象之后,会生成一个匿名类对象作为工厂对象放入到三级缓存中。按照上文我们给定的依赖关系,此时 Spring 容器中的缓存情况应当如下所示(我们关心的情况):

注意,在实例化 Bean 的这个过程中,只是实例化 Bean,因此 @Order 注解在这个过程中是不会生效的,Bean 的初始化顺序将会按照 beanName 的顺序进行实例化,因此按照我们上文的定义,我们关心的 Bean 中,现在只有一个创建 author Bean 的工厂对象

当处理完成 BeanFactory 之后,会填充 Bean 中相关的属性,我们熟知的 @Resource@Autowired 以及 @Inject 注解就是在这个过程中进行处理的,具体对应的源代码如下:

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
// 省略部分代码
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
}
pvs = pvsToUse;
}
// 省略部分代码
}

InstantiationAwareBeanPostProcessor 可以定义一些在实例化 Bean 之后的后置操作,这里我们主要关心的是对于字段属性注入的相关处理,因此我们主要关心 CommonAnnotationBeanPostProcessor 实现类的相关处理(处理 @Resource 的注入,对于 @Autowired 注入的处理在 AutowiredAnnotationBeanPostProcessor 中实现),具体的处理逻辑如下:

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
}
return pvs;
} // 处理 @Resource 相关的元数据信息
private InjectionMetadata findResourceMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) { // 这里也用到了缓存
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) {
metadata.clear(pvs);
}
metadata = buildResourceMetadata(clazz); // 解析 @Resource 注解处理的元数据
this.injectionMetadataCache.put(cacheKey, metadata);
}
}
}
return metadata;
} private InjectionMetadata buildResourceMetadata(Class<?> clazz) {
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz; do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
// 这一部分用于处理被 @Reource 修饰的字段(基于字段的注入)
ReflectionUtils.doWithLocalFields(targetClass, field -> {
// 省略部分不太重要的源码。。。
if (field.isAnnotationPresent(Resource.class)) { // 检查当前属性是否被 @Resource 注解修饰过
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static fields");
}
if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
currElements.add(new ResourceElement(field, field, null));
}
}
}); // 这一部分用于处理被 @Reource 修饰的所有方法(基于方法的注入)
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
// 省略部分源码
if (bridgedMethod.isAnnotationPresent(Resource.class)) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
Class<?>[] paramTypes = method.getParameterTypes();
if (paramTypes.length != 1) {
throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
}
if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new ResourceElement(method, bridgedMethod, pd));
}
}
}
}); elements.addAll(0, currElements); // 添加到此被 @Resource 修饰的字段到元数据对象
targetClass = targetClass.getSuperclass(); // 父类的递归处理
}
while (targetClass != null && targetClass != Object.class); return InjectionMetadata.forElements(elements, clazz); // 包装成统一的元数据对象
}

随后就是对相关属性的注入了,具体源码如下:

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Collection<InjectedElement> checkedElements = this.checkedElements;
/*
这里的 injectedElements 在处理 @Resource 注解时被添加到当前元数据对象,
因此这里处理的就是当前对象中所有被 @Resource 注解修饰的属性
*/
Collection<InjectedElement> elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) { //
for (InjectedElement element : elementsToIterate) {
element.inject(target, beanName, pvs);
}
}
} protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
throws Throwable {
if (this.isField) { // 这里也被称为 “基于字段的注入”,即通过反射的方式进行注入,一般我们都是通过这种方式来实现注入的
Field field = (Field) this.member;
ReflectionUtils.makeAccessible(field);
field.set(target, getResourceToInject(target, requestingBeanName));
}
else { // 基于方法的注入
if (checkPropertySkipping(pvs)) {
return;
}
try {
Method method = (Method) this.member;
ReflectionUtils.makeAccessible(method);
method.invoke(target, getResourceToInject(target, requestingBeanName));
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}

当执行到这一步的时候,Spring 就会去寻找对应的依赖项,最终在 autowireResource 方法中完成对所需 Bean 的注入:

protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)
throws NoSuchBeanDefinitionException { Object resource;
Set<String> autowiredBeanNames;
String name = element.name; if (factory instanceof AutowireCapableBeanFactory) {
AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
DependencyDescriptor descriptor = element.getDependencyDescriptor(); /*
从 BeanFactory 中查找依赖项,由于 Author Bean 优先于 Book Bean 进行初始化,因此在填充 Author Bean 的
依赖项 Book Bean 时 Spring 容器按照相同的方式对 Book Bean 进行初始化
*/
resource = beanFactory.resolveBeanByName(name, descriptor);
autowiredBeanNames = Collections.singleton(name);
}
// 省略部分源代码 return resource;
}

按照相同的策略,完成 Book Bean 的初始化,然而和 Author Bean 初始化不同的地方在于,Book Bean 中的依赖项 Author 已经在 “三级缓存” 中存在了对应的工厂类,此时的缓存情况如下图所示:

当在执行对 Book Bean 的属性注入时,从容器中获取 Author Bean 的方式将会是从 “三级缓存” 中直接获取对应的 Bean,对应的源码如下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
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) {
/*
从三级缓存中对应 Bean 的 Factory 获取对象,同时将获取到的对象放入二级缓存
*/
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}

因此,当初始化 Book Bean 的过程中处理完对应的依赖项之后,此时容器的缓存情况如下图所示:

当 Book Bean 初始化完成之后,按照相关的逻辑,会对当前 Bean 的缓存进行一次处理,主要的目的是将初始化好的 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);
}
}

因此在 Book Bean 初始化完成之后,此时的缓存情况如下所示:

同样地,初始化完成 Book Bean 之后,Author Bean 的依赖项已经被解决了,因此 Author Bean 也会完成初始化,对相关的 Bean 进行同样的缓存处理操作,最终容器中的缓存如下所示:

至此,Author Bean 和 Book Bean 全部完成初始化

总的执行流程如下图所示:

存在的问题

为什么需要三级缓存

根据上文的描述,实际上只需要使用到两个缓存即可完成对应依赖的处理,二级缓存的存在是否是多余的?

在上文的处理过程中,有一个前提条件是 “不存在代理”,当需要进行代理处理,如 AOP 等功能时,会创建对应的代理类,一般来讲,代理的处理是放在实例化 Bean 之后,放入三级缓存中(三级缓存缓存的是 Factory 对象,重新定义了 Bean 的获取逻辑),具体的 Factory 逻辑如下:

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;
}

一般 AOP 或者其它的代理方式的实现都是通过 org.springframework.aop.framework.autoproxy.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);
} protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 省略部分源代码 /*
检查当前 Bean 上存在的切面,根据需要创建对应的代理对象
*/
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

假设现在存在以下的依赖关系:

@Component
@Transactional
public class Author {
@Resource
private Book book; @Resource
private Publisher publisher;
} @Component
@Transactional
public class Book {
@Resource
private Author author; @Resource
private Publisher publisher;
} @Component
@Transactional
public class Publisher {
@Resource
private Author author; @Resource
private Book book;
}

由于现在我们加上了 @Transactional 注解,因此在从三级缓存中获取对应的 Bean 时会执行代理操作,假设我们现在只有一级缓存和三级缓存,那么当 Book Bean 和 Publisher Bean 填充 Author 的依赖项时,都会从三级缓存中通过 Factory 执行一次获取 Author Bean 的操作(注意 Author Bean 被放入一级缓存时是在构造完成之后,然而此时是在处理 Book Bean 和 Publisher Bean 的初始化的过程中),使得 Author Bean 的单例特征无法保证。而通过引入二级缓存,在首次通过三级缓存获取 Bean 时,会将获取到的 Bean 放入到二级缓存,从而维护了 Bean 实例的单例属性

基于构造函数的注入

根据上文的分析,可以得知 Spring 中对于基于字段或方法的 Bean 注入,是能够自行处理循环依赖的(后续 Spring 版本默认关闭了这个行为,因为出现这种情况很大概率是设计存在缺陷)。但是针对基于构造函数的注入来讲,情况可能不大一样

上文的分析都是在实例化对应的 Bean 对象之后进行的,基于构造函数的注入则是在构造这个 Bean 实例的时候进行处理,对应的源码如下:

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// Make sure bean class is actually resolved at this point.
Class<?> beanClass = resolveBeanClass(mbd, beanName);
// 省略部分源码 /**
* 查找被 @Autowired 注解修饰的构造函数作为构造 Bean 的构造函数,如果只存在一个构造函数,那么不管它有没有被
* @Autowired 注解修饰过,都将作为本次 Bean 的构造函数
*/
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args); // 对于依赖项的处理和基于字段的注入类似
} // Preferred constructors for default construction?
ctors = mbd.getPreferredConstructors();
if (ctors != null) {
return autowireConstructor(beanName, mbd, ctors, null);
} // No special handling: simply use no-arg constructor.
return instantiateBean(beanName, mbd);
}

问题在于,由于基于构造函数的注入发生在构造 Bean 实例对象的过程中,因此被构造的 Bean 实际上在三级缓存中是不存在对应的 Factory 对象的,这就会出现其它的 Bean 对于被构造的 Bean 的依赖无法被解决,从而抛出异常。更为具体的一点,我们依旧以下面的例子为例讲述一下这个问题

加入此时的依赖关系是下面这样:

@Component
public class Author {
@Autowired
public Author(Book book) {}
} @Component
public class Book {
@Resource
public Author author;
}

由于 Author Bean 会比 Book Bean 优先初始化(按照 Bean Name 排序,此时 @Order 注解不生效),因此当 Author Bean 在解决对 Book Bean 的依赖时,由于 Book Bean 同样也依赖于 Author Bean,但是此时 Spring 容器中并不存在 Author Bean 对应的缓存(此时 Author Bean 还在实例化的过程中),因此在解决 Book Bean 的依赖过程中就无法被解决,从而抛出异常

然而,如果是依赖关系是下面这样:

@Component
public class Author {
@Resource
private Book book;
} @Component
public class Book {
@Autowired
public Book(Author author){}
}

由于在解决 Book Bean 的依赖过程中 Author Bean 已经实例化,并且已经放入了三级缓存,因此可以解决 Book Bean 所需的依赖项(尽管此时 Book Bean 处于实例化的过程中,即缓存中不存在 Book Bean),使得循环依赖能够被解决

解决循环依赖

最好的做法是改善依赖关系,避免循环依赖的出现

在了解了相关的处理逻辑之后,我们就可以针对性的采取相关的措施解决循环以来

  1. 对相关的依赖项添加 @Lazy,如下:

    @Component
    public class Author {
    @Autowired
    public Author(Book book) {}
    } import org.springframework.context.annotation.Lazy; @Component
    public class Book {
    @Autowired
    public Book(@Lazy Author author) {
    }
    }

    通过添加 @Lazy 注解,使得当前 Book Bean 对于 Author Bean 的依赖可以被延迟处理,从而打破了循环的结构

  2. 如果可以,可以考虑使用基于字段或者方法的注入方式替换基于构造函数的注入,因为我们发现只有在基于构造函数的注入时才会导致循环依赖的产生

    Note: 由于 Spring 后来默认把循环依赖这一功能给禁用了,可以通过在配置文件中打开这个选项:

    # application.yaml
    spring:
    main:
    allow-circular-references: true

参考:

[1] https://www.baeldung.com/circular-dependencies-in-spring

[2] https://developer.aliyun.com/article/766880

[3] https://mp.weixin.qq.com/s?__biz=Mzg2MjU1ODgxMg==&mid=2247510161&idx=1&sn=9094299ff403ee6a4c207b818bd67de3

Spring 中循环依赖的处理的更多相关文章

  1. Spring 的循环依赖问题

    什么是循环依赖 什么是循环依赖呢?可以把它拆分成循环和依赖两个部分来看,循环是指计算机领域中的循环,执行流程形成闭合回路:依赖就是完成这个动作的前提准备条件,和我们平常说的依赖大体上含义一致.放到 S ...

  2. Spring的循环依赖问题

    spring容器循环依赖包括构造器循环依赖和setter循环依赖,那Spring容器如何解决循环依赖呢?首先让我们来定义循环引用类: 在Spring中将循环依赖的处理分成了3种情况: 构造器循环依赖 ...

  3. Spring之循环依赖

    转:http://my.oschina.net/tryUcatchUfinallyU/blog/287936 概述 如何检测循环依赖 循环依赖如何解决 Spring如何解决循环依赖 主要的几个缓存 主 ...

  4. 再谈spring的循环依赖是怎么造成的?

    老生常谈,循环依赖!顾名思义嘛,就是你依赖我,我依赖你,然后就造成了循环依赖了!由于A中注入B,B中注入A导致的吗? 看起来没毛病,然而,却没有说清楚问题!甚至会让你觉得你是不清楚spring的循环依 ...

  5. Spring解决循环依赖

    1.Spring解决循环依赖 什么是循环依赖:比如A引用B,B引用C,C引用A,它们最终形成一个依赖环. 循环依赖有两种 1.构造器循环依赖 构造器注入导致的循环依赖,Spring是无法解决的,只能抛 ...

  6. Spring当中循环依赖很少有人讲,今天一起来学习!

    网上关于Spring循环依赖的博客太多了,有很多都分析的很深入,写的很用心,甚至还画了时序图.流程图帮助读者理解,我看了后,感觉自己是懂了,但是闭上眼睛,总觉得还没有完全理解,总觉得还有一两个坎过不去 ...

  7. Spring的循环依赖,学就完事了【附源码】

    目录 啥是循环依赖? Spring可以解决循环依赖的条件 Spring如何去解决循环依赖 SpringBean的创建流程 Spring维护的三级缓存 getSingleton getSingleton ...

  8. Spring的循环依赖

    本文简要介绍了循环依赖以及Spring解决循环依赖的过程 一.定义 循环依赖是指对象之间的循环依赖,即2个或以上的对象互相持有对方,最终形成闭环.这里的对象特指单例对象. 二.表现形式 对象之间的循环 ...

  9. 详解Spring DI循环依赖实现机制

    一个对象引用另一个对象递归注入属性即可实现后续的实例化,同时如果两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环即所谓的循环依赖怎么实现呢属性的互相注入呢? Spring bean生命周期具体 ...

  10. Spring03-IOC-循环依赖的实现(Debug查看spring解决循环依赖的过程)

    1 什么是循环依赖 如下,有类A和B,A中有一个类型为B的属性b,B中有一个类型为A的属性a,A和B相互依赖 public class A { private B b; public B getB() ...

随机推荐

  1. 低功耗引擎 Cliptrix 有什么价值

    在万物互联的时代,现代人已普遍接受电视.音箱等电器设备具备智能化能力,也是在这个趋势下,我们身边越来越多的iOT设备联网和交互成为刚需.但iot设备也面临到一些非常显著的痛点,例如iot设备的内存.处 ...

  2. JAVA图搜索算法之DFS-BFS

    图算法DFS与BFS BFS和DFS代表对图进行遍历,即搜索的算法,搜索算法中常用的只要有两种算法:深度优先遍历(Depth-First-Search : DFS)和广度优先遍历(Breadth-Fi ...

  3. osx12.6设置全屏

    首先安装VMWARE TOOLS就是unlocker208下的tool的darwin.iso 然后进入终端命令,这里要说一下,好多资料说按COMMAND键,反正我是没有进去,直接在vmware菜单里选 ...

  4. 从零用VitePress搭建博客教程(2) –VitePress默认首页和头部导航、左侧导航配置

    2. 从零用VitePress搭建博客教程(2) –VitePress默认首页和头部导航.左侧导航配置 接上一节: 从零用VitePress搭建博客教程(1) – VitePress的安装和运行 四. ...

  5. 如何使用DALL-E 3

    如何使用 DALL-E 3:OpenAI 图像生成指南 DALL-E 3 是 OpenAI 图像生成器的高级版本,它可以理解自然语言提示来创建详细图像. 它克服了以前版本的方形图像限制,现在支持各种宽 ...

  6. YCSB对MongoDB数据库性能测试

    一.安装部署 1.1前置条件 Install Java and Maven Go to http://www.oracle.com/technetwork/java/javase/downloads/ ...

  7. win10如何美化cmd[添加新字体+配色方案+窗口栏样式]

    最近学mysql的时候用到很多cmd操作,但是cmd那默认界面实在是丑到没朋友.在网上收集了些资料最后把cmd美化成这样: 修改方法: 1.修改字体,新建一个txt文件,里面粘贴以下代码: Windo ...

  8. [Python急救站课程]Hello Kitty的绘制

    我女朋友喜欢Hello Kitty,当她知道我会用Python画图,让我给画一个,怎么说呢,当然要安排上啦!请看程序 import math import turtle as t # 计算长度.角度 ...

  9. 【re】[NISACTF 2022]string --linux下的随机数

    附件下载,查壳 发现是ELF程序,64位,ida打开分析 flag函数点进去 前面一堆代码其实都不重要,直接看主要代码:  puts("The length of flag is 13&qu ...

  10. Ubuntu部署雷池Waf社区版

    安装docker环境 更新软件包 sudo apt update 安装docker环境 apt-get install docker.io docker -v 安装docker compose V2版 ...