728x90

https://susuhan.notion.site/Spring-DI-6113d9eefba446c99413d1323abe9276

- 이쁘게 보기 -

 

Spring DI 방법론

글의 목적 필드 의존성 주입과 setter 의존성 주입, 생성자 의존성 주입에 관한 차이를 알아가기

susuhan.notion.site

Spring DI 방법론

A. 문제의 발단

코드 컨밴션이 존재하지 않아, 프로젝트 내에 코드의 일관성이 일치하지 않는 현상이 발생하였다.
그 중 하나로 의존성 주입하는 방식이 거론되어, 의존성 주입 방식의 차이와 실제 오류가 발생하는지를 알아보게 되었다.

의존성 주입 방식에는 크게 3가지가 존재한다.

  1. 필드 의존성
  2. 생성자 의존성
  3. setter 의존성

1. 필드 의존성 주입 (Field Injection)

public class A {
    @Autowired
    private AService aService;
}

2. setter 의존성 주입 (Setter based Injection)

public class A {
    private AService aService;

    @Autowired
    public void setAService(AService aService){
        this.aService = aService;
    }
}

3. 생성자 의존성 주입 (Constructor based Injection)

// 의존성 주입 대상이 한 개일 경우, 생성자에 @Autowired 어노테이션을 붙이지 않아도 된다.
public class A {
    private final AService aService;

    public A(AService aService){
        this.aService = aService;
    }
}

// 의존성 주입 대상이 2개이상인 경우, 생성자에 @Autowired 어노테이션을 붙여야 한다.
public class A {
    private final AService aService;
    private final BService bService;

    @Autowired
    public A(AService aService, BSerivce bService){
        this.aService = aService;
        this.bService = vService;
    }
}

B. 차이점: Bean 주입 순서

1. 필드 의존성 주입

  1. 주입 받으려는 빈의 생성자를 호출하여, 빈을 찾거나 빈 팩토리에 등록한다.
  2. 생성자 인자를 사용하는 빈을 찾거나 만든다.
  3. 필드에 주입한다.

2. Setter 의존성 주입

  1. 주입받으려는 빈의 생성자를 호출하여, 빈을찾거나 빈 팩토리에 등록한다.
  2. 생성자의 인자를 사용하는 빈을 찾거나 만든다.
  3. 주입하려는 빈 객체의 수정자(setter)를 호출하여 주입한다.

🟥 필드, Setter 의존성 방식은 런타임에서 의존성을 주입하여, 의존성을 주입하지 않아도 객체가 생성될 수 있다.

3. 생성자 의존성 주입

  1. 생성자의 인자에 사용되는 빈을 찾거나 빈 팩토리에서 만든다.
  2. 찾은 파라미터 빈으로 주입하려는 생성자를 호출한다.

🟥 객체가 생성되는 시점에 빈을 주입하여, 의존성 주입되지 않아 발생할 수 있는 NullPointerException을 방지한다.

C. 순환 참조

A에서 B를 호출하고, B에서 A를 호출하는 관계를 순환 참조라 한다. 이런 경우가 발생하는 일은 한 프로젝트 내에 관리해야하는 클래스가 많아지는 경우 실수로 발생하게 된다.

@Service
public class Aservice {
    @Autowired
    private Bservice bservice;
}

@Service
public class Bservice {
    @Autowired
    private Aservice aservice;
}
  1. 필드와 Setter 방식에서는 빈이 생성된 후에 참조를 하기 때문에 애플리케이션이 아무런 오류나 경고없이 구동된다. 실제 코드가 호출될 때까지 알 수 없다는 의미이다.
  2. 생성자를 통해 의존성 주입할 경우 BeanCurrentlyInCreationException이 발생한다. 순환참조뿐만 아니라 의존 관계에 내용을 외부로 노출 시킴으로 애플리케이션이 실행하는 시점에서 오류를 확인할 수 있다.

D. 코드의 깔끔함

1. 필드 의존성 주입

@Service
public class Aservice {
    @Autowired
    private Bservice bservice;
}

장점: 한 필드에 어노테이션을 붙임으로써 생성자를 만들지 않아 코드의 수가 줄어든다.

단점: 필드가 많아질 경우 한 줄씩 어노테이션이 추가적으로 붙게 된다.

2.생성자 주입과 Loombok

매번 생성자를 만들어서 주입하는 것이 코드의 깔끔함을 저해시킨다. 하지만 Lombok을 이용한다면 생성자를 자동으로 만들어준다.

@RequiredArgsConstructor
@Service
public class Aservice {
    private final Bservice bservice;
}

RequiredArgsConstructor는 final로 선언된 필드를 가지고 생성자를 만들어준다.

E. final 사용

필드나 setter 방식을 이용시에 final 키워드를 사용할 수 없다.

final 키워드 사용 시 런타임 상에서 의존성이 변경되는 가능성을 제거해준다.

F. 실제 코드 실행 시 발생 상황 관찰하기

@Service
public class A {

    @Autowired
    B b;

}

@Service
public class B {

    @Autowired
    A a;
}

@Service
public class A {

    private final B b;

    public A(B b) {
        this.b = b;
    }
}
@Service
public class B {

    private final A a;

    public B(A a) {
        this.a = a;
    }
}

오류가 발생하지 않았다.

앞에서 한 말이 거짓말일까?

2.6.0 버전 특징

기본적으로 순환참조가 발생하지 않는다.

spring.main.allow-circular-references=true

필드 주입 = 속성 추가후 발생 시 순환참조 오류가 발생하지 않고 그대로 동작한다.

결론

  1. 2.6.0 버전 이후부터는 순환참조 오류를 미리 잡아주게 되었다. 순환참조 상의 오류때문에 필드, set방식을 안 쓸 이유는 없다.
  2. 생성자 의존성 주입 방식의 이점은 런타임 상에서 bean 객체가 변경되지 않는 것을 선호한다는 이유만 남은 것 같다. 하지만 런타임 상에서 bean 객체를 의도적으로 변경하는 사람이 존재할까? 하지만 이런 생각은 이전에 순환참조를 안 만들면 된다는 생각과 동일하기 때문에, 간단하게 막을 수 있다면 막아두는 것이 좋다고 생각한다.
  3. 코드의 깔끔함에 있어서 사람들의 견해가 다르다고 생각한다.
    누군가는 어노테이션을 사용함에 있어 깔끔하다고 생각하고, 누군가는 어노테이션이 없는 것에 깔끔함을 느낄 수 있다.
    만약에 생성자 주입이 직접 설정해야하는 번거로움이 걱정된다면 final이 있는 필드만 생성자를 만들어주는 @RequiredArgsConstructor를 사용하면 될 것 같다.
  4. set방식도 구현하는 방식만 다르지 필드 의존성 주입과의 단점과 동일하여 위에 논한 것으로 충분하다고 생각한다.
반응형

+ Recent posts