군만두의 IT 공부 일지

[1주차] 내일배움캠프 Spring Java 심화 부트캠프 3기 - IoC와 DI 본문

개발일지/스파르타코딩클럽

[1주차] 내일배움캠프 Spring Java 심화 부트캠프 3기 - IoC와 DI

mandus 2025. 2. 4. 11:46

 

오늘은 객체지향 책을 읽으면서 봤던 의존성 주입(DI: Dependency Injection), 제어의 역전(IoC)에 대해서 실제 코드로 학습을 진행했다. 먼저 주요 용어에 대해서 알아본다.

  • 의존성: 객체 간의 관계에서 한 객체가 다른 객체를 사용하는 상황
  • 주입: 코드에서 여러 방법을 통해 필요로 하는 객체를 해당 객체에 전달하는 것
  • 제어의 역전: 객체의 생성, 생명주기, 의존성 관리를 개발자가 아닌 프레임워크나 컨테이너가 대신 하는 디자인 원칙
@RestController
@RequestMapping("/api")
public class MemoController {

    private final MemoService memoService;

    public MemoController(JdbcTemplate jdbcTemplate) {
        this.memoService = new MemoService(jdbcTemplate);
    }

    @PostMapping("/memos")
    public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
        return memoService.createMemo(requestDto);
    }

    @GetMapping("/memos")
    public List<MemoResponseDto> getMemos() {
        return memoService.getMemos();
    }

    @PutMapping("/memos/{id}")
    public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
        return memoService.updateMemo(id, requestDto);
    }

    @DeleteMapping("/memos/{id}")
    public Long deleteMemo(@PathVariable Long id) {
        return memoService.deleteMemo(id);
    }
}
public class MemoService {

    private final MemoRepository memoRepository;

    public MemoService(JdbcTemplate jdbcTemplate) {
        this.memoRepository = new MemoRepository(jdbcTemplate);
    }

    public MemoResponseDto createMemo(MemoRequestDto requestDto) {
        Memo memo = new Memo(requestDto);
        Memo saveMemo = memoRepository.save(memo);
        MemoResponseDto memoResponseDto = new MemoResponseDto(saveMemo);

        return memoResponseDto;
    }

    public List<MemoResponseDto> getMemos() {
        return memoRepository.findAll();
    }

    public Long updateMemo(Long id, MemoRequestDto requestDto) {
        Memo memo = memoRepository.findById(id);
        if (memo != null) {
            memoRepository.update(id, requestDto);

            return id;
        } else {
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }

    public Long deleteMemo(Long id) {
        Memo memo = memoRepository.findById(id);
        if (memo != null) {
            memoRepository.delete(id);

            return id;
        } else {
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }
}

위 코드에서 MemoService와 MemoRepository 사이의 강한 결합(strong coupling)을 확인할 수 있다.

public MemoController(JdbcTemplate jdbcTemplate) {
    this.memoService = new MemoService(jdbcTemplate);
}

...


public MemoService(JdbcTemplate jdbcTemplate) {
    this.memoRepository = new MemoRepository(jdbcTemplate);
}

 

MemoService 클래스의 생성자에서 MemoRepository 인스턴스를 직접 생성하고 있다. 또한, MemoController 클래스에서도 생성자에 MemoService를 직접 생성하고 있다.

 

제어의 흐름이 MemoController → MemoService → MemoRepository로 흐르고 있다. 강한 결합은 유연성이 떨어지고, 유지보수성 및 테스트를 어렵게 만든다.

"강한 결합" 해결할 방법
1. 각 객체에 대한 객체 생성은 딱 1번만!
2. 생성된 객체를 모든 곳에서 재사용!
3. 생성자 주입을 사용하여 필요로하는 객체에 해당 객체 주입!

 

위 내용을 바탕으로 코드를 아래와 같이 수정할 수 있다. MemoController와 MemoService에서 JdbcTemplate을 MemoRepository에 주입할 필요가 없어졌다. DI를 사용하면 MemoRepository → MemoService → MemoController로 제어의 흐름이 역전된 것을 확인할 수 있다.

public MemoController(MemoService memoService) {
    this.memoService = memoService;
}

...

public MemoService(MemoRepository memoRepository) {
    this.memoRepository = memoRepository;
}

DI에서는 객체 생성이 우선된다. 이때 객체를 생성하고 관리하는 역할은 Spring 프레임워크가 대신해준다.

  • 빈 (Bean): Spring이 관리하는 객체
  • Spring IoC 컨테이너: Bean을 모아둔 컨테이너

Spring Bean을 등록하려면 @Component 애너테이션을 사용한다.

@Component
public class MemoService {
		
    @Autowired
    private MemoRepository memoRepository;
		
		// ...
}

MemoSercive 객체를 생성할 때는 카멜 케이스(첫 문자는 소문자, 다음 단어의 첫 문자는 대문자)로 이름을 짓는다.

MemoService memoService = new MemoService();

Spring Bean을 사용할 때는  @Autowired 애너테이션을 사용한다. @Autowired 애너테이션을 적용하는 것은 Spring IoC 컨테이너에 의해 관리되는 클래스에서만 가능하다. 객체의 불변성 때문에 일반적으로는 생성자를 사용하여 DI한다.

  1. 필드 위에 @Autowired
  2. Bean을 주입할 때 사용할 메서드 위에 @Autowired
@Component
@RequiredArgsConstructor // final로 선언된 멤버 변수를 파라미터로 사용하여 생성자를 자동으로 생성합니다.
public class MemoService {

    private final MemoRepository memoRepository;
    
//    public MemoService(MemoRepository memoRepository) {
//        this.memoRepository = memoRepository;
//    }

		...

}

Lombok의 @RequiredArgsConstructor를 사용하면, 생성자가 자동으로 주입되므로 생성자 생성 코드를 생략할 수 있다.

 

Comments