챕터 5: 의존성 주입 (DI)와 빈 관리
스프링부트 애플리케이션에서 의존성 주입(Dependency Injection, DI)은 객체 간의 의존 관계를 관리하고, 이를 통해 코드의 유연성과 테스트 가능성을 높이는 중요한 개념입니다. 이 장에서는 DI의 기본 개념, 스프링부트에서의 구현, 주요 어노테이션의 역할과 사용 예, 그리고 다양한 주입 방법을 다룹니다.
5.1 DI 개념과 스프링부트에서의 사용
5.1.1 DI의 기본 개념
의존성 주입은 객체가 직접 의존 객체를 생성하지 않고, 외부에서 주입받는 설계 패턴입니다. 이를 통해 객체 간의 결합도를 낮추고, 코드의 재사용성을 높일 수 있습니다.
- 의존성: 클래스가 다른 클래스의 기능을 필요로 하는 관계
- 주입: 필요한 의존성을 외부에서 제공하는 행위
배경과 역사
DI의 개념은 1990년대에 등장했으며, 소프트웨어 디자인 패턴 중 하나인 Inversion of Control (IoC)의 구현 방식 중 하나로 발전했습니다. IoC는 객체가 자신이 사용할 객체를 직접 생성하지 않고 외부에서 주입받아 사용하는 방식으로, 이를 통해 모듈 간의 결합도를 낮출 수 있습니다.
기존 구조 대비 DI의 이점
- 결합도 감소: 클래스 간의 직접적인 의존성을 줄여 코드의 결합도를 낮춥니다.
- 유연성 증가: 객체 간의 관계를 쉽게 변경할 수 있어 코드의 유연성이 증가합니다.
- 테스트 용이성: 모의 객체(Mock Object)를 주입할 수 있어 단위 테스트가 용이해집니다.
- 재사용성 향상: 의존성 주입을 통해 재사용 가능한 코드를 작성할 수 있습니다.
5.1.2 스프링부트에서의 DI 구현
스프링부트에서는 스프링 컨테이너가 빈(Bean)을 관리하며, 필요한 의존성을 자동으로 주입합니다. 이는 주로 어노테이션을 사용하여 구현됩니다.
5.2 @Component, @Service, @Repository
5.2.1 주요 어노테이션의 역할과 사용 예
- @Component: 일반적인 스프링 빈을 정의하는 데 사용됩니다.
- @Service: 비즈니스 로직을 담은 서비스 클래스에서 사용됩니다.
- @Repository: 데이터 접근 계층(DAO) 클래스에서 사용됩니다.
예제: @Component 사용
package com.example.demo;
import org.springframework.stereotype.Component;
// @Component 어노테이션을 사용하여 이 클래스를 스프링 빈으로 등록
@Component
public class MyComponent {
// 메시지를 반환하는 메서드
public String getMessage() {
return "Hello from MyComponent!";
}
}
예제: @Service 사용
package com.example.demo;
import org.springframework.stereotype.Service;
// @Service 어노테이션을 사용하여 이 클래스를 서비스 빈으로 등록
@Service
public class MyService {
// 서비스 메시지를 반환하는 메서드
public String getServiceMessage() {
return "Hello from MyService!";
}
}
예제: @Repository 사용
package com.example.demo;
import org.springframework.stereotype.Repository;
// @Repository 어노테이션을 사용하여 이 클래스를 리포지토리 빈으로 등록
@Repository
public class MyRepository {
// 리포지토리 메시지를 반환하는 메서드
public String getRepositoryMessage() {
return "Hello from MyRepository!";
}
}
5.3 @Autowired와 생성자 주입
5.3.1 필드 주입과 생성자 주입 비교
스프링부트에서 의존성을 주입하는 방법에는 필드 주입(Field Injection)과 생성자 주입(Constructor Injection)이 있습니다.
필드 주입 예제
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
// @Component 어노테이션을 사용하여 이 클래스를 스프링 빈으로 등록
@Component
public class FieldInjectionExample {
// @Autowired 어노테이션을 사용하여 필드에 의존성을 주입
@Autowired
private MyService myService;
// 메시지를 출력하는 메서드
public void printMessage() {
// 주입된 MyService 빈의 메서드를 호출
System.out.println(myService.getServiceMessage());
}
}
생성자 주입 예제
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
// @Component 어노테이션을 사용하여 이 클래스를 스프링 빈으로 등록
@Component
public class ConstructorInjectionExample {
// 주입받을 의존성 필드 정의
private final MyService myService;
// @Autowired 어노테이션을 사용하여 생성자에 의존성을 주입
@Autowired
public ConstructorInjectionExample(MyService myService) {
this.myService = myService;
}
// 메시지를 출력하는 메서드
public void printMessage() {
// 주입된 MyService 빈의 메서드를 호출
System.out.println(myService.getServiceMessage());
}
}
5.3.2 장단점 분석
- 필드 주입
- 장점: 코드가 간결하고, 설정이 쉽습니다.
- 단점: 테스트하기 어렵고, 순환 의존성 문제를 야기할 수 있습니다.
- 생성자 주입
- 장점: 의존성이 명확하게 드러나며, 테스트가 용이합니다. 또한, 순환 의존성 문제를 예방할 수 있습니다.
- 단점: 의존성이 많은 경우 생성자 코드가 길어질 수 있습니다.
5.4 @Qualifier 사용법
5.4.1 여러 빈이 있을 때 선택하는 방법
스프링부트에서는 동일한 타입의 빈이 여러 개 있을 때, 어떤 빈을 주입할지 명시적으로 지정할 수 있습니다. 이를 위해 @Qualifier 어노테이션을 사용합니다.
예제: @Qualifier 사용
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
// @Component 어노테이션을 사용하여 이 클래스를 스프링 빈으로 등록
@Component
public class QualifierExample {
// 주입받을 의존성 필드 정의
private final MyService myService;
// @Autowired와 @Qualifier 어노테이션을 사용하여 특정 빈을 주입
@Autowired
public QualifierExample(@Qualifier("myServiceImpl1") MyService myService) {
this.myService = myService;
}
// 메시지를 출력하는 메서드
public void printMessage() {
// 주입된 MyService 빈의 메서드를 호출
System.out.println(myService.getServiceMessage());
}
}
5.4.2 @Primary와의 비교
@Primary 어노테이션을 사용하면 기본적으로 주입될 빈을 지정할 수 있습니다.
예제: @Primary 사용
package com.example.demo;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
// @Service 어노테이션을 사용하여 이 클래스를 서비스 빈으로 등록
@Service
@Primary // 기본적으로 주입될 빈으로 지정
public class MyPrimaryService implements MyService {
@Override
public String getServiceMessage() {
return "Hello from MyPrimaryService!";
}
}
예시 설명
- DI의 기본 개념:
- 의존성 주입은 객체 간의 결합도를 낮추고 코드의 유연성을 높이는 설계 패턴입니다.
- 스프링부트에서는 스프링 컨테이너가 빈을 관리하고, 의존성을 자동으로 주입하여 DI를 구현합니다.
- DI의 배경은 1990년대에 등장한 Inversion of Control (IoC) 개념에서 비롯되었습니다. IoC는 객체가 자신의 의존성을 직접 관리하지 않고 외부에서 주입받는 방식을 말합니다.
- 기존 구조 대비 DI의 이점은 결합도 감소, 유연성 증가, 테스트 용이성, 재사용성 향상 등입니다.
- 주요 어노테이션의 역할과 사용 예:
- @Component, @Service, @Repository 어노테이션은 각각 일반 빈, 서비스 클래스, 데이터 접근 계층 클래스를 정의하는 데 사용됩니다.
- 이 어노테이션을 사용하면 스프링 컨테이너가 해당 클래스를 빈으로 관리합니다.
- @Component는 일반 빈을 정의하는 데 사용되며, @Service는 비즈니스 로직을 담은 서비스 클래스에, @Repository는 데이터 접근 계층 클래스에 사용됩니다.
- 필드 주입과 생성자 주입 비교:
- 필드 주입은 코드가 간결하지만 테스트하기 어려운 반면, 생성자 주입은 의존성이 명확하게 드러나고 테스트하기 용이합니다.
- 생성자 주입을 사용하면 순환 의존성 문제를 예방할 수 있습니다.
- 필드 주입은 주로 간단한 테스트나 프로토타입 코드에서 사용되지만, 생성자 주입은 더 안정적이고 유지보수하기 좋은 코드를 작성하는 데 권장됩니다.
- @Qualifier 사용법과 @Primary와의 비교:
- @Qualifier를 사용하면 동일한 타입의 여러 빈 중 특정 빈을 선택하여 주입할 수 있습니다.
- @Primary를 사용하면 기본적으로 주입될 빈을 지정할 수 있으며, @Qualifier를 통해 특정 빈을 명시적으로 선택할 수 있습니다.
- @Primary는 프로젝트의 기본 구현을 설정할 때 유용하며, @Qualifier는 특정한 상황에서 다른 구현이 필요할 때 사용됩니다.
이 장에서는 스프링부트에서 의존성 주입과 빈 관리의 다양한 방법을 다루었습니다. 추가된 예제는 각 주입 방식의 사용법과 장단점을 이해하는 데 도움이 됩니다. DI의 역사와 배경, 그리고 각 어노테이션과 개념의 연관성에 대한 설명을 통해 스프링부트의 DI를 더 깊이 이해할 수 있습니다. 또한, 과거와 현재의 사용 방식의 변화를 통해 DI가 소프트웨어 개발에서 중요한 역할을 하고 있음을 알 수 있습니다.
'IT 강좌(IT Lectures) > SpringBoot' 카테고리의 다른 글
7강. 데이터 접근 (0) | 2024.07.18 |
---|---|
6강. 컨트롤러(Controller)와 라우팅(Routing) (0) | 2024.07.09 |
4강. 애플리케이션 구성 및 실행 (0) | 2024.06.26 |
3강. 스프링부트 프로젝트 생성 (0) | 2024.06.24 |
2강. 개발 환경 설정 (0) | 2024.06.21 |