[Spring Core] Dependency Injection

컨테이너·2일 전
0

SpringFramework

목록 보기
13/15
post-thumbnail

1. DI(Dependency Injection)란?

원래는 개발자가 필요한 객체를 선언해서 사용했지만, 이제는 IOC 컨테이너 에서 객체를 모두 만들어두고 자동으로 연결해줌.

  • 결합도 ↓
  • 유지보수성 ↑
  • 유연성 ↑

직접 선언하면 결합도가 높다. 객체를 생성해주고 사용도 하기 때문. DI는 IOC에서 선언해두고, 설정정보를 바탕으로 필요한 경우에만 끌어다가 쓸 수 있도록 한다. 그래서 interface만 있는데 사용할 수 있는 경우도 볼 수 있다.(?)

1-1. Dependency Injection

의존(Dependency)

어떤 객체가 다른 객체를 사용해야만 동작하는 관계를 말한다.

예:

class A {
    private B b = new B();
}

A는 B 없이는 동작 불가 → A는 B에 의존한다.


문제: 결합도(Coupling)가 높다

만약 B 클래스 이름이 바뀌면?

class NewB { }

A 안의 코드도 수정해야 한다.

A가 B의 구체 구현에 직접 묶여있다. → 결합도가 높다 → 유지보수 어려움


해결: 상위 타입 + 외부 주입

public class A {
    private B b; // 인터페이스

    public A(B b) {  // 외부에서 넣어줌
        this.b = b;
    }
}

public interface B {}
public class NewB implements B {}

A는 B라는 추상 타입(인터페이스로 틀만 잡아줌)만 알고 있고 실제 구현체(NewB)는 외부에서 넣어줌.

→ A 수정 없이 NewB → OtherB 로 얼마든지 교체 가능

→ 결합도 낮음 → 객체 변경에 유연해짐


DI(의존성 주입)가 하는 일

"이런 객체(B)를 A에 넣어줘"

라는 작업을 개발자가 new 하지 않고,

스프링 IoC 컨테이너가 대신 객체를 만들어 넣어주는 것

즉,

  • 객체 생성
  • 의존성 연결
  • 초기화

이런 것들을 개발자가 아니라 스프링이 자동으로 처리한다.


2. DI 방식 3가지

2-1. 생성자 주입 (가장 권장)

public class A {
    private final B b;

    public A(B b) {
        this.b = b;
    }
}

필수 의존성을 안전하게 주입할 수 있어

가장 많이 사용된다.


2-2. Setter 주입

public class A {
    private B b;

    public void setB(B b) {
        this.b = b;
    }
}

필수가 아닌 의존성(선택적) 주입에 유용.


2-3. 필드 주입 (Spring Boot 자주 사용하지만 권장 X)

@Autowired
private B b;

테스트하기 어렵고 결합도가 높아지기 때문에

스프링 공식 문서에서는 권장하지 않지만

Controller, Service에서 자주 보이는 패턴.


3. 실습 예시에서의 DI 구조

3-1. 구조 설명

  • Account : 인터페이스
  • PersonalAccount : Account 구현체
  • MemberDTO : Account 타입을 필드로 가짐 (의존)

MemberDTO → Account 인터페이스에 의존

실제 구현은 PersonalAccount


4. XML 설정으로 DI 하기

4-1. 생성자 주입

<bean id="account" class="com.daniel.common.PersonalAccount">
    <constructor-arg index="0" value="20"/>
    <constructor-arg index="1" value="110-234-567890"/>
</bean>

<!-- 1. 생성자 주입 -->
<bean id="member" class="com.daniel.common.MemberDTO">
<constructor-arg name="sequence" value="1"/>
<constructor-arg name="name" value="홍길동"/>
<constructor-arg name="phone" value="010-1234-5678"/>
<constructor-arg name="email" value="hong123@gmail.com"/>
<constructor-arg name="personalAccount">
<ref bean="account"/>
</constructor-arg>
</bean>
  • 생성된 다른 bean을 의존성에 주입할 경우 ref속성을 사용한다.

여기서 포인트:

  • MemberDTO는 Account(인터페이스)를 의존
  • XML에서는 구현체(PersonalAccount)를 bean으로 등록해야 함. 인터페이스를 사용할 수 없다.
  • MemberDTO 생성자 인자로 <ref bean="account"/> 전달

결과: 컨테이너가 MemberDTO 생성 시 자동으로 PersonalAccount를 넣어준다.


4-2. Setter 주입

<property name="personalAccount" ref="account"/>

Setter가 호출되면서 의존성이 주입된다.


5. Java 설정으로 DI 하기

bean 등록에 사용된 메소드를 호출하여 의존성 주입을 처리할 수 있다.

5-1. 생성자 주입

@Bean
public Account accountGenerator() {
    return new PersonalAccount(20, "110-234-567890");
}

@Bean
public MemberDTO memberGenerator() {
    return new MemberDTO(1, "홍길동", "010...", "hong@gm...", accountGenerator());
}

→ memberGenerator() 실행 시

accountGenerator() 결과(PersonalAccount)를 넣어서 Bean 생성.


5-2. Setter 주입

@Bean
public MemberDTO memberGenerator() {
    MemberDTO member = new MemberDTO();
    member.setPersonalAccount(accountGenerator());
    return member;
}

둘 다 정상적으로 동작하지만,

생성자 주입은 “필수 의존성”을 깔끔하게 표현할 수 있어 더 권장된다.


6. DI 전체 흐름 요약

DI란?

  • 객체 A가 객체 B를 사용할 때
  • A가 B를 직접 new 하지 않고
  • 스프링이 대신 만들어 A에 넣어주는 것

왜 필요한가?

  • 결합도 낮추기
  • 유지보수 유리
  • 객체 교체 용이
  • 테스트 용이

DI 방법 3가지

  1. 생성자 주입 (권장)
  2. Setter 주입
  3. 필드 주입 (Controller/Service에서 단순 패턴, 테스트는 불편)

XML DI 방식

  • <constructor-arg> → 생성자 주입
  • <property> → Setter 주입

Java DI 방식

  • @Bean 메서드의 파라미터 값 전달

정리

1. 객체 간 결합도를 낮추기 위해 DI를 사용한다

2. MemberDTO는 Account에 의존 → 구현체는 PersonalAccount

3. 스프링이 객체 생성 + 의존관계 연결을 자동으로 처리

4. DI 방식: 생성자 주입 > Setter 주입

5. XML/Java 설정 둘 다 의존성은 “ref 또는 메서드 호출”로 연결


profile
백엔드

0개의 댓글