스프링 전환의 핵심 로직 요약
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, 트랜잭션 등)
- 객체를 스프링이 관리하게 되면, 그 객체에 "에러 나면 자동 롤백해줘"나 "실행 시간 측정해줘" 같은 강력한 부가 기능들을 아주 쉽게 붙일 수 있습니다.