이 글을 준비하게 된 계기는 다름 아닌 누군가의 질문에서 시작됐다…
??? : Hey, 너 자바 좀 잘해?
나 : …모르겠네 그래도 열심히 공부는 하고 있어
??? : 너 프로젝트에서 IoC, AOP, DI 적용한 사례 설명해봐.
나 : 아 그거~~?? 쉽지 ㅋㅋㅋ (막상 못 말함)
??? : 알았어 그러면 추상화, 상속, 다형성, 캡슐화 적용한 부분 얘기해봐
나 : 상속은 DTO에서 하기도 하고… 많이 하는건데 그냥…
이게 문제다. 이론을 안다고 생각하고 프로젝트를 만들다 보면
내 코드의 설계 방식이 어떤 개념을 채택하고 있는지조차 모른 채 만들기 쉽다.
그래서! 이번 기회에 개념 기강 한 번 제대로 잡고,
지금 진행 중인 Spring Boot 프로젝트에 어떻게 적용했는지도 같이 정리해보려 한다.
Inversion of Control, 제어의 역전
객체의 생성과 의존성 관리를 개발자가 아닌 프레임워크가 담당하는 것
객체 간 의존 관계를 스프링 컨테이너가 알아서 조율한다
📌 핵심
→ 내가 객체를 new해서 쓰는 게 아니라
→ Spring이 대신 생성하고 연결해줌
2️⃣ DI란?
Dependency Injection, 의존성 주입
→ IoC를 구현하는 방법 중 하나
• 객체가 필요한 의존 객체를 직접 생성하지 않고 외부에서 주입받음
• 스프링에서는 생성자 주입을 많이 사용함
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
👆 UserService는 UserRepository에 의존하지만
직접 만들지 않고 스프링이 알아서 주입해준다.
💡 ApplicationContext란?
스프링의 IoC 컨테이너
애플리케이션 실행 시 빈(Bean)을 생성하고, 의존성 주입 등을 관리함
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
💡 Bean이란?
스프링이 관리하는 객체
@Component, @Service, @Repository, @Controller 등을 사용하면 자동 등록됨
@Bean으로 수동 등록도 가능
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService(userRepository());
}
}
// StoreController.java
@RestController
@RequestMapping("/stores")
public class StoreController {
private final StoreService storeService;
public StoreController(StoreService storeService) {
this.storeService = storeService;
}
@GetMapping
public List<Store> getAllStores() {
return storeService.findAllStores();
}
}
✅ 결합도 감소
→ 객체 간 관계가 느슨해져 확장과 변경이 쉬움
✅ 테스트 용이성
→ 의존 객체를 Mock으로 바꿔서 단위 테스트 가능
✅ 가독성 & 관리성 향상
→ 구성과 로직이 분리되어 코드가 명확해짐
의존 관계를 명확히 표현하고 유지보수하기 쉬운 구조를 만드는 데
IoC와 DI는 필수적인 개념이다.
특히 Spring Boot는 이걸 너무나도 자연스럽고 편하게 해준다.
이론만 알고 넘어가기보단,지금 내가 짜고 있는 코드에 적용된 개념을 스스로 인지하고 있어야 진짜로 쓰는 의미가 있다는 걸 이번 글을 쓰면서 다시 느끼게 됐다.
📌 다음 글 예고: 객체 지향 프로그래밍의 4가지 특징! (완전 필수)