Циклические зависимости в Spring
В этой статье мы обсудим одну из самых важных концепций Spring, т.е. круговую зависимость. Здесь мы поймем, что такое циклическая зависимость в Spring и как мы можем решить проблемы с циклической зависимостью в Spring.
Что такое циклическая зависимость?
В программной инженерии циклическая зависимость — это отношение между двумя или более модулями, которые прямо или косвенно зависят друг от друга для правильного функционирования. Давайте попробуем понять это определение, прокрутив его с помощью Spring. Предположим, есть bean-компонент A, который зависит от другого bean-компонента B, и bean-компонент B также зависит от bean-компонента A, как показано ниже:
Bean A → Bean B → Bean A
Итак, это все об определении, и мы также поняли, что такое циклическая зависимость. Но вы все должны думать о том, как это может вызвать проблемы весной. Давайте разбираться. Всякий раз, когда контекст Spring создает bean-компоненты, читая файл конфигурации, т.е. метаданные. Он пытается создать bean-компоненты в порядке, необходимом для их полной работы. Давайте возьмем пример, чтобы понять это
Bean A → Bean B → Bean C
Здесь, как вы видите, Bean A нужен Bean B, а Bean B нужен Bean C. Итак, здесь Spring создаст bean C, затем создаст bean B (и внедрит в него bean C), затем создаст bean A (и вставит bean B в Это). Но, как мы обсуждали ранее, предположим, что существует компонент A, который зависит от другого компонента B, и компонент B также зависит от компонента A, как показано ниже.
Bean A → Bean B → Bean A
Здесь, как вы можете видеть, существует циклическая зависимость, и Spring не сможет решить, какой из bean-компонентов должен быть создан первым, поскольку они зависят друг от друга. Когда Spring столкнулся с проблемой такого типа, он вызвал исключение BeanCurrentlyInCreationException при загрузке контекста.
Note: We have to remember this one thing that spring context throw BeanCurrentlyInCreationException while loading context when we use constructor-based dependency injection. If we use any other dependency injection method then spring will not throw this exception in case of circular dependency.
Теперь мы поняли круговую зависимость и проблему с круговой зависимостью в Spring. Итак, давайте разберемся с этим, написав небольшой код. Давайте определим два bean-компонента, которые зависят друг от друга (через внедрение конструктора):
Java
package com.gfg.technicalscripter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class BeanA { private BeanB beanB; @Autowired public BeanA(BeanB beanB) { this .beanB = beanB; } } |
Java
package com.gfg.technicalscripter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class BeanB { private BeanA beanA; @Autowired public BeanB(BeanA beanA) { this .beanA = beanA; } } |
Java
package com.gfg.technicalscripter; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan (basePackages = { "com.gfg.technicalscripter" }) public class AppConfig { } |
Java
package com.gfg.technicalscripter; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class App { public static void main(String[] args) { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig. class ); } } |
Если мы попытаемся запустить этот тест, мы получим следующее исключение: BeanCurrentlyInCreationException : Ошибка при создании bean-компонента с именем beanA: запрошенный bean-компонент находится в процессе создания: существует ли неразрешимая циклическая ссылка?
Обходные пути: Теперь мы столкнулись с проблемой, и каждый из вас будет думать, как мы можем решить эту проблему. Сейчас мы попробуем некоторые из самых популярных способов решения этой проблемы.
- Используйте @Lazy: мы предполагаем, что вы знаете об аннотации @Lazy и о том, как она работает. Итак, с помощью аннотации @Lazy мы можем решить проблему. Мы можем сказать Spring лениво инициализировать один из bean-компонентов. Внедренный bean-компонент будет полностью создан только тогда, когда он впервые понадобится, и во время создания bean-компонента он внедряет прокси-компонент в качестве зависимости.
Java
package com.gfg.technicalscripter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @Component public class BeanA { private BeanB beanB; @Autowired public BeanA( @Lazy BeanB beanB) { this .beanB = beanB; } } |
Теперь, когда мы запустим проект и Spring загрузит контекст, он не будет генерировать исключение BeanCurrentlyInCreationException.
- Используйте инъекцию Setter/Field: вместо внедрения зависимостей на основе конструктора в контексте круговой зависимости мы должны использовать внедрение зависимостей на основе Setter, как это предлагается в документации Spring. Таким образом, Spring создает bean-компоненты, но зависимости не внедряются до тех пор, пока они не потребуются.
Java
package com.gfg.technicalscripter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class BeanA { BeanA() { System.out.println( "BeanA constructor called !!!" ); } private BeanB beanB; @Autowired public void setBeanB(BeanB beanB) { System.out.println( "Setting property beanB of BeanA instance" ); this .beanB = beanB; } } |
Java
package com.gfg.technicalscripter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class BeanB { BeanB() { System.out.println( "BeanB constructor called !!!" ); } private BeanA beanA; @Autowired public void setBeanA(BeanA beanA) { System.out.println( "Setting property beanA of BeanB instance" ); this .beanA = beanA; } } |
В случае внедрения зависимостей на основе сеттера Spring создает bean-компонент, сначала вызывая конструктор, а затем внедряя зависимости с помощью методов сеттера. Итак, здесь Spring не будет вызывать исключение BeanCurrentlyInCreationException, поскольку Spring будет иметь требуемый объект во время внедрения зависимости. В Spring есть много способов справиться с циклическими зависимостями. Рекомендуемый метод решения проблем циклической зависимости — использование инъекций сеттера. Но есть и другие альтернативы, которые ограничивают Spring полной инициализацией bean-компонентов с зависимостью одновременно, используя разные стратегии.