📌 의존성 주입이란?
Spring Boot에서 `의존성 주입(Dependency Injection, DI)`은 객체 간의 의존 관계를 자동으로 설정해주는 중요한 개념이다.
이를 통해 코드의 `결합도`를 낮추고, `테스트 용이성`과 `유지보수성`을 높일 수 있다.
의존성 주입을 통해 클래스 간의 결합도를 낮출 수 있다. 결합도가 낮아지면, 한 클래스의 변경이 다른 클래스에 미치는 영향을 줄일 수 있다. 이는 코드의 유지보수성과 확장성을 높이는 데 중요한 역할을 한다.
예를 들어, 클래스 A가 클래스 B에 직접 의존하는 경우, B의 변경사항은 A에 직접적인 영향을 미치게 된다. 하지만 의존성 주입을 사용하면, A는 B의 구체적인 구현이 아니라 인터페이스나 추상 클래스에 의존하게 되어, B의 구현 변경이 A에 미치는 영향을 최소화할 수 있다.
강한 결합의 예시 코드
public class B {
public void doSomething() {
System.out.println("Doing something in B");
}
}
public class A {
private B b;
public A() {
this.b = new B(); // 강한 결합
}
public void performAction() {
b.doSomething();
}
public static void main(String[] args) {
A a = new A();
a.performAction();
}
}
클래스 A가 클래스 B의 구체적인 구현에 직접 의존하고 있다. B의 변경사항이 A에 영향을 미치게 된다.
느슨한 결합의 예시 코드
// 인터페이스 정의
public interface Service {
void doSomething();
}
// 인터페이스 구현체 1
public class ServiceImpl implements Service {
@Override
public void doSomething() {
System.out.println("Doing something in ServiceImpl");
}
}
// 인터페이스 구현체 2
public class AnotherServiceImpl implements Service {
@Override
public void doSomething() {
System.out.println("Doing something in AnotherServiceImpl");
}
}
// 의존성 주입을 통해 느슨한 결합 구현
public class A {
private final Service service;
// 생성자 주입
public A(Service service) {
this.service = service;
}
public void performAction() {
service.doSomething();
}
public static void main(String[] args) {
// ServiceImpl 주입
Service service1 = new ServiceImpl();
A a1 = new A(service1);
a1.performAction();
// AnotherServiceImpl 주입
Service service2 = new AnotherServiceImpl();
A a2 = new A(service2);
a2.performAction();
}
}
느슨한 결합을 구현하기 위해 인터페이스를 사용하고, 의존성 주입을 통해 클래스 간의 결합도를 낮추고 있다.
클래스 A는 Service 인터페이스에 의존하고 구체적인 구현 클래스는 생성자를 통해 주입된다. A 클래스는 이제 Service 인터페이스만 의존하므로, 구체적인 구현체가 변경되더라도 A 클래스에는 영향을 미치지 않는 것이다.
📌 Spring Boot 에서 DI를 구현하는 주요 방법과 개념
💡 주요 개념
`빈(Bean)`
- 스프링 IoC 컨테이너가 관리하는 자바 객체
- 일반적으로 `@Component, @Service, @Repository, @Controller`와 같은 어노테이션을 사용하여 빈으로 등록
`IoC 컨테이너(Inversion of Control Container)`
- 스프링 프레임워크의 핵심 부분으로, `빈을 생성하고 관리`
- IoC 컨테이너는 애플리케이션 시작 시 `빈을 생성하고, 필요할 때마다 주입`
`어노테이션 기반 설정`
- 의존성 주입을 설정할 때 주로 사용되는 어노테이션 `@Autowired, @Inject, @Resource`
💡 의존성 주입 방법
1. 필드 주입
클래스의 멤버 변수에 직접 주입하는 방법
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
}
2. 생성자 주입
생성자를 통해 의존성을 주입하는 방법, 최근에는 더 권장되고 있음 (`@RequiredConstructor` 로 해도 됨!)
@Service
public class MyService {
private final MyRepository myRepository;
@Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
}
@RequiredArgsConstructor
public class CopurchasingService {
private final CopurchasingRepository copurchasingRepository;
private final UserRepository userRepository;
...
}
이런 식으로 해도 된다.
3. 세터 주입
세터 메서드를 통해 의존성을 주입하는 방법
@Service
public class MyService {
private final MyRepository myRepository;
@Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
}
💡 @Autowired
`@Autowired`
Spring에서 `@Autowired` 어노테이션은 의존성 주입을 간편하게 해주는 기능이다. 이를 통해 스프링 프레임워크가 자동으로 필요한 의존성을 주입해 준다.
`@Autowired`의 원리
스프링 IoC 컨테이너는 애플리케이션의 빈(bean)을 관리한다. 빈은 스프링 컨테이너에 의해 생성되고 관리되는 객체이다. 빈 정의는 XML 파일, 자바 설정 클래스(@Configuration, @Bean), 또는 컴포넌트 스캔(@Component, @Service, @Repository, @Controller) 등을 통해 이루어진다.
스프링 컨테이너는 애플리케이션이 시작될 때, 빈 정의를 로드하고 인스턴스를 생성한다. @Autowired 어노테이션이 붙은 필드, 생성자, 또는 세터 메서드를 찾습니다.
`@Autowired`는 기본적으로 타입 매칭을 사용한다. 스프링 컨테이너는 해당 타입의 빈을 검색하여 주입한다. 만약 해당 타입 빈이 여러 개 있을 경우, `@Qualifier` 어노테이션을 사용하여 빈 이름을 지정할 수 있다.
필드에 `@Autowired`가 붙어 있으면, 스프링은 해당 필드 타입을 확인하고, 컨테이너에서 찾아 주입한다.
생성자에 `@Autowired`가 붙어 있으면, 스프링은 해당 생성자를 사용하여 빈을 생성한다.
Spring 4.3 이전에는 생성자 주입에도 `@Autowired`를 붙여줘야 했지만 Spring 4.3 이후부터는 클래스에 생성자가 하나만 있고, 해당 생성자가 파라미터를 요구하는 경우, 스프링은 자동으로 이 생성자를 사용하여 의존성을 주입한다! 그러나 클래스에 여러 생성자가 있거나, 기본 생성자와 의존성 생성자가 동시에 존재하는 경우에는 `@Autowired` 어노테이션을 명시적으로 사용하는 것이 좋다.
동작 과정
import org.springframework.stereotype.Repository;
@Repository
public class MyRepository {
public void doSomething() {
System.out.println("Doing something...");
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MyService {
private final MyRepository myRepository;
@Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
public void performAction() {
myRepository.doSomething();
}
}
1. 컴포넌트 스캔
스프링 애플리케이션이 시작되면, @Component, @Service, @Repository, @Controller 어노테이션이 붙은 클래스들을 스캔한다!
2. 빈 등록
MyService와 MyRepository가 각각 빈으로 등록된다.
3. 의존성 주입
스프링 컨테이너는 MyService 빈을 생성할 때, 생성자에 @Autowired가 붙어 있으므로, MyRepository 타입의 빈을 찾아 주입한다. MyRepository 빈이 MyService의 생성자로 주입되어 MyService 인스턴스가 생성된다.
💡 @Autowired vs 생성자 주입
`@Autowired`를 이용한 필드 주입
필드 주입(Field Injection)은 클래스의 필드에 직접 `@Autowired` 어노테이션을 붙여 의존성을 주입하는 방법이다.
이 방법의 장점은 구현이 매우 간단하다는 점이다. 어노테이션을 필드에 추가하는 것만으로 쉽게 의존성을 주입할 수 있고, 코드가 간결하여 직관적으로 이해하기 쉽다.
그러나 필드 주입에는 몇 가지 단점이 있다. 우선, 테스트하기가 어렵다. 필드 주입으로 주입된 필드가 private일 경우, 테스트 시 리플렉션을 사용하지 않고는 쉽게 모킹할 수 없다. 또한, 필드를 final로 선언할 수 없어 해당 필드의 불변성을 보장할 수 없다. 이로 인해, 주입된 의존성이 변경될 가능성이 생기며, 이는 코드의 안정성을 떨어뜨릴 수 있다!
`생성자` 주입
생성자 주입(Constructor Injection)은 클래스의 생성자를 통해 의존성을 주입하는 방법이다.
이 방법의 주요 장점은 불변성을 보장할 수 있다는 점이다. 생성자를 통해 주입된 의존성을 final로 선언할 수 있어, 주입된 이후 변경될 가능성이 없어진다. 또한, 생성자를 통해 필요한 의존성을 명시적으로 선언하므로, 클래스가 어떤 의존성을 필요로 하는지 명확히 알 수 있다.
그러나 생성자 주입에도 단점이 있다. 의존성이 많은 경우, 생성자가 길어질 수 있고 가독성이 떨어질 수 있다.
대부분의 경우, 생성자 주입이 더 권장된다. 명시적 의존성 선언, 불변성 보장, 테스트 용이성 등 여러가지 이유에서 더 나은 선택이 된다.
📌 요약
의존성 주입(Dependency Injection, DI)
- 객체 간의 의존 관계를 자동으로 설정해주는 방법으로, 코드의 결합도를 낮추고, 테스트 용이성 및 유지보수성을 높임
느슨한 결합
- 인터페이스를 사용하여 구현체의 변경이 다른 클래스에 미치는 영향을 최소화
Spring Boot에서 DI 방법
빈(Bean): 스프링 IoC 컨테이너가 관리하는 자바 객체.
IoC 컨테이너: 빈을 생성하고 관리.
DI 방법
- 필드 주입: 클래스의 멤버 변수에 직접 주입
- 생성자 주입 (권장): 생성자를 통해 주입
- 세터 주입: 세터 메서드를 통해 주입
@Autowired 원리
- 스프링 컨테이너가 애플리케이션 시작 시 빈을 생성하고 @Autowired가 붙은 필드, 생성자, 세터 메서드를 찾아 주입
- Spring 4.3 이후, 생성자가 하나만 있을 경우 @Autowired 생략 가능
'공부 > Spring' 카테고리의 다른 글
[Spring Cloud] 클라이언트 사이드 로드 밸런싱 (FeignClient와 Ribbon) (0) | 2024.08.05 |
---|---|
[Spring Cloud] MSA Spring Cloud (Eureka) (0) | 2024.08.01 |
Spring Framework 각 계층의 역할 / 비즈니스 로직은 누구의 역할일까? (0) | 2024.07.08 |
@Entity 에 @NoArgsConstructor, @AllArgsConstructor는 언제 붙이는걸까? (0) | 2024.06.20 |
[Spring Security] JWT 로그인 흐름 이해하기 (0) | 2024.06.11 |