Циклические зависимости в Spring

Опубликовано: 22 Февраля, 2023

В этой статье мы обсудим одну из самых важных концепций 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-компонентов с зависимостью одновременно, используя разные стратегии.