728x90
반응형
섹션3 스프링 핵심 원리 이해2 - 객체 지향 원리 적용
AppConfig 리팩터링
- 입문서부터 아래 코딩이 왜 가능한지 궁금했는데 디자인패턴이라고 한다.
new OrderServiceImpl에 메서드를 바로 호출하는 것인데 이러한 문법을 보고 자바스크립트에서 처음 보고 신기하다고 생각했었다. 위 문법은 디자인 패턴 중 의존성 주입 패턴으로, 의존성 주입은 객체가 필요로 하는 의존성을 위부에서 주입받도록 설계하는 패턴이다.public MemberService memberService() { return new MemberServiceImpl(memberRepository()); } private static MemoryMemberRepository memberRepository() { return new MemoryMemberRepository(); } public OrderService orderService() { return new OrderServiceImpl(memberRepository(), discountPolicy()); } public DiscountPolicy discountPolicy() { return new FixDiscountPolicy(); }
코드에서 OrderServiceImpl의 생성자와 memberService() 메소드에서 memberRepository()를 인자로 받는 것이 이 디자인 패턴을 적용한 예시이다. OrderServiceImpl은 외부에서 MemberRepository를 제공받기 때문에 OrderServiceImpl 내부에서 MemoryMemberRepository에 직접 의존하지 않는다. 이는 OrderServiceImpl과 MemberRepository 사이의 결합도를 낮추고, MemberRepository 구현체를 변경하더라도 OrderServiceImpl에 영향을 주지 않도록 한다.
새로운 구조와 할인 정책 적용
실습
전체 흐름 정리
-
좋은 객체 지향 설계의 5가지 원칙 적용
-
IoC, DI, 그리고 컨테이너
- 제어의 역전(IoC, Inversion of Control): 기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고 연결하고 실행했다. 즉, 구현 객체가 프로그램의 제어 흐름을 스스로 조종했다. 반면 AppConfig가 등장한 이후에 구현 객체는 자신의 로직을 실행하는 역할만 담당한다. 프로그램의 제어 흐름은 이제 AppConfig가 가져간다. 구현 객체는 필요한 인터페이스를 호출하지만 어떤 구현 객체들이 실행될지 모른다. 그러니까 프로그램에 대한 제어 흐름에 대한 권한은 모두 AppConfig가 가진다. 이렇게 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전이라 한다.
- 프레임워크 vs 라이브러리
- 프레임워크는 내가 작성한 코드를 제어하고, 대신 실행하면 그것은 프레임워크가 맞다(JUnit)
- 반면 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 라이브러리다.
- 의존관계 주입(DI, Dependency Injection): 구현객체는 인터페이스에 의존한다. 실제 어떤 구현 객체가 사용될지는 모른다. 의존 관계는 정적 클래스 의존 관계와 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계를 분리해서 생각해야 한다.
- 정적 클래스 의존 관계(클래스 다이어그램)
- 클래스가 사용하는 import 코드만 보고 의존관계를 쉽게 판단할 수 있다. 정적인 의존관계는 애플리케이션을 실행하지 않아도 분석할 수 있다.
- 이러한 클래스 의존관계만으로는 실제 어떤 객체가 주입되는지 알 수 없다.
- 동적인 객체 인스턴스 의존 관계
- 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계다
- 애플리케이션 실행 시점에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것을 의존관계 주입이라고 한다.
- 의존 관계 주입을 사용하면 클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있다. 그리고 정적인 클래스 의존관계를 변경하지 않고 동적인 객체 인스턴스 의존 관계를 변경할 수 있다.
- 정적 클래스 의존 관계(클래스 다이어그램)
- IoC 컨테이너, DI 컨테이너
- 본 강의에서 AppConfig가 두 컨테이너의 역할을 한다. 즉, 객체를 생성하고 관리하면서 의존 관계를 연결해 주는 것. 의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다. 또는 어샘블러, 오브젝트 팩토리 등으로 부른다.
스프링으로 전환하기
- 기존에는 코드를 실행하는 메인 메서드 내에서 AppConfig의 객체를 직접 생성하고 해당 객체로 메서드를 호출했었다.
public static void main(String[] args) { AppConfig appConfig = new AppConfig(); MemberService memberService = appConfig.memberService(); OrderService orderService = appConfig.orderService(); ... }
- 이를 스프링으로 전환하려면 일단 AppConfig 클래스에 @Configuration 어노테이션을 추가하고 각 메서드마다 @Bean 어노테이션을 추가한다 그리고 AppConfig 객체를 생성하는 코드를 아래처럼 바꾼다
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); ... }
- 스프링 컨테이너
- ApplicationContext를 스프링 컨테이너라 한다.
- 기존에는 개발자가 AppConfig를 사용해서 직접 객체를 생성하고 DI를 했지만 이제 스프링 컨테이너를 통해 사용한다.
- 스프링 컨테이너는 @Configuration이 붙은 AppConfig를 구성 정보로 사용한다. 여기서 @Bean이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다.
- 스프링 빈은 @Bean이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다(별도로 지정할 수 있다) 이러한 스프링 빈은 applicationContext.getBean()으로 찾을 수 있다.
- 스프링 컨테이너
섹션4 스프링 컨테이너와 스프링 빈
스프링 컨테이너 생성
- ApplicationContext를 스프링 컨테이너라 한다. 이는 인터페이스이다.
- 스프링 컨테이너는 XML(잘 사용X)을 기반으로 만들 수 있고, 어노테이션 기반의 자바 설정 클래스(AppConfig, 많이 사용)로 만들 수 있다.
- 스프링 컨테이너를 생성하면
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
AnnotationConfigApplicationContext는 ApplicationContext의 구현체이다 - 스프링 컨테이너의 생성 과정
- 스프링 컨테이너 생성
- new AnnotationConfigApplicationContext(AppConfig.class)로 하면 스프링 컨테이너를 생성한다 그러면 스프링 컨테이너 내부에는 스프링 빈 저장소가 생성되고 저장소 내부에 빈 이름(key)과 빈 객체(value)가 생성된다
- 스프링 컨테이너를 생성할 때에는 구성 정보를 지정해주어야 한다.
- 스프링 빈 등록
- 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈(@Bean)을 등록한다.
- 빈 이름
- 빈 이름은 메서드 이름을 사용한다
- 빈 이름을 직접 부여할 수 있다. @Bean(name="mmm")
- 주의⛔️ 빈 이름은 항상 다른 이름을 부여해야 한다.
- 스프링 빈 의존 관계 설정 - 준비
- 스프링 빈 의존 관계 설정 - 완료
- 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입한다
- 단순히 자바 코드를 호출하는 것 같지만 차이가 있다. 이 차이는 싱글턴 컨테이너에서 설명한다
- 참고
- 스프링은 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있다. 먼저 스프링 빈 객체를 생성하고 빈이 다 생성되면 의존관계를 주입한다. 그런데 이렇게 자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리된다.
- 스프링 컨테이너 생성
컨테이너에 등록된 모든 빈 조회
- 모든 빈 출력하기
@Test @DisplayName("모든 빈 출력하기") void findAllBean() { String[] beanDefinitionNames = ac.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { Object bean = ac.getBean(beanDefinitionName); System.out.println("name = " + beanDefinitionName + ", object = " + bean); } }
- 애플리케이션 빈 출력하기
@Test @DisplayName("애플리케이션 빈 출력하기") void findApplicationBean() { String[] beanDefinitionNames = ac.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName); if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) { Object bean = ac.getBean(beanDefinitionName); System.out.println("name = " + beanDefinitionName + ", object = " + bean); } } }
- ROLE_APPLICATION: 내가 등록한 빈만 조회
- ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈만 조회
스프링 빈 조회 - 기본
import hello.core.AppConfig;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
public class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
MemberService memberService = ac.getBean("memberService", MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("이름 없이 타입으로 빈 조회")
void findBeanByType() {
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("구체 타입으로 조회")
void findBeanByName2() {
MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("빈 이름으로 조회X")
void findBeanByNameX() {
assertThrows(NoSuchBeanDefinitionException.class, () -> ac.getBean("xxx", MemberService.class));
}
}
스프링 빈 조회 - 동일한 타입이 둘 이상
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
@Test
@DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다")
void findBeanByTypeDuplication() {
assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(MemberRepository.class));
}
@Test
@DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다")
void findBeanByName() {
MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);
assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}
@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeanByType() {
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value = " + beansOfType.get(key));
}
System.out.println("beansOfType = " + beansOfType);
assertThat(beansOfType.size()).isEqualTo(2);
}
@Configuration
static class SameBeanConfig {
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
}
728x90
반응형