[Spring] DI와 IoC
DI와 IoC에 대해 아는 만큼 설명해주실 수 있을까요?
DI(Dependency Injection)
DI란 스프링이 다른 프레임워크와 차별화되어 제공하는 의존 관계 주입기능으로 객체를 직접 생성하는게 아니라 외부에서 생성한 후 주입시켜주는 방식이다.
DI(의존성 주입)을 통해서 모듈 간의 결합도가 낮아지고 유연성이 높아집니다.
IoC(Inversion of Control)
IoC(Inversion of Control)란 "제어의 역전"이라는 의미로, 말 그대로 메소드나 객체의 호출작업을 개발자가 결정하는 것이 아니라 외부에서 결정되는것을 의미한다.
객체의 의존성을 역전시켜 객체 간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 하여 가독성 및 코드 중복, 유지 보수를 편하게 할 수 있게 한다.
IoC(Inversion of Control)
IoC(Inversion of Control)란 "제어의 역전"이라는 의미로, 말 그대로 메소드나 객체의 호출작업을 개발자가 결정하는 것이 아니라 외부에서 결정되는것을 의미한다. Spring에서 loC Container인 ApplicationContext에서는 bean.xml 또는 annotation을 이용하여 객체를 관리한다.
객체의 의존성을 역전시켜 객체 간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 하여 가독성 및 코드 중복, 유지 보수를 편하게 할 수 있게 한다.
예제)
객체와 인스턴스
과일 중 수박과 복숭아로 예제를 들어보자
수박, 복숭아는 실체화가 가능함으로 object(객체) 이고 실체화가 되기 때문에 모두 instance(인스턴스)이다.
public void one(){
복숭아 s = new 복숭아();
}
public void user(){
복숭아 s = new 복숭아();
}
복숭아 객체를 heap이라는 메모리 공간에 올리게 되면 복숭아를 (new) 실체화해서 s를 메모리에 저장
하지만 one 메서드의 s와 user 메서드의 s는 다른 복숭아가 된다.
복숭아 객체들을 따로 메서드에서 만들어서 메모리에 올리는 것이 아니라 스프링이 스캔하여 메모리에 자동으로 올라가고 스프링이 관리한다. 위에서 말한
"객체의 호출작업을 개발자가 결정하는 것이 아니라 외부에서 결정되는것을 의미한다. " 이 성립되게 된다.
그렇다면 확장해보자.
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
Member member = new Member(1L, "memberA", Grade.VIP);
memberService.join(member);
Member findMember = memberService.findMember(1L);
System.out.println("member = " + member.getName());
System.out.println("findMember = " + findMember.getName());
}
우리는 이미 main() 메소드로 시작하여 개발자가 코드를 작성하고 작성한 코드의 흐름에 따라 객체가 생성되고 실행된다.
MemberService 객체를 생성하고 Member 객체를 생성하고 join() 메서드에 멤버객체를 넣어준다. (이하 생략) 개발자가 짠 코드의 흐름대로 흘러가고 있지 않은가
"객체의 직접 객체를 결정하고 호출작업을 개발자가 직접 결장하고 있다."
의존관계 주입 DI(Dependency Injection)
DI란 스프링이 다른 프레임워크와 차별화되어 제공하는 의존 관계 주입기능으로 객체를 직접 생성하는게 아니라 외부에서 생성한 후 주입시켜주는 방식이다.
DI는 IoC의 핵심 요소이며 제어의 반전이 일어날때 스프링 내부에 있는 객체들간의 관계를 관리할때 사용하는 기법이다.
DI는 의존성 주입을 의미하며 이는 특정 객체에 필요한 객체를 외부에서 결정해서 연결시키는 것을 의미한다. 즉 클래스의 기능을 추상적으로 묶어놓은 인터페이스를 가져다 쓰면 되는 것이며, 그 밖은 스프링에서 객체를 주입해준다.
DI(의존성 주입)을 통해서 모듈 간의 결합도가 낮아지고 유연성이 높아집니다.
자바에서 예시를 보자
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
참고로 MemberRepository과 DiscountPolicy는 Interface로 추상화 객체이다.
OrderServiceImpl 클래스는 추상화에 의존하고 있다. 그렇다면 추상화에 구체화를 시켜줘야 하는데 이건 어디서 이뤄지는걸까?
이대로 코드를 실행하게되면 NullPointException이 날 것이다.
AppConfig 파일을 살펴보자
public class AppConfig {
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy(){
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
- AppConfig에서 구현객체를 대신 생성해주고 있다.
- 구현객체 OrderServiceImpl은 자신의 로직을 실행하는 역할만 담당한다.
- OrderServiceImpl은 필요한 인터페이스들을 호출하지만 어떤 구현 객체들이 실행될지 모른다.
- 객체를 OverServiceImpl 즉 개발자가 직접 코드를 작성해서 호출하는 것이 아닌 AppConfig에서 직접 호출해주는 것
- 만약 DiscountPolicy에 구현체가 바뀐다면 주석 친 부분만 바꿔주면 된다.
public DiscountPolicy discountPolicy(){
return new FixDiscountPolicy();
//return new RateDiscountPolicy();
}
만약 AppConfig에서 저 역할을 담당하지 않는다면 어떻게 될까?
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDisCountPolicy();
... (생략)
}
직접 개발자가 생성자 주입을 해주어야 한다.
만약 주입해야하는 구현체가 바뀐다면 OrderServiceImpl에서 바꿔줘야한다.
스프링 컨테이너에서는 어떻게 DI, IoC를 어떻게 사용할까?
@Configuration // 설정정보담당하는 어노테이션
public class AppConfig {
...
@Bean
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy(){
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
Long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 10000);
System.out.println("order = " + order.toString());
}
스프링 컨테이너
- ApplicationContext를 스프링 컨테이너라고 한다.
- 기존의 개발자가 AppConfig를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제부터 스프링 컨테이너를 통해서 사용한다.
- 스프링 컨테이너는 @Configuration이 붙은 AppConfig를 설정(구성) 정보로 사용한다. 여기서 @Bean이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다.
- 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 한다.
- 스프링 빈은 @Bean이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다.
- 스프링 컨테이너를 통해서 필요한 스프링 빈(객체)를 찾아야 한다. 스프링 빈은 applicationContext.getBean() 메서드를 사용해서 찾을 수 있다.
- 개발자가 직접 자바코드로 모든 것을 했다면 이제부터 스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용하도록 변경되었다.
Reference
https://twoline.tistory.com/66
https://swdevelopment.tistory.com/272