Skip to content

Cyclic dependency with setter injection and Java Config #25443

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
JawinSoft opened this issue Jul 21, 2020 · 11 comments
Open

Cyclic dependency with setter injection and Java Config #25443

JawinSoft opened this issue Jul 21, 2020 · 11 comments
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: documentation A documentation task

Comments

@JawinSoft
Copy link

JawinSoft commented Jul 21, 2020

Hi,

I'm trying to run simple spring core with Spring Circular dependency + Setter Dependency Injection + Java Config => Getting Exception. It's working fine with XML config. but its failing with java config. I'm not able to understand what is the problem.

Attached required files to replicate the issue locally.

Spring Circular dependency + Setter Dependency Injection + Java Config => Getting Exception

Spring Circular dependency + Setter Dependency Injection + XMLConfig => Working Fine
Attached Files for debugging purpose

Exception in thread "main" java.lang.NoClassDefFoundError: org.springframework.beans.FatalBeanExceptionException in thread "main" java.lang.NoClassDefFoundError: org.springframework.beans.FatalBeanException at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
circular.zip

Related Issues

@wilkinsona
Copy link
Member

Thanks for the four Java source files. Unfortunately, they don't provide enough information to diagnose the problem. Can you please provide a complete minimal project including the XML configuration, pom.xml or build.gradle, etc that reproduces the failure that you're seeing?

@JawinSoft
Copy link
Author

JawinSoft commented Jul 21, 2020

Hi Andy Wilkinson,

PFA. Spring Boot Core Maven Project.

  1. For Xml configuration -> com.yuvintech.spring.boot.core.circular_xml.XmlMain.java
    1. For Java configuration -> 1) For Xml configuration -> com.yuvintech.spring.boot.core.circular_xml.XmlMain.java.JavaMain.java

In Both cases, I'm trying to do Circular dependencies with Setter Dependency Injection.

Please let me know if you need any more info from my end. Thank you in advance.

springboot-core-circulardependency.zip

@wilkinsona
Copy link
Member

Thanks for the complete sample. The problem is in your JavaConfig class where your oc and pp @Bean methods depend upon each other. This results in a StackOverflowError. You can fix the problem by changing JavaConfig to the following:

@Configuration
public class JavaConfig {

	@Bean
	public PaymentProcesser paymentProcessor(){
		return new PaymentProcesser(); 
	}
	
	@Bean
	public OrderConfirmation orderConfirmation() {
		return new OrderConfirmation();
	}
	
}

In addition, you need to add @Autowired to the setters on PaymentProcessor and OrderConfirmation:

@Autowired
public void setOc(OrderConfirmation oc) {
	this.oc = oc;
}
@Autowired
public void setPp(PaymentProcesser pp) {
	this.pp = pp;
}

If you have any further questions, please follow up on Stack Overflow or Gitter. As mentioned in the guidelines for contributing, we prefer to use GitHub issues only for bugs and enhancements.

@JawinSoft
Copy link
Author

JawinSoft commented Jul 21, 2020

thank you for the information. I'm not looking for the solution, just trying to understand the concept here.
How the same code is working fine with Xml config and why not on Java config. And its not mentioned on Spring documentation about this conflict.

image

@wilkinsona
Copy link
Member

Re-opening so the issue can be transferred to Spring Framework's issue tracker.

@wilkinsona wilkinsona reopened this Jul 21, 2020
@bclozel bclozel transferred this issue from spring-projects/spring-boot Jul 21, 2020
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jul 21, 2020
@sbrannen sbrannen added the in: core Issues in core modules (aop, beans, core, context, expression) label Jul 21, 2020
@sbrannen
Copy link
Member

An alternative solution exists that does not require the use of @Autowired within the component classes, as follows.

@Configuration
public class JavaConfig {

	@Bean
	public PaymentProcesser pp(OrderConfirmation oc) {
		PaymentProcesser pp = new PaymentProcesser();
		pp.setOc(oc);
		return pp;
	}

	@Bean
	public OrderConfirmation oc() {
		OrderConfirmation oc = new OrderConfirmation();
		oc.setPp(pp(oc));
		return oc;
	}
}

The main method also needs to be updated, however, since there is no bean named orderConfirmation. That can be achieved by looking up the bean solely by type, as follows.

OrderConfirmation sp = context.getBean(OrderConfirmation.class);

Note, however, that the above alternative solution would not be sufficient if the OrderConfirmation bean needs to be proxied for additional services (e.g., transactions, security, caching, etc.). This is because the raw OrderConfirmation instance is passed directly to the pp(OrderConfirmation) method.


If you attempt to reference each of the beans by type via @Bean method arguments as follows...

@Configuration
public class JavaConfig {

	@Bean
	public PaymentProcesser pp(OrderConfirmation oc) {
		PaymentProcesser pp = new PaymentProcesser();
		pp.setOc(oc);
		return pp;
	}

	@Bean
	public OrderConfirmation oc(PaymentProcesser pp) {
		OrderConfirmation oc = new OrderConfirmation();
		oc.setPp(pp);
		return oc;
	}
}

... Spring Boot will then inform you of the circular dependency as follows.

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  pp defined in class path resource [com/yuvintech/spring/boot/core/circular_java/JavaConfig.class]
↑     ↓
|  oc defined in class path resource [com/yuvintech/spring/boot/core/circular_java/JavaConfig.class]
└─────┘

