의존성 주입 방식

 

의존성을 주입하는 방법은 4가지가 있다.

 

1. 생성자 주입(Constructor Injection)

2. setter

3. 필드 주입


생성자 주입

생성자를 통해 의존관계를 주입하는 방법이다.

 

@RestController
public class AnimalController {
    private final AnimalService animalService;
    
    @Autowired
    public AnimalController (AnimalService animalService) {
        this.animalService = animalService;
    }

    @PostMapping("/animal")
    public ResponseEntity<String> adoptAnAnimal(AdoptRequestDto requestDto) {
        return animalService.adoptAnAnimal(requestDto);
    }
}

 

정상적으로 작동

  • 생성자 호출 시점에 1번만 호출되는 것을 보장한다.
  • 주입받은 객체가 변하지 않거나 반드시 객체의 주입이 필요할 경우 강제하기 위해 사용할 수 있다.
  • 생성자가 1개만 존재하는 경우 @Autowired를 생략해도 자동주입이 된다.
  • 주입받을 필드를 불변을 보장하는 final로 선언이 가능하다.
  • NPE(NullPointException)을 방지할 수 있다.

수정자 주입

Setter를 통해서 의존관계를 주입한다.

생성자 주입과는 다르게 주입받는 객체가 변경될 가능성이 있는 경우에 사용한다.

 

@RestController
public class AnimalController {
    private AnimalService animalService;

    @Autowired
    public void setAnimalService (AnimalService animalService) {
        this.animalService = animalService;
    }

    @PostMapping("/animal")
    public ResponseEntity<String> adoptAnAnimal(AdoptRequestDto requestDto) {
        return animalService.adoptAnAnimal(requestDto);
    }
}

Autowired가 있으면 정상적으로 작동

 

Autowired가 없으면 오류가 발생하며 동작하지 않는다.
NPE가 발생한다.

  • 선택과 변경 가능성이 있는 의존관계에 사용한다.
  • 자바의 Setter로 수정자 메서드 방식을 사용한다.
  • @Autowired가 없으면 컴파일은 되지만, 실행 시 NPE가 발생한다.
  • Setter는 언제든지 변경하게될 위험이 있다.
  • 한번 주입이 일어나면 변경이 일어날 가능성이 거의 없으므로 지양한다.

필드 주입

필드에 바로 의존관계를 주입하는 방법이다.

@RestController
public class AnimalController {
    @Autowired
    private AnimalService animalService;

    @PostMapping("/animal")
    public ResponseEntity<String> adoptAnAnimal(AdoptRequestDto requestDto) {
        return animalService.adoptAnAnimal(requestDto);
    }
}

정상적으로 작동

  • 코드가 간결해진다.
  • 외부에서 변경이 불가능하여 테스트가 어려워진다.
  • DI프레임워크가 없으면 아무것도 할 수가 없다.
  • 애플리케이션의 실제 코드와 상관없는 특정 테스트를 하고 싶을 때 사용한다.

생성자방식을 사용해야 하는 이유

 

1. 순환 참조 방지

 

AnimalService

@Service
public class AnimalService {

    private UserService userService;
    
    public AnimalService(UserService userService) {
		this.userService = userService;
    }

    public ResponseEntity<String> adoptAnAnimal(AdoptRequestDto requestDto) {
        return new ResponseEntity<>("입양되었습니다.", HttpStatus.OK);
    }
}

AnimalService에서 의존성을 필드주입 방식으로 주입해 주었다.

테스트를 위해 userSerivce에서는 사용하지 않지만 예시를 위해 넣어주었다.

 

UserService

@Service
public class UserService {
   
    private AnimalService animalService;
    
    public UserService(AnimalService animalService) {
		this.animalService = animalService;
    }

    public ResponseEntity<String> createUser(CreateUserRequestDto requestDto) {
        return new ResponseEntity<>("이용자 등록이 완료되었습니다.", HttpStatus.OK);
    }
}

UserService 또한 필드 주입방식으로 AnmalService를 주입해 주었다.

 

 

실행하면  순환참조가 일어난다는 문구가 나오게 된다.

 

컴파일 단계에서 순환참조의 원인을 파악할 수 있다는 장점이 있다.

 


2. 테스트 코드 작성 용이

 

생성자 주입 적용 전

@ActiveProfiles("test")
@SpringBootTest
class AnimalServiceTest {
    @Autowired
    AnimalService animalService;

    @Test
    @DisplayName("동물 하나를 입양하면 \"입양되었습니다.\"의 응답이 나오며 HTTP.OK를 반환한다.")
    public void adoptAnimal() {
        // given
        AdoptRequestDto requestDto = new AdoptRequestDto(AnimalType.FOX, "미호", "female");

        // when
        ResponseEntity<String> response = animalService.adoptAnAnimal(requestDto);

        // then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).isEqualTo("입양되었습니다.");
    }
}

 

생성자 주입 적용

@ActiveProfiles("test")
@SpringBootTest
class AnimalServiceTest {

    @Test
    @DisplayName("동물 하나를 입양하면 \"입양되었습니다.\"의 응답이 나오며 HTTP.OK를 반환한다.")
    void adoptAnAnimal() {
        // given
        AnimalService animalService = new AnimalService();
        AnimalController animalController = new AnimalController(animalService);

        AdoptRequestDto adoptRequestDto = new AdoptRequestDto(FOX, "미호", "female");

        // when
        ResponseEntity<String> response = animalController.adoptAnAnimal(adoptRequestDto);

        // then
        assertThat(response.getBody()).isEqualTo("입양되었습니다.");
    }
}

 

mock 객체를 생성하거나 Autowired 할 필요도 없어져 테스트코드 작성이 편해졌다.


3. 불변성(Immutability)

 

필드 주입과 수정자 주입은 필드에 final을 붙일 수 없다.

따라서 초기화 한 후 빈 객체가 변경될 수 있지만, 생성자 주입은 final을 선언하여

객체의 불변성을 보장받을 수 있다.

오지랖 토끼