이제, 지금까지 작성한 내용을 스프링 기반으로 전환해보자.
AppConfig
AppConfig 클래스에 @Configuration 어노테이션을, 각 메서드에는 @Bean 어노테이션을 붙인다.
어노테이션을 붙일 때 import되는 클래스 혹은 패키지명에 주의한다.
AppConfig.java
package hdxian.hdxianspringcore;
import hdxian.hdxianspringcore.discount.DiscountPolicy;
import hdxian.hdxianspringcore.discount.FixDiscountPolicy;
import hdxian.hdxianspringcore.discount.RateDiscountPolicy;
import hdxian.hdxianspringcore.member.MemberRepository;
import hdxian.hdxianspringcore.member.MemberService;
import hdxian.hdxianspringcore.member.MemberServiceImpl;
import hdxian.hdxianspringcore.member.MemoryMemberRepository;
import hdxian.hdxianspringcore.order.OrderService;
import hdxian.hdxianspringcore.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 애플리케이션 전체를 설정하고 구성함.
// 앞으로 애플리케이션에 대한 환경 설정은 모두 이 클래스에서 수행한다.
@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
}
@Configuration은 어플리케이션의 설정 정보가 저장된 클래스에 붙인다.
@Bean 어노테이션은 @Configuration이 붙은 클래스의 메서드에 붙이며, 이 어노테이션이 붙은 메서드가 반환하는 객체는 스프링 빈으로 등록된다.
스프링 빈은, 스프링 컨테이너에서 관리하는 구현 객체를 말한다.
MemberApp, OrderApp
MemberApp과 OrderApp에서 기존 AppConfig를 사용하는 부분을 주석 처리하고, memberService와 orderService 객체를 사용하는 부분을 다음과 같이 변경한다.
MemberApp.java
package hdxian.hdxianspringcore;
import hdxian.hdxianspringcore.member.Grade;
import hdxian.hdxianspringcore.member.Member;
import hdxian.hdxianspringcore.member.MemberService;
import hdxian.hdxianspringcore.member.MemberServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MemberApp {
public static void main(String[] args) {
// // AppConfig 객체를 생성하고, AppConfig 객체로부터 구현체를 생성한다.
// AppConfig appConfig = new AppConfig();
//
// // memberService에는 memberServiceImpl 객체가 들어있음.
// MemberService memberService = appConfig.memberService();
// MemberService memberService = new MemberServiceImpl();
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
Member member = new Member(1L, "memberA", Grade.VIP);
memberService.join(member);
Member findMember = memberService.findMember(1L);
System.out.println("new member: " + member.getName());
System.out.println("findMember = " + findMember.getName());
}
}
OrderApp.java
package hdxian.hdxianspringcore;
import hdxian.hdxianspringcore.member.Grade;
import hdxian.hdxianspringcore.member.Member;
import hdxian.hdxianspringcore.member.MemberService;
import hdxian.hdxianspringcore.member.MemberServiceImpl;
import hdxian.hdxianspringcore.order.Order;
import hdxian.hdxianspringcore.order.OrderService;
import hdxian.hdxianspringcore.order.OrderServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class OrderApp {
public static void main(String[] args) {
// MemberService memberService = new MemberServiceImpl(null);
// OrderService orderService = new OrderServiceImpl(null, null);
// AppConfig appConfig = new AppConfig();
// // memberService는 MemberServiceImpl 객체를 참조함.
// MemberService memberService = appConfig.memberService();
// // orderService는 OrderServiceImpl 객체를 참조함.
// // AppConfig에서 MemoryMemberService, FixDiscountPolicy를 주입해줌.
// OrderService orderService = appConfig.orderService();
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(1L, "itemA", 20000);
System.out.println("order = " + order);
System.out.println("order.calculatePrice = " + order.calculatePrice());
}
}
ApplicationContext: 스프링 컨테이너는 ApplicationContext 참조변수를 통해 이용 가능하다.
AnnotationConfigApplicationContext
- @Configuration 어노테이션이 붙은 클래스를 설정 정보로 하는 스프링 컨테이너를 구성한다.
- 인수로 @Configuration 어노테이션이 붙은 클래스의 정보(Appconfig.class)를 전달한다.
applicationContext.getBean()
- 스프링 컨테이너의 스프링 빈은 applicationContext의 getBean() 메서드를 통해 찾을 수 있다.
- getBean()에 전달하는 인수로는 빈의 이름과 빈의 타입이다.
- 빈의 이름과 타입은 기본적으로 @Bean 어노테이션이 붙은 메서드의 이름과 반환 타입이다.
- 가령, MemberService의 구현 객체를 가져오고 싶다면 applicationContext.getBean("memberService", MemberService.class)를 호출한다.
- AppConfig를 보면, @Bean 어노테이션이 붙은 memberService()의 리턴 타입은 MemberService이기 때문이다.
수정한 내용을 바탕으로 MemberApp과 OrderApp을 실행하면 다음과 같은 창이 뜬다.
스프링과 관련하여 로그가 몇 개 뜨고, 이전과 똑같은 실행 결과가 나온다.
로그 내용은 스프링 빈이 생성되었다는 내용으로, 위의 org... 부분은 스프링이 기본적으로 생성하는 필수 빈이고, 나머지는 appConfig, memberRepository 등 익숙한 이름이 보인다.
지금까지의 내용을 정리하면 다음과 같다.
우리는 이전까지 작성한 프로그램을 스프링 컨테이너 기반으로 전환하는 작업을 하였다.
스프링 컨테이너
- ApplicationContext를 스프링 컨테이너라 한다.
- 앞으로 어플리케이션에서 생성하고 관리하는 모든 구현 객체는 스프링 컨테이너에서 관리한다.
- 스프링 컨테이너는 @Configuration 어노테이션이 붙은 클래스를 설정 정보(구성 정보)로 사용한다.
- 스프링 컨테이너는 @Configuration이 붙은 클래스에서, @Bean 어노테이션이 붙은 메서드를 모두 실행하여 반환된 객체를 스프링 컨테이너에 등록한다.
- 이렇게 스프링 컨테이너에 등록된 객체들을 스프링 빈이라 한다.
- 스프링 빈은 @Bean 어노테이션이 붙은 메서드명을 기본 이름으로 사용한다. (memberService, orderService 등)
- name 옵션을 통해 변경할 수도 있으나, 관례상 잘 사용하지는 않는다. (복잡해질게 뻔하긴 하다.)
- 이전에는 개발자가 필요한 객체를 AppConfig를 통해 직접 생성(혹은 조회)했지만, 이제는 그러한 설정 정보를 스프링에 등록하고, 스프링 컨테이너를 통해 필요한 스프링 빈(객체)를 찾아 사용한다.
- 필요한 스프링 빈은 ApplicationContext.getBean() 메서드를 통해 찾을 수 있다.
그런데, 이 내용만으로는 코드만 더 길어졌지, 어떤 장점이 있는지 와닿지 않는다.
강의에서는 이런 식으로 스프링 빈을 통해 요소를 자동 관리 해주는 것에는 어마어마한 장점이 있다고 하니, 차차 배우면서 이해하게 될 것이다.
이번 섹션까지 우리는 순수하게 자바 코드로 객체 지향 프로그래밍을 하면서 프로그램의 구조, 실행 흐름, 객체지향적으로 고려할 점 등에 대해 배웠다.
다음 섹션부터는 이러한 이해를 바탕으로 스프링을 직접 사용해보고 그 핵심 원리를 배우게 될 것이다.
이 포스트를 기준으로, 우리는 이제 막 스프링을 시작했을 뿐이다.
앞으로의 내용도 잘 이해해서 기반이 탄탄한 개발자로 성장했으면 좋겠다.
'[inflearn] 스프링 핵심 원리 - 기본편 > 섹션 3 - 객체 지향 원리 적용' 카테고리의 다른 글
18. IoC, DI, 그리고 컨테이너 (0) | 2023.10.25 |
---|---|
17. 좋은 객체 지향 설계의 5가지 원칙 (SOLID)의 적용 (0) | 2023.10.25 |
16. 새로운 구조와 할인정책 적용, 전체 흐름 정리 (0) | 2023.10.24 |
15. AppConfig 리팩터링 (0) | 2023.10.14 |
14. 관심사의 분리 (0) | 2023.10.13 |