thank you for the information. I'm not looking for the solution, just trying to understand the concept here.
How the same code is working fine with Xml config and why not on Java config. And its not mentioned on Spring documentation about this conflict.

Although there are viable workarounds for the example you've provided, the question still remains why there is a difference between the XML and Java configuration. The answer likely lies in the fact that the XML configuration is ultimately just metadata that Spring uses to create BeanDefinition instances, and the physical wiring of the components for the XML configuration ends up similar to the approach suggested by @wilkinsona, where the beans are instantiated separately and then wired together. Whereas, in the Java configuration you've provided, each bean is instantiated and wired within the same method, where the act of wiring results in infinite recursion between the two @Bean methods.

In any case, we'll see if there's anything that can be done, and otherwise we'll consider adding a note to the documentation for such scenarios.

@sbrannen sbrannen changed the title Spring Circular dependency + Setter Dependency Injection + Java Config => Getting Exception Circular dependency + setter injection + Java Config => exception Jul 21, 2020
@sbrannen
Copy link
Member

sbrannen commented Jul 21, 2020

If you attempt to reference each of the beans by type via @Bean method arguments as follows...

I previously said that would not work; however, it does work if you annotate one of the @Bean method arguments as @Lazy which results in a "lazy-resolution proxy" being injected into the corresponding @Bean method.

This approach can be used to circumvent circular dependency issues between @Bean methods as follows.

@Configuration
public class JavaConfig {

	@Bean
	public PaymentProcesser pp(OrderConfirmation oc) {
		PaymentProcesser pp = new PaymentProcesser();
		pp.setOc(oc);
		return pp;
	}

	@Bean
	public OrderConfirmation oc(@Lazy PaymentProcesser pp) {
		OrderConfirmation oc = new OrderConfirmation();
		oc.setPp(pp);
		return oc;
	}

}

@ELearnTez, although that does not definitively answer your question, does that meet your needs in terms of a viable solution?

@sbrannen sbrannen added type: documentation A documentation task and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jul 21, 2020
@sbrannen sbrannen self-assigned this Jul 21, 2020
@sbrannen sbrannen added this to the 5.3 RC1 milestone Jul 21, 2020
@sbrannen
Copy link
Member

In light of the above solution using @Lazy on a @Bean method parameter, we are turning this into a documentation issue to add such an example (and explanation) to the reference manual.

@JawinSoft
Copy link
Author

Thank you Much for the Consideration

@jhoeller jhoeller modified the milestones: 5.3 RC1, 5.3 RC2 Aug 24, 2020
@sbrannen sbrannen modified the milestones: 5.3 RC2, 5.3 GA Oct 13, 2020
@sbrannen sbrannen modified the milestones: 5.3 GA, 5.3.1 Oct 27, 2020
@jhoeller jhoeller modified the milestones: 5.3.1, 5.3.2 Nov 6, 2020
@sbrannen sbrannen modified the milestones: 5.3.2, 5.3.3 Dec 7, 2020
@jhoeller jhoeller removed this from the 5.3.3 milestone Jan 11, 2021
@sbrannen sbrannen changed the title Circular dependency + setter injection + Java Config => exception Cyclic dependency with setter injection and Java Config Mar 12, 2022
@sbrannen sbrannen modified the milestones: 5.3.17, 6.0.0-M4 Mar 16, 2022
@sbrannen sbrannen modified the milestones: 6.0.0-M4, 6.0.0-M5 May 10, 2022
@sbrannen sbrannen modified the milestones: 6.0.0-M5, 6.0.0-M6 Jun 7, 2022
@sbrannen sbrannen modified the milestones: 6.0.0-M6, 6.0.0-RC1 Aug 29, 2022
@sbrannen sbrannen modified the milestones: 6.0.0-RC1, 6.0.0-RC2 Oct 3, 2022
@sbrannen sbrannen modified the milestones: 6.0.0-RC2, 6.0.0 Oct 12, 2022
@sbrannen sbrannen modified the milestones: 6.0.0, 6.0.x Nov 15, 2022
@bjmi
Copy link

bjmi commented Mar 28, 2023

Is there a kind of post-configurer for Beans like the following fictional example to separate bean instantiation from initialization?

@Configuration
public class JavaConfig {

    @Bean
    public PaymentProcesser paymentProcessor(){
        return new PaymentProcesser();
    }

    @Bean
    public OrderConfirmation orderConfirmation() {
        return new OrderConfirmation();
    }

    @Autowired
    void postProcessBeans(PaymentProcesser pp, OrderConfirmation oc) {
        pp.setOc(oc);
        oc.setPp(pp);
    }

}

@sbrannen sbrannen modified the milestones: 6.0.x, 6.1.x Jul 4, 2023
@sbrannen sbrannen modified the milestones: 6.1.x, 6.1.0-RC1 Aug 19, 2023
@sbrannen sbrannen modified the milestones: 6.1.0-M5, 6.1.x, 6.1.0-RC2 Sep 13, 2023
@sbrannen sbrannen modified the milestones: 6.1.0-RC2, 6.1.x Oct 10, 2023
@jhoeller jhoeller modified the milestones: 6.1.x, General Backlog Jan 11, 2024
@sbrannen sbrannen removed their assignment May 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: documentation A documentation task
Projects
None yet
Development

No branches or pull requests

6 participants