spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.26k stars 37.98k forks source link

Cyclic dependency with setter injection and Java Config #25443

Open JawinSoft opened 4 years ago

JawinSoft commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

Hi Andy Wilkinson,

PFA. Spring Boot Core Maven Project.

1) For Xml configuration -> com.yuvintech.spring.boot.core.circular_xml.XmlMain.java 2) 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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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

sbrannen commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

Thank you Much for the Consideration

sbrannen commented 3 years ago

Related Issues

bjmi commented 1 year ago

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

}