17 Haziran 2019 Pazartesi

Circular Dependency

Giriş
Hata şöyle
Requested bean is currently in creation: Is there an unresolvable circular reference?
Çözümler
Çözümler şöyle
1. Refactoring ile iki sınıfa bir ortak arayüz eklemek
2. Bir sınıfı @Lazy kullanır hale getirmek
3. Field Injection kullanmak. Bu çözümde @Lazy kullanmak zorunda değiliz.
4. Setter Injection kullanmak. Bu çözümde @Lazy kullanmak zorunda değiliz.
3. @PostConstruct anotasyonu kullanmak

1. Refactoring 
Örnek
Elimizde şöyle bir kod olsun. Bu kod Circular Dependency olduğu için çalışmaz
@Service
public class Order {
  private Customer customer;

  public Order(Customer customer) {
    this.customer = customer;
  }
}

@Service
public class Customer {
  private Order order;

  public Customer(Order order) {
    this.order = order;
  }
}
Düzeltmek için şöyle yaparız. Burada User diye yeni bir arayüz yaratıldı ve Customer bu arayüzden kalıtıyor. Order sınıfı da arayüzü kullanıyor
public interface User {}

@Service
public class Order {
  private User user;

  public Order(User user) {
    this.user = user;
  }
}

@Service
public class Customer implements User {
  private Order order;

  public Customer(Order order) {
    this.order = order;
  }
}
Örnek - Kodları Ortak Bir Sınıfa Taşımak
Elimizde şöyle bir kod olsun. Bu kod Circular Dependency olduğu için çalışmaz
@Service
public class Payment {
  private Order order;

  public Payment(Order order) {
    this.order = order;
  }
}

@Service
public class Order {
  private Payment payment;

  public Order(Payment payment) {
    this.payment = payment;
  }
}
Düzeltmek için şöyle yaparız. Burada PaymentService diye yeni bir sınıf yaratıldı ve ortak kodlar buraya taşındı
@Service
public class Payment {
  private PaymentService paymentService;

  public Payment(PaymentService paymentService) {
    this.paymentService = paymentService;
  }
}

@Service
public class Order {
  private PaymentService paymentService;

  public Order(PaymentService paymentService) {
    this.paymentService = paymentService;
  }
}

@Service
public class PaymentService {
  private OrderRepository orderRepository;

  public PaymentService(OrderRepository orderRepository) {
    this.orderRepository = orderRepository;
  }
}
2. @Lazy Hale Getirmek

Bu kullanımda hem A hem de B bean'i halen yaratılır. Ancak Spring A bean'ine B için bir tane proxy verir.

Dolayısıyla bu kullanım yerine önce Field Injection veya Setter Injection tercih edilebilir. Açıklaması şöyle.
Circular dependencies
If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.
For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container 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.
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 being fully initialized itself (a classic chicken/egg scenario).
Örnek - @Lazy Field Injection
Elimizde şöyle bir kod olsun. Bu kod Circular Dependency olduğu için çalışmaz
@Service
public class UserService {
  private EmailService emailService;

  public UserService(EmailService emailService) {
    this.emailService = emailService;
  }
}

@Service
public class EmailService {
  private UserService userService;

  public EmailService(UserService userService) {
    this.userService = userService;
  }
}
Düzeltmek için şöyle yaparız
@Service
public class UserService {
  @Lazy
  @Autowired
  private EmailService emailService;

  public void doSomething() {
    emailService.sendEmail();
  }
}

@Service
public class EmailService {
  private UserService userService;

  public EmailService(UserService userService) {
    this.userService = userService;
  }

  public void sendEmail() {
    userService.doSomethingElse();
  }
}
Örnek 
@Lazy Constructor Injection
Şöyle yaparız.
@Autowired
public AService(@Lazy BService bservice) {
    this.bservice = bservice;
}
3. Field Injection
Bu kullanımda hem A hem de B bean'i halen yaratılır. Ancak Spring alanlara değeri sonra atadığı için sorun olmuyor. Yani B sınıfın constructor'ına breakpoint koyarsak bu metodda çıktığımızda this.a alanın null olduğunu görürüz. Field'a değer daha sonra atanır.

Eğer field List<A> olsaydı da Field Injection çalışırdı.

Örnek
Örnekte @Lazy kullanılmış ancak kullanmak zorunda değiliz. Şöyle yaparız.
@Service
class BService{

  @Lazy
  @Autowired
  AService aservice
}
4. Setter Injection
Bu kullanımda hem A hem de B bean'i halen yaratılır. Ancak Spring setter metodları sonra çağırdığı için sorun olmuyor. Yani B sınıfın constructor'ına breakpoint koyarsak bu metodda çıktığımızda this.a alanın null olduğunu görürüz. Field'a değer daha sonra atanır.

Örnek
Elimizde şöyle bir kod olsun. Bu kod Circular Dependency olduğu için çalışmaz
@Service
public class ClassA {
  private ClassB b;

  public ClassA(ClassB b) {
    this.b = b;
  }
}

@Service
public class ClassB {
  private ClassA a;

  public ClassB(ClassA a) {
    this.a = a;
  }
}
Düzeltmek için şöyle yaparız
@Service
public class ClassA {
  private ClassB b;

  public ClassA() {}

  @Autowired
  public void setB(ClassB b) {
    this.b = b;
  }
}

@Service
public class ClassB {
  private ClassA a;

  public ClassB() {}

  @Autowired
  public void setA(ClassA a) {
    this.a = a;
  }
}
5. @PostConstruct Anotasyonu Kullanmak
Örnek ver

Hiç yorum yok:

Yorum Gönder