diff --git a/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-collaborators.adoc b/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-collaborators.adoc index 2d660fc01162..bb8f03703a5e 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-collaborators.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-collaborators.adoc @@ -377,10 +377,40 @@ beans for classes A and B to be injected into each other, the Spring IoC contain detects this circular reference at runtime, and throws a `BeanCurrentlyInCreationException`. -One possible solution is to edit the source code of some classes to be configured by -setters rather than constructors. Alternatively, avoid constructor injection and use -setter injection only. In other words, although it is not recommended, you can configure -circular dependencies with setter injection. +A circular dependency is often a sign that responsibilities are not cleanly separated. +The preferred solution is to refactor the design to remove the cycle -- for example, by +extracting the shared logic into a third component on which both classes depend. + +If the cycle cannot be avoided, you have a few options for retaining constructor +injection: + +* Annotate one of the constructor parameters with + xref:core/beans/classpath-scanning.adoc#beans-factorybeans-annotations-lazy-injection-points[`@Lazy`]. + Spring then injects a lazy-resolution proxy instead of the fully initialized bean. The + proxy must not be invoked during construction, since doing so would resolve the target + bean too early and re-create the cycle. +* Inject an {spring-framework-api}/beans/factory/ObjectProvider.html[`ObjectProvider`] + or a xref:core/beans/standard-annotations.adoc#beans-inject-named[`Provider`] for one + side of the cycle and retrieve the collaborating bean on demand. + +Alternatively, you can edit the source code of some classes to be configured by setters +rather than constructors, or avoid constructor injection and use setter injection only. +In other words, although it is not recommended, you can configure circular dependencies +with setter injection. + +With `@Bean`-based configuration, a circular dependency between `@Bean` methods can be +broken by adding `@Lazy` to one of the method parameters, as the following example +shows: + +include-code::./CircularDependencyConfiguration[tag=snippet,indent=0] + +In the preceding example, `PaymentProcessor` is an interface, which allows Spring to use +a JDK dynamic proxy without requiring a subclass of a concrete class. Spring injects the +lazy-resolution proxy instead of resolving the `PaymentProcessor` bean immediately when +it creates `OrderConfirmation`. The target bean is resolved when the proxy is first used, +subject to the same constraint described above. See +xref:core/beans/classpath-scanning.adoc#beans-factorybeans-annotations-lazy-injection-points[Lazy Injection Points] +for more details. Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to diff --git a/framework-docs/src/main/java/org/springframework/docs/core/beans/dependencies/beansdependencyresolution/CircularDependencyConfiguration.java b/framework-docs/src/main/java/org/springframework/docs/core/beans/dependencies/beansdependencyresolution/CircularDependencyConfiguration.java new file mode 100644 index 000000000000..f008797e45d6 --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/core/beans/dependencies/beansdependencyresolution/CircularDependencyConfiguration.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.core.beans.dependencies.beansdependencyresolution; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; + +// tag::snippet[] +@Configuration(proxyBeanMethods = false) +public class CircularDependencyConfiguration { + + @Bean + PaymentProcessor paymentProcessor(OrderConfirmation orderConfirmation) { + DefaultPaymentProcessor paymentProcessor = new DefaultPaymentProcessor(); + paymentProcessor.setOrderConfirmation(orderConfirmation); + return paymentProcessor; + } + + @Bean + OrderConfirmation orderConfirmation(@Lazy PaymentProcessor paymentProcessor) { + OrderConfirmation orderConfirmation = new OrderConfirmation(); + orderConfirmation.setPaymentProcessor(paymentProcessor); + return orderConfirmation; + } +} + +interface PaymentProcessor { + + void processPayment(); +} +// end::snippet[] + +class DefaultPaymentProcessor implements PaymentProcessor { + + private OrderConfirmation orderConfirmation; + + void setOrderConfirmation(OrderConfirmation orderConfirmation) { + this.orderConfirmation = orderConfirmation; + } + + @Override + public void processPayment() { + // Process the payment. + } +} + +class OrderConfirmation { + + private PaymentProcessor paymentProcessor; + + void setPaymentProcessor(PaymentProcessor paymentProcessor) { + this.paymentProcessor = paymentProcessor; + } + + void confirmOrder() { + this.paymentProcessor.processPayment(); + } +} diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/dependencies/beansdependencyresolution/CircularDependencyConfiguration.kt b/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/dependencies/beansdependencyresolution/CircularDependencyConfiguration.kt new file mode 100644 index 000000000000..ca8b2f8ea2a0 --- /dev/null +++ b/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/dependencies/beansdependencyresolution/CircularDependencyConfiguration.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.core.beans.dependencies.beansdependencyresolution + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Lazy + +// tag::snippet[] +@Configuration(proxyBeanMethods = false) +class CircularDependencyConfiguration { + + @Bean + fun paymentProcessor(orderConfirmation: OrderConfirmation): PaymentProcessor { + return DefaultPaymentProcessor().apply { + this.orderConfirmation = orderConfirmation + } + } + + @Bean + fun orderConfirmation(@Lazy paymentProcessor: PaymentProcessor): OrderConfirmation { + return OrderConfirmation().apply { + this.paymentProcessor = paymentProcessor + } + } +} + +interface PaymentProcessor { + + fun processPayment() +} +// end::snippet[] + +class DefaultPaymentProcessor : PaymentProcessor { + + lateinit var orderConfirmation: OrderConfirmation + + override fun processPayment() { + // Process the payment. + } +} + +class OrderConfirmation { + + lateinit var paymentProcessor: PaymentProcessor + + fun confirmOrder() { + paymentProcessor.processPayment() + } +}