INFLEARN

[스프링 핵심 원리 - 기본편] 4. 순수 Java 코드 → Spring 전환

ch010104 2026. 3. 12. 20:24

스프링 전환의 핵심 로직 요약

1. 설정 정보의 변화 (AppConfig)

  • 기존: 단순한 자바 클래스. 개발자가 직접 메서드를 호출해 객체를 생성함.
  • 스프링: @Configuration이 붙어 스프링의 **'설정 지도'**가 됨. 메서드 위의 @Bean은 "이 객체를 관리해줘!"라는 등록 마크임.

2. 관리 주체의 변화 (IoC 컨테이너 등장)

  • ApplicationContext: 우리가 만든 AppConfig를 읽어서 객체들을 담아두는 **'거대한 창고'**입니다.
  • 스프링 빈(Bean): 창고에 보관된 객체들입니다. 기본적으로 메서드 이름(예: memberService)이 이름표가 됩니다.
package com.example.spring_study.spring_study;

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

// 객체의 생성과 연결을 담당함
@Configuration
public class AppConfig {

    // 각 메서드의 역할이 잘 들어나게 리팩터링
    @Bean // Spring Container에 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 RateDiscountPolicy();
    }
}

package com.example.spring_study.spring_study;

import com.example.spring_study.spring_study.member.Grade;
import com.example.spring_study.spring_study.member.Member;
import com.example.spring_study.spring_study.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 = new MemberServiceImpl()을 대체 -> AppConfig의 생성자로 memberService 객체를 가져옴
        // MemberService memberService = appConfig.memberService();

        // Spring에서는 ApplicationContext가 Spring Container임
        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("find Member = " + findMember.getName());
    }
}

package com.example.spring_study.spring_study;

import com.example.spring_study.spring_study.member.Grade;
import com.example.spring_study.spring_study.member.Member;
import com.example.spring_study.spring_study.member.MemberService;
import com.example.spring_study.spring_study.order.Order;
import com.example.spring_study.spring_study.order.OrderService;
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(); // MemberService memberService = new MemberServiceImpl()을 대체
        // OrderService orderService = appConfig.orderService(); // OrderService orderService = new OrderServiceImpl()을 대체

        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);
        // System.out.println("order.calculatePrice = " + order.calculatePrice());
    }
}


"코드가 왜 더 복잡해 보일까?" (Before vs After)

구분 순수 자바 (Before) 스프링 컨테이너 (After)

객체 조회 appConfig.memberService() ac.getBean("memberService", ...)
생성 시점 필요한 시점에 new 또는 호출 컨테이너가 뜰 때 미리 싹 다 만들어둠
관리 방식 개발자가 직접 AppConfig를 관리함 스프링이 객체 생명주기를 통제함

스프링 컨테이너를 사용하면 얻는 결정적 장점

1. 싱글톤 컨테이너 (Singleton)

  • 자바 방식: 누군가 appConfig.memberService()를 100번 호출하면 (설계에 따라 다르지만) 객체가 계속 생성될 위험이 있습니다.
  • 스프링: 스프링은 컨테이너에 등록된 빈을 딱 하나만 생성해서 공유합니다. 메모리 효율이 압도적입니다.

2. 의존관계 주입의 자동화 (DI)

  • 지금은 수동으로 getBean을 하지만, 나중에는 @Autowired 한 줄로 컨테이너가 복잡한 의존관계를 알아서 엮어줍니다. (조립의 자동화)

3. 유연한 부품 교체와 설정 확장

  • 수만 개의 클래스가 얽힌 대규모 프로젝트에서 특정 기능을 바꿀 때, 자바 코드를 다 뒤질 필요 없이 설정(Config)만 슥 바꾸면 애플리케이션 전체의 동작이 변합니다.

4. 부가 기능 제공 (AOP, 트랜잭션 등)

  • 객체를 스프링이 관리하게 되면, 그 객체에 "에러 나면 자동 롤백해줘"나 "실행 시간 측정해줘" 같은 강력한 부가 기능들을 아주 쉽게 붙일 수 있습니다.