자바는 객체지향 언어이다. 사용할 객체를 여러개 생성해서 다른 클래스에서 해당 클래스를 사용한다.
스프링을 사용해서 개발하다보면 이런 객체들을 많이 사용하게 되는데, 객체들이 여러개 생기다 보면 꼬이기 마련이다.
어떤 클래스에서 사용하는 객체들과의 관계가 복잡해지기 때문이다.
아래 예시를 보자.
// DI가 없는 경우의 문제점 예시
class Engine {
public void start() {
System.out.println("엔진이 시동됩니다.");
}
}
class Car {
private Engine engine; // Car가 Engine에 의존
public Car() {
// Car 내부에서 직접 Engine 객체를 생성
this.engine = new Engine();
}
public void drive() {
engine.start();
System.out.println("차가 출발합니다.");
}
}
public class Main {
public static void main(String[] args) {
Car myCar = new Car();
myCar.drive();
}
}
한 객체가 다른 객체를 사용할 때, 그 사용되는 객체를 의존성 이라 부른다.
위 코드에서는 Car 클래스가 Engine 클래스를 사용하는데, 이 때 Car 은 Engine 에 의존한다고 정의한다.
해당 코드는 문제가 생길 가능성이 높다.
1. 강한 결합도
- Car 클래스 내부에서 Engine 객체를 직접 new 로 생성한다. ( this.engine = new Engine(); )
- 이는 Car 이 Engine 에 강하게 묶여있다는 것을 의미한다.
- 만약 Engine 내부 코드가 바뀐다거나, Engine 이 다른 의존성을 가지게 된다면 Car 클래스도 함께 수정해줘야 한다.
2. 재사용성
- Car은 오직 Engine 타입의 객체만 사용할 수 있다. 다른 클래스의 객체를 선언하지 않았기 때문이다.
- Car 에 Engine 대신 ElectricMotor 같은 클래스를 사용하고 싶어도, Car 클래스를 수정해줘야 한다.
3. 테스트의 어려움
- Car 을 테스트 할때, 실제 Engine 객체가 필요하다.
- Engine 에 문제가 생기면 당연히 Car 도 문제가 생기며, Engine 의 동작방식을 제어하기 어렵다.
DI (Dependency Injection) 은 이를 해결해준다.
DI는 이러한 문제를 해결하기 위해 객체 자신이 의존하는 객체를 직접 생성하거나 찾는 대신, 외부에서 의존 객체를 주입해주는 방식이다. 즉 너가 필요한 객체는 내가 만들어서 줄게! 라고 말하는 것 과 동일하다.
장점 (Engine 을 인터페이스로 생성했을 경우)
1. 약해진 결합도
- Car 은 Engine 을 직접 생성하지 않고, 외부로부터 주입받게 된다.
- Car은 어떤 Engine 이 주입될지 알 필요가 없고, 단순히 Engine 인터페이스에만 의존하게 된다.
2. 높은 재사용성 및 확장성
- Car은 Engine 인터페이스에만 의존하므로, GasolineEngine, DiselEngine 등 Engine 인터페이스를 구현하는
어떤 객체든 주입받아 사용할 수 있다.
- Car 코드를 변경할 필요 없이 다양한 엔진을 장착할 수 있게 된다.
3. 용이한 테스트
- Car 을 테스트 할 때, 실제 Engine 대신 테스트 목적으로 만든 가짜 Engine 을 주입할 수 있다.
- 이를 통해 Car 자체의 로직만 독립적으로 테스트 할 수 있어, 테스트의 효율성이 올라간다.
그러면 DI 는 스프링에서 어떤식으로 사용하면 될까?
1. 스프링 Ioc 컨테이너 생성 및 빈으로 등록 요청
- 스프링은 Ioc 컨테이너(Bean Factory, ApplicationContext)를 통해 객체의 생성, 생명주기 관리, 그리고 의존성 주입을 담당한다.
- 컨테이너는 환경이다. 그래서 개발자들은 빈을 만들 클래스들에게 어노테이션을 붙여, 스프링에게 이 클래스는 앞으로 빈으로 관리해줘! 라고 말해줘야 한다.
- @Component, @Service, @Repositorty, @Controller, @RestController 전부 해당 클래스는 빈으로 관리해줌을 명시하는 어노테이션이다.
@RestController // 이 부분이 해당 컨트롤러를 스프링에게 빈으로 관리해주라는 뜻
@RequestMapping("/api/boards")
@RequiredArgsConstructor
public class BoardController {
2. 빈으로 관리하게 된 클래스에 의존성을 주입시킨다.
- 해당 빈이 필요로 하는 다른 빈들을 스프링 컨테이너가 찾아서 연결해준다.
- 우리는 new 키워드를 사용해서 의존 객체를 직접 만들거나 찾을 필요가 없어진다.
@RestController // 이 부분이 해당 컨트롤러를 스프링에게 빈으로 관리해주라는 뜻
@RequestMapping("/api/boards")
@RequiredArgsConstructor // 이 부분이 의존성 주입을 간결하게 선언할 수 있도록 돕는 도구이다.
public class BoardController {
private final BoardService boardService; // 의존성 주입
@RequiredArgsConstructor 덕분에 우리는 서비스 객체를 의존성 주입할때, 별다른 생성자를 별도로 만들지 않아도 된다.
3. 의존성을 주입시키고, 의존성 객체를 코드에서 사용한다.
// 게시글 작성
@PostMapping
public ResponseEntity<Long> addBoard(@ModelAttribute BoardDto.Create boardCreate) throws IOException {
return ResponseEntity.ok(boardService.createBoard(boardCreate));
}
- 해당 클래스는 주입받은 의존성 객체의 메소드를 마치 자신의 멤버처럼 자유롭게 호출하여 사용 가능하다.
- BoardController 클래스의 addBoard 메소드는 boardService의 createBoard 라는 메소드를 사용하여 비즈니스 로직을 호출한다.
이 흐름은 기존의 강한 결합도, 낮은 응집도, 어려운 테스트 등을 효율적으로 해결해준다.
'Spring' 카테고리의 다른 글
| [Spring] @SpringBootApplication 은 어떤 역할을 할까? (0) | 2025.05.26 |
|---|---|
| 프록시 패턴과 AOP에 대해 연관성 (0) | 2025.05.25 |
| [Spring] Spring 프레임워크란 무엇일까? (2) | 2025.05.20 |
| [Spring] ResponseEntity 와 RESTful API 설계에 대해 (0) | 2025.05.19 |
| [JPA] JPA 에 대해 (0) | 2025.05.13 |