[Spring] 스프링으로 전환하기

이전까지의 포스팅은 순수한 자바만을 사용하여 DI를 적용했다. 이제 스프링을 본격적으로 사용해보자. 

우선, 스프링 코드를 살펴보자.


AppConfig

package hello.core;

import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.member.MemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }

    @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();
    }

}
AppConfig에 설정을 구성한다는 뜻의 @Configuration 을 붙여주고,
각 메서드에 @Bean 을 붙여줌으로써 스프링 컨테이너에 스프링 빈으로 등록된다. 

 

MemberApp 클래스 또한 스프링을 사용하는 버전으로 변경해보자.

MemberApp

package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MemberApp {

    public static void main(String[] args) {
//        AppConfig appConfig = new AppConfig();
//        MemberService memberService = appConfig.memberService();

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
        
        Member member = new Member(1L, "memberA", Grade.VIP); //Long 타입이므로 1L로 작성
        memberService.join(member);

        Member findMember = memberService.findMember(1L);
        System.out.println("new member = " + member.getName());
        System.out.println("find Member = " + findMember.getName());
    }
}

기존의 AppConfig에서 가져오는 것을 주석 처리해주고...

 

스프링은 모든 것이 ApplicationContext 라는 것으로 시작한다. 이것이 스프링 컨테이너이고 모든 객체(@Bean)들을 관리해준다. 

생성할 때는 new AnnotationConfigApplicationContext()로 만들어주고(어노테이션을 기반으로 생성되었기 때문), 파라미터는 사용하고자 하는 AppConfig.class를 넣는다. 

이렇게 하면 AppConfig에 있는 환경설정 정보를 가지고 스프링이 AppConfig안에 있는 @Bean이 붙은 메서드들을 스프링 컨테이너에 객체를 생성하고 관리해준다. 

 

기존에는 AppConfig에서 호출하여 직접 찾아왔지만, 이제는 스프링 컨테이너를 통해 찾아온다.

applicationContext.getBean(...) 을 사용하는데 AppConfig에서 memberService를 찾아올 것이다. 

첫 번째 파라미터는 찾아올 메서드의 이름, 두 번째 파라미터반환 타입을 명시한다. 

 

이를 실행시켜보면 다음과 같은 결과를 확인할 수 있다.

위의 5개 log는 스프링이 내부적으로 필요해서 등록하는 스프링 빈이고, 

아래 5개의 log를 살펴보면, 'appConfig', 'memberSerivce' ... 등 우리가 @Bean으로 등록해놓은 메서드들이 Key는 메서드명, Value는 객체 인스턴스(return 값)로 하여 스프링 컨테이너에 등록이 되는 것을 확인할 수 있다. 이를 찾아 꺼낼 때는 스프링 컨테이너에 위와 같이 메서드 명과 타입을 이용하여 꺼내오면 된다.

 

위와 같은 방법으로 앞서 생성한 OrderApp도 스프링을 이용하여 수정하자.

OrderApp

package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.order.Order;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class OrderApp {
    public static void main(String[] args) {

//        AppConfig appConfig = new AppConfig();
//        MemberService memberService = appConfig.memberService();
//        OrderService orderService = appConfig.orderService();

        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = ac.getBean("memberService", MemberService.class);
        OrderService orderService = ac.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);
        System.out.println("order.calculatePrice() = " + order.calculatePrice());
    }
}

스프링 컨테이너 

위의 내용들을 정리해보면...

  • ApplicationContext스프링 컨테이너라 한다. 
  • 기존에는 AppConfig를 사용하여 직접 객체를 생성하고 DI를 했지만, 이제는 스프링 컨테이너를 사용한다.
  • 스프링 컨테이너는 @Configuration이 붙은 AppConfig를 구성 정보로 사용하고, @Bean이 붙은 메서드를 모두 호출하여 반환된 객체(return)를 스프링 컨테이너에 등록한다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다. 
  • 스프링 빈은 @Bean이 붙은 메서드의 이름(메서드 명)을 스프링 빈의 이름으로 사용한다. 
    • @Bean(name = "method_name") 과 같은 방식으로 변경할 수 있긴 하다. 하지만 웬만하면 default값을 따르자.
  • 이전에는 필요한 객체를 AppConfig를 사용해 직접 조회했지만, 이제는 스프링 컨테이너를 통해서 필요한 스프링 빈(객체)을 찾아야 한다. 스프링 빈은 .getBean(...) 메서드를 사용하여 찾을 수 있다. 
  • 기존에는 직접 자바 코드로 모든 것을 생성, 관리했다면 이제는 스프링 컨테이너에 객체를 스프링 빈으로 등록하고 스프링 컨테이너에서 스프링 빈을 찾아 사용하도록 바뀌었다. 

다음 포스팅부터 본격적으로 스프링 컨테이너와 스프링 빈에 대하여 자세히 알아보자.