
원래는 개발자가 필요한 객체를 선언해서 사용했지만, 이제는 IOC 컨테이너 에서 객체를 모두 만들어두고 자동으로 연결해줌.
직접 선언하면 결합도가 높다. 객체를 생성해주고 사용도 하기 때문. DI는 IOC에서 선언해두고, 설정정보를 바탕으로 필요한 경우에만 끌어다가 쓸 수 있도록 한다. 그래서 interface만 있는데 사용할 수 있는 경우도 볼 수 있다.(?)
어떤 객체가 다른 객체를 사용해야만 동작하는 관계를 말한다.
예:
class A {
private B b = new B();
}
A는 B 없이는 동작 불가 → A는 B에 의존한다.
만약 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 로 얼마든지 교체 가능
→ 결합도 낮음 → 객체 변경에 유연해짐
"이런 객체(B)를 A에 넣어줘"
라는 작업을 개발자가 new 하지 않고,
스프링 IoC 컨테이너가 대신 객체를 만들어 넣어주는 것
즉,
이런 것들을 개발자가 아니라 스프링이 자동으로 처리한다.
public class A {
private final B b;
public A(B b) {
this.b = b;
}
}
필수 의존성을 안전하게 주입할 수 있어
가장 많이 사용된다.
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
필수가 아닌 의존성(선택적) 주입에 유용.
@Autowired
private B b;
테스트하기 어렵고 결합도가 높아지기 때문에
스프링 공식 문서에서는 권장하지 않지만
Controller, Service에서 자주 보이는 패턴.
즉
MemberDTO → Account 인터페이스에 의존
실제 구현은 PersonalAccount
<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>
여기서 포인트:
<ref bean="account"/> 전달결과: 컨테이너가 MemberDTO 생성 시 자동으로 PersonalAccount를 넣어준다.
<property name="personalAccount" ref="account"/>
Setter가 호출되면서 의존성이 주입된다.
bean 등록에 사용된 메소드를 호출하여 의존성 주입을 처리할 수 있다.
@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 생성.
@Bean
public MemberDTO memberGenerator() {
MemberDTO member = new MemberDTO();
member.setPersonalAccount(accountGenerator());
return member;
}
둘 다 정상적으로 동작하지만,
생성자 주입은 “필수 의존성”을 깔끔하게 표현할 수 있어 더 권장된다.
<constructor-arg> → 생성자 주입<property> → Setter 주입@Bean 메서드의 파라미터 값 전달