[Spring] 스프링 핵심 원리 이해
객체지향설계와 스프링
- 객체 지향 프로그래밍
- 컴퓨터 프로그램을 “객체들의 모임”으로 파악하고자 하는 것
- 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다.
- 스프링은 다형성을 극대화한 것
- 역할(인터페이스)와 구현(인터페이스를 구현한 클래스)을 분리한다
- 클라이언트는?
- 대상의 역할(인터페이스)만 알면 되고, 구현 대상의 내부 구조를 몰라도 된다.
- 구현 대상 자체나 구현 대상의 내부구조가 바뀌어도 영향 X
- 즉, 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있어야 한다
좋은 객체 지향 설계의 5가지 원칙 (SOLID)
- SRP 단일 책임 원칙
- 한 클래스는 하나의 책임만 가져야 한다.
- OCP 개방-폐쇄 원칙
- 소프트웨어 요소는 확장에는 열려있으나, 변경에는 닫혀있어야 한다.
- LSP 리스코프 치환 원칙
- 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
- ISP 인터페이스 분리 원칙
- 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
- 인터페이스가 명확해지고, 대체 가능성이 높아진다.
- DIP 의존관계 역전의 원칙
- 구현 클래스에 의존하지 말고 인터페이스(역할)에 의존한다.
- 유연하게 구현체를 변경할 수 있다.
스프링 컨테이너와 스프링 빈
ApplicationContext
: 스프링 컨테이너@Configuration
이 붙은AppConfig
를 구성 정보로 사용한다.AppConfig
에서@Bean
이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다.
- 스프링 빈 : 스프링 컨테이너에 등록된 객체
@Bean
이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다. (빈 이름을 직접 부여도 가능)- 빈 이름은 항상 다른 이름을 부여해야 한다.
- 스프링 컨테이너는
applicationContext.getBean()
메서드를 이용하여 스프링 빈을 찾을 수 있다. - 스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용한다.
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
ApplicationContext
: 스프링 컨테이너이자 인터페이스- 스프링 컨테이너는 XML 기반 or 애노테이션 기반의 자바 설정 클래스로 만들 수 있다.
new AnnotationConfigApplicationContext(AppConfig.class)
: 인터페이스의 구현체- 스프링 컨테이너 생성 시 구성 정보를 지정해야 한다. (
AppConfig.class
: 구성 정보) - 스프링 컨테이너는 설정 정보를 참고하여 의존 관계을 주입한다.
스프링 빈 조회
- 모든 빈 출력하기
ac.getBeanDefinitionNames()
: 스프링에 등록된 모든 빈 이름을 조회한다.ac.getBean()
: 빈 이름으로 빈 객체(인스턴스)를 조회한다.@Test @DisplayName("모든 빈 출력하기") void findAllBean() { String[] beanDefinitionNames = ac.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { Object bean = ac.getBean(beanDefinitionName); System.out.println("name=" + beanDefinitionName + " object=" + bean); } }
- 애플리케이션 빈 출력하기
getRole()
: 스프링이 사용자가 정의한 빈인지, 내부에서 사용하는 빈인지를 구분한다.ROLE_APPLICATION
: 일반적으로 사용자가 정의한 빈ROLE_INFRASTRUCTURE
: 스프링이 내부에서 사용한 빈@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); } } }
- 스프링 빈 조회(기본)
ac.getBean(빈이름, 타입)
@Test @DisplayName("빈 이름으로 조회") void findBeanByName() { MemberService memberService = ac.getBean("memberService", MemberService.class); assertThat(memberService).isInstanceOf(MemberServiceImpl.class); }
ac.getBean(타입)
@Test @DisplayName("이름 없이 타입으로만 조회") void findBeanByType() { MemberService memberService = ac.getBean(MemberService.class); assertThat(memberService).isInstanceOf(MemberServiceImpl.class); }
- 조회 대상 스프링 빈이 없으면 예외 발생
NoSuchBeanDefinitionException: No bean named 'xxxxx' available
ac.getBean("xxxxx", MemberService.class)
를 실행하면NoSuchBeanDefinitionException.class
예외가 실행되어야 한다.@Test @DisplayName("빈 이름으로 조회 X") void findBeanByNameX() { // ac.getBean("xxxx", MemberService.class); assertThrows(NoSuchBeanDefinitionException.class, () -> ac.getBean("xxxxx", MemberService.class)); }
- 스프링 빈 조회 - 동일한 타입이 둘 이상일 경우
- 타입으로 조회 시 동일한 타입이 둘 이상일 경우 오류가 발생한다 (
NoUniqueBeanDefinitionException
) - 해결 방법 : 빈 이름을 저장한다
@Test @DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 저장하면 된다") void findBeanByName() { MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class); assertThat(memberRepository).isInstanceOf(MemberRepository.class); }
ac.getBeansOfType()
: 해당 타입의 빈을 모두 조회한다.@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); // 해당 타입이 2개 등록되어있으니 2개가 나와야함을 검증한다 }
- 타입으로 조회 시 동일한 타입이 둘 이상일 경우 오류가 발생한다 (
- 스프링 빈 조회 - 상속 관계의 경우
- 모든 자바의 최고 부모인
Object
타입으로 조회하면 모든 스프링 빈을 조회할 수 있다. - 부모 타입으로 조회 시 자식이 둘 이상이 있으면,
NoUniqueBeanDefinitionException
오류가 발생한다. - 해결 방법 : 빈 이름을 지정한다.
@Test @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면 빈 이름을 지정하면 된다") void findBeanByParentTypeBeanName() { DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class); assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class); }
- 부모 타입으로 모두 조회
ac.getBeansOfType()
사용
@Test @DisplayName("부모 타입으로 모두 조회하기") void findAllBeanByParentType() { Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class); assertThat(beansOfType.size()).isEqualTo(2); // 하위에 자식이 2개 있다. 여기서는 자식이 2개인지 검증한다. for (String key : beansOfType.keySet()) { System.out.println("key = " + key + " value = " + beansOfType.get(key)); } }
- 모든 자바의 최고 부모인
BeanFactory vs. ApplicationContext (스프링 컨테이너)
- BeanFactory
- 스프링 컨테이너의 최상위 인터페이스
- 스프링 빈을 관리하고 조회하는 역할
getBean()
을 제공
- ApplicationContext
- BeanFactory 기능을 모두 상속받는다
- 빈 관리 기능 + 편리한 부가 기능을 제공한다.
- 메시지 소스를 활용한 국제화 기능
- 환경 변수
- 애플리케이션 이벤트
- 편리한 리소스 조회
- BeanFactory를 직접 사용하지 않고, 부가기능이 포함된 ApplicationContext를 사용한다.
BeanDefinition
BeanDefinition
: 스프링 빈 설정 메타 정보- 스프링의 다양한 형태의 설정 정보를
BeanDefinition
으로 추상화해서 사용한다. @Bean
,<bean>
당 각각 하나씩 메타 정보가 생성된다.
- 스프링의 다양한 형태의 설정 정보를
- 스프링 컨테이너는 메타정보를 기반으로 스프링 빈을 생성한다.
AnnotationConfigApplicationContext
는AnnotatedBeanDefinitionReader
를 사용해서AppConfig.class
를 읽고BeanDefinition
을 생성한다.
김영한의 스프링 핵심 원리 - 기본편을 참고하였습니다