【问题标题】:Spring circular dependency even though injection through setters is usedSpring 循环依赖,即使使用通过 setter 注入
【发布时间】:2019-09-04 16:35:01
【问题描述】:

我对 Spring 循环依赖项有一个我不理解的特定问题。我已经编译了一个简单的示例来重现该问题并类似于我的大型应用程序:

@Configuration
@ImportResource("classpath:my-spring-config.xml")
public class DeleteMeSpringTest {

    @Test
    public void test() {
        AnnotationConfigApplicationContext appContext = new AnnotationConfigApplicationContext("extremeSpecial");
        appContext.register(DeleteMeSpringTest.class);

        MainUIFrame master = appContext.getBean(MainUIFrame.class);
        System.out.println(master.toString());
    }

    public static class DialogProvider {
        private JComponent component;

        public void setComponent(final JComponent master) {
            this.component = master;
        }
    }

    @Component(value = "nestedUIComponent")
    public static class SomeNestedUIView extends JComponent {
        private DialogProvider dialogProvider;

        @Autowired
        public void setDialogProvider(final @Qualifier("nestedDialogProvider") DialogProvider dialogProvider) {
            this.dialogProvider = dialogProvider;
        }
    }

    @Component(value = "mainUIFrame")
    public static class MainUIFrame extends JComponent {
        private final SomeNestedUIView compA;
        private final DialogProvider mainDialogProvider;

        public MainUIFrame(final SomeNestedUIView compA, final @Qualifier("mainDialogProvider") DialogProvider dialogProvider) {
            this.compA = compA;
            this.mainDialogProvider = dialogProvider;
        }
    }

}

以下 XML bean 定义也存在:

<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 class="extremeSpecial.DeleteMeSpringTest$DialogProvider" id="mainDialogProvider">
        <property name="component" ref="mainUIFrame"/>
    </bean>

    <bean class="extremeSpecial.DeleteMeSpringTest$DialogProvider" id="nestedDialogProvider">
        <property name="component" ref="nestedUIComponent"/>
    </bean>
</beans>

查看代码应该实现以下循环结构:mainDialogProvider -> MainUIFrame -> mainDialogProvider。但是我不明白为什么 Spring 无法解决循环依赖:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'mainUIFrame': Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mainDialogProvider' defined in class path resource [my-spring-config.xml]: Cannot resolve reference to bean 'mainUIFrame' while setting bean property 'component'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'mainUIFrame': Requested bean is currently in creation: Is there an unresolvable circular reference?

at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:732)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:197)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1267)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1124)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:99)
at extremeSpecial.DeleteMeSpringTest.test(DeleteMeSpringTest.java:25)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mainDialogProvider' defined in class path resource [my-spring-config.xml]: Cannot resolve reference to bean 'mainUIFrame' while setting bean property 'component'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'mainUIFrame': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:378)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:110)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1602)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1354)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:572)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:251)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1135)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1062)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:818)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:724)
... 36 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'mainUIFrame': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:367)
... 50 more

我原以为通过使用 setter 注入(或 XML 中的属性),Spring 应该能够首先在没有属性的情况下创建 bean,然后再使用 setter 注入循环依赖项。对于更简单的示例(例如,此处演示:https://stackoverflow.com/a/49188520/606513),它的工作原理正是如此,但是我的示例中的某些东西会导致它中断,我需要找出那是什么!

请注意,通过避免使用 XML 文件(并且不需要两个不同的 DialogProvider 实例),Spring 可以毫无问题地解决主循环!

【问题讨论】:

  • 异常消息可以提供帮助。
  • @davidxxx 我添加了异常
  • 对不起,我以为你真的错过了这里的循环依赖,但你希望解决它,等等让我试试看:-)
  • 你用的是什么Spring版本?
  • @MartinvanWingerden 在生产系统 spring-boot 2.0.4 中,对于这个测试,spring-boot 2.0.4 引入的依赖项相同,但只是直接使用 ApplicationContext 而不使用 spring-boot -> spring-context 5.0.8

标签: java spring


【解决方案1】:

我在最新的 Spring Boot 中测试了您的代码,它工作正常,我所做的唯一更改是用 @Bean 替换 xml 配置并移动设置器,xml-beans 是有效的构造函数自动装配,因此导致了问题:

 @Bean
    public MainFrameDialogProvider mainDialogProvider(MainUIFrame master) {
        return new MainFrameDialogProvider(master, "mainDialogProvider");
    }

    @Bean
    public MainFrameDialogProvider nestedDialogProvider(SomeNestedUIView master) {
        return new MainFrameDialogProvider(master, "nestedDialogProvider");
    }

    public static class MainFrameDialogProvider {
        private final Object master;
        private final String nestedDialogProvider;

        public MainFrameDialogProvider(Object master, String nestedDialogProvider) {
            this.master = master;
            this.nestedDialogProvider = nestedDialogProvider;
        }
    }

    @Component
    public static class SomeNestedUIView {
        private MainFrameDialogProvider dialogProvider;

        @Autowired
        public void setMainFrameDialogProvider(@Qualifier("nestedDialogProvider") MainFrameDialogProvider dialogProvider) {
            this.dialogProvider = dialogProvider;
        }
    }

    @Component
    public static class MainUIFrame {
        private SomeNestedUIView compA;
        private MainFrameDialogProvider mainFrameDialogProvider;

        @Autowired
        public void setCompA(SomeNestedUIView compA) {
            this.compA = compA;
        }

        @Autowired
        public void setMainFrameDialogProvider(@Qualifier("mainDialogProvider") MainFrameDialogProvider mainFrameDialogProvider) {
            this.mainFrameDialogProvider = mainFrameDialogProvider;
        }
    }
}

使用 spring-boot 2.1.7

编辑:我的解决方案并没有忽略有两个不同实例的事实:

【讨论】:

  • 感谢您的回答 - 我不得不承认这是可行的,但您刚刚在我的最小样本中找到了一条捷径。我将更新上面的代码,使其更接近于我的生产问题,在这种情况下,您的解决方案将不起作用(至少我看不出它会如何)。
  • 确实如此,但只要您将MainFrameDialogProvider#master 更改为JFrame 并拥有SomeNestedUIViewMainUIFrame 扩展JFrame 然后使nestedDialogProvider 得到SomeNestedUIView组件再次遇到麻烦。我认为您的解决方案忽略了两个 MainFrameDialogProvider 具有不同依赖关系的事实,如我的示例所示。感谢您的努力(!!),它就像非常接近,但仍然不是正确的解决方案:-/
  • 我建议你 fork 我的 repo github.com/martinvw/stackoverflow-demo-57792571 并在本地尝试一下,你最后的评论我不清楚。如果您能够用我的代码重现问题,请随时 ping 我。
  • 抱歉不清楚,请查看我在 fork 中重现问题的更改:github.com/sischnei/stackoverflow-demo-57792571
  • 当然因为只有MainUIFrame 使用了setter 而SomeNestedUIView 还没有...更新了我的帖子...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-29
  • 1970-01-01
  • 2019-10-18
  • 1970-01-01
相关资源
最近更新 更多