[스프링] 빈 주입 방식, DI, 의존 관계 주입

June·2021년 7월 29일
0

의존성

정의

클래스의 연관 관계

class Person {
    private Animal animal;

    public String getPetName() {
        return animal.name();
    }
}

이 경우 Person 객체는 Animal에 의존하고 있다. animal의 로직이 흐르기 때문이다. 의존성 주입클래스가 가지고 있는 연관관계를 주입해주는 것이다.

필요성

객체를 실제 코드흐름에 생성하는 것이 아닌 외부에 생성된 객체를 가져와 사용하는 방식으로 클래스간의 결합도를 낮춰 재사용성을 높인다.

객체 지향 설계의 5가지 원칙

  • DIP (Dependency Inversion Principle)
    "구체화에 의존하지 않고 추상화에 의존해야 한다"
    만약 의존성을 주입하지 않으면 의존 클래스가 바뀌면 코드를 바꿔줘야한다. 하지만 만약 추상화한 인터페이스를 의존하고 있으면 코드를 변경할 필요가 없다.

  • OCP (Open Closed Principle)
    "확장에는 열려있으나 변경에는 닫혀 있어야 한다"
    어플리케잇녀의 사용 영역과 구성 영역을 나눈다. 즉 위와 마찬가지로 소프트웨어 요소를 확장해도 사용 영역의 변경은 닫혀있다.

의존 관계 주입 방법

  • 생성자 주입
  • 수정자 주입(setter 주입)
  • 필드 주입

필드 주입

@Component
public class OrderServiceImpl implements OrderService {

    @Autowired
    private MemberRepository memberRepository;
    @Autowired
    private DiscountPolicy discountPolicy;
}

이름 그대로 필드에 바로 주입하는 방법이다.

특징

  • 코드가 간결해서 많은 개발자들을 유혹하지만 외부에서 변경이 불가능해서 테스트 하기 힘들다는 치명적인 단점이 있다.

  • DI 프레임워크가 없으면 아무것도 할 수 없다.

  • 스프링 설정으로 목적으로 하는 Configuration에서만 제한적으로 사용하자.

Setter 주입 (수정자 주입)

setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다.

특징

  • 선택, 변경 가능성이 있는 의존관계에 사용

  • 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법이다.

  • 생성자 방식은 빈 객체를 생성하는 시점에 필요한 모든 의존 객체를 주입 받기 때문에 객체를 사용할 때 완전한 상태로 사용할 수 있다. 하지만 세터 메서드 방식은 세터 메서드를 사용해서 필요한 의존 객체를 전달하지 않아도 빈 객체가 생성되기 때문에 객체를 사용하는 시점에 NullPointerException이 발생할 수 있다.

. 수정자 주입을 이용하면 불필요하게 수정의 가능성을 열어두게 되며, 이는 OOP의 5가지 개발 원칙 중 OCP(Open-Closed Principal, 개방-폐쇄의 법칙)를 위반하게 된다. 그러므로 생성자 주입을 통해 변경의 가능성을 배제하고 불변성을 보장하는 것이 좋다.

생성자 주입

빈 객체를 생성하는 시점에 모든 의존 객체가 주입된다.

@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy
discountPolicy) {
        this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
    }
}

특징

  • 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다.

  • 불변, 필수 의존관계에 사용

  • 또한 Spring 프레임워크에서는 생성자 주입을 적극 지원하고 있기 때문에, 생성자가 1개만 있을 경우에 @Autowired를 생략해도 주입이 가능하도록 편의성을 제공하고 있다.

  • 생성자 주입을 사용하면 필드 객체에 final 키워드를 사용할 수 있으며, 컴파일 시점에 누락된 의존성을 확인할 수 있다. 의존 관계가 실행 중에 동적으로 변하는 경우는 거의 없다. 즉 불변성을 보장해주는 효과도 있다.

  • 순환 참조 에러 방지: 애플리케이션 구동 시점(객체의 생성 시점)에 순환 참조 에러를 방지할 수 있다. 예를 들어 UserServiceImpl의 register 함수가 memberService의 add를 호출하고, memberServiceImpl의 add함수가 UserServiceImpl의 register 함수를 호출한다면 어떻게 되겠는가? 위의 두 메소드는 서로를 계속 호출할 것이고, 메모리에 함수의 CallStack이 계속 쌓여 StackOverflow 에러가 발생하게 된다.만약 이러한 문제를 발견하지 못하고 서버가 운영된다면 어떻게 되겠는가? 해당 메소드의 호출 시에 StackOverflow 에러에 의해 서버가 죽게 될 것이다. 하지만 생성자 주입을 이용하면 이러한 순환 참조 문제를 방지할 수 있다. 애플리케이션 구동 시점(객체의 생성 시점)에 에러가 발생하기 때문이다. 그러한 이유는 Bean에 등록하기 위해 객체를 생성하는 과정에서 다음과 같이 순환 참조가 발생하기 때문이다.

0개의 댓글