3가지 주입 방식이 있다.

필드 주입

@Service
public class TestService2 {
    @Autowired
    private TestService3 testService3;
    ...
}

필드 주입을 하면 순환 참조를 잡아내지 못한다는 글이 있다. 확인해보자.

@Service
public class TestService2 {
    @Autowired
    private TestService3 testService3;
    ...
}

@Service
public class TestService3 {
    @Autowired
    private TestService2 testService2;
    ...
}

위와 같은 코드로 run을 하면

순환 참조 에러

다음과 같이 빌드에서 에러를 발견할 수 있다. 예전 버전의 spring boot에선 잡아내지 못했던 것 같다.

spring boot:3.1.2 에선 필드 주입에서도 순환 참조를 방지하는 걸 볼 수 있다.

수정자 주입

@Service
public class TestService2 {
    private TestService3 testService3;

    @Autowired
    public void setTestService3(TestService3 testService3){
        this.testService3 = testService3;
    }
    ...
}

생성자 주입

@Service
public class TestService2 {
    private final TestService3 testService3;
    
    public TestService2(TestService3 testService3){
        this.testService3 = testService3;
    }
    ...
}

생성자가 하나라면 @Autowired를 명시하지 않아도 된다. 확인해보자.

@Service
public class TestService2 {
    private Object testService;
    
    public TestService2(TestService3 testService3){
        this.testService = testService3;
    }

    public TestService2(TestService1 testService1){
        this.testService = testService1;
    }

    public void sayClass(){
        System.out.println(testService.getClass());
    }
}

위와 같이 작성하고 testService2.sayClass()를 호출하면 다음과 같은 에러가 나온다.

NullPointerException: Cannot invoke "Object.getClass()" because "this.testService" is null

생성자가 2개 이상일 때 @Autowired를 달지 않으면 어떤 의존성 주입도 되지않고 null인 것을 확인할 수 있고

2개의 생성자 중 하나에 @Autowired를 달아주면 해당 생성자의 의존성이 주입되고 getClass가 잘 찍히는 것을 볼 수 있다.

생성자가 하나일 때, lombok으로 생성자 주입

@Service
@RequiredArgsConstructor
public class TestService2 {
    private final TestService3 testService3;
    ...
}

@RequiredArgsConstructor는 필수 필드(final)를 인자로 생성자를 자동 생성해준다.

@Service
public class TestService2 {
    private final TestService3 testService3;

    public TestService2(final TestService3 testService3) {
        this.testService3 = testService3;
    }
}

빌드된 코드는 위와 같다.

@Service
@RequiredArgsConstructor
public class TestService2 {
    @NonNull
    private TestService3 testService3;
    ...
}

@NonNull 어노테이션을 사용하는게 더 좋아보인다.

@Service
public class TestService2 {
    private @NonNull TestService3 testService3;

    public TestService2(final @NonNull TestService3 testService3) {
        if (testService3 == null) {
            throw new NullPointerException("testService3 is marked non-null but is null");
        } else {
            this.testService3 = testService3;
        }
    }
}

빌드된 코드는 위와 같다. final이여도 null을 인자로 넘길 수 있는데 그 부분에 대한 예외처리를 해준다.


순환 의존에 대해선 3가지 방법 모두 실행 전에 에러를 뱉는다.

기본적으로 불변성을 유지하고자 한다면 생성자 주입을 사용하고 수정이 필요하면 필드 주입을 사용하면 될 것 같다.

생성자 주입은 생성자가 여러개가 아니면 lombok을 이용한 방법이 편하고 안전해보인다.