1

I have a specific problem with Spring circular dependencies that I do not understand. I have compiled a simple example that reproduces the issue and resembles my big application:

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

}

The following XML bean definition exists as well:

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

Looking at the code the following cyclic structure should be achived: mainDialogProvider -> MainUIFrame -> mainDialogProvider. However I don't understand why Spring is unable to resolve the cyclic dependency:

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

I would have thought that by using setter injection (or properties in the XML) Spring should be able to create the bean first without the property and then later on inject the cyclic dependencies using the setter. For simpler examples (e.g. demonstrated here: https://stackoverflow.com/a/49188520/606513) it works exactly that way, however something in my example causes it to break and I need to find out what that is!

Note that by avoiding the XML file (and without the need for two different DialogProvider instances), the main cycle is resolved without issues by Spring!

schneida
  • 729
  • 3
  • 11
  • 37
  • 1
    The exception message can help. – davidxxx Sep 04 '19 at 16:36
  • @davidxxx I added the exception – schneida Sep 05 '19 at 05:25
  • Sorry, I thought you really missed the cyclic dependency here, but you hoped to work around it, wait let me try something :-) – Martin van Wingerden Sep 05 '19 at 05:42
  • What Spring version are you using? – Martin van Wingerden Sep 05 '19 at 05:49
  • @MartinvanWingerden in the production system spring-boot 2.0.4, for this test the same dependencies brought in by spring-boot 2.0.4 but just using ApplicationContext directly without spring-boot -> spring-context 5.0.8 – schneida Sep 05 '19 at 05:51
  • My answer/code still works when running it locally when with your spring-boot-version, could you validate? – Martin van Wingerden Sep 05 '19 at 05:54
  • @MartinvanWingerden thanks for the solution, I can confirm that it does work - however you made me aware that my example wasn't complete and that I needed the XML config to provide two instances of the DialogProvider (using different components). I have now updated the example and error trace to show that issue. If you can adapt your solution that would be great! – schneida Sep 05 '19 at 06:13

1 Answers1

1

I tested your code in the latest Spring Boot and it works fine, the only changes I made were replacing the xml configuration with @Bean and moving around the setters, the xml-beans were effectively constructor autowired so caused the problems:

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

Used spring-boot 2.1.7

edit: my solution does not ignore the fact that there are two different instances:

enter image description here

  • Thanks for your answer - and I have to admit that works, but you just found a shortcut in my minmal sample. I will update my code above to have it resemble my production problem more closely, in which case your solution won't work (at least I don't see how it would). – schneida Sep 05 '19 at 06:03
  • It does but as soon as you change the `MainFrameDialogProvider#master` to be `JFrame` and have `SomeNestedUIView` and `MainUIFrame` extend `JFrame` and then make the `nestedDialogProvider` get a `SomeNestedUIView` component it again runs into trouble. I think your solution ignores the fact that the two `MainFrameDialogProvider` have different dependencies as shown in my example. Thanks for your effort (!!) and it's like very close, but still not the correct solution :-/ – schneida Sep 05 '19 at 06:39
  • 1
    I suggest you fork my repo https://github.com/martinvw/stackoverflow-demo-57792571 and try it out locally, your last comment is not clear to me. If you are able to reproduce the problem with my code feel free to ping me. – Martin van Wingerden Sep 05 '19 at 06:50
  • Sorry for not being clear, see my changes that reproduce the issue in my fork: https://github.com/sischnei/stackoverflow-demo-57792571 – schneida Sep 05 '19 at 06:58
  • Of course because only `MainUIFrame` used setters and `SomeNestedUIView` not yet... updated my post... – Martin van Wingerden Sep 05 '19 at 07:04
  • It does work now, at least the example - thanks! I'm still not quite happy with the solution, as it requires changing a lot of my ctor injections into setters, whereas the new component (the DialogProvider) uses ctor injection - I would have prefered it the other way around as originally intended in my example. – schneida Sep 05 '19 at 07:13