Post

스프링 핵심원리 - 기본편 정리2

스프링 핵심원리 - 기본편 정리2

들어가며

이번 글에서는 스프링 컨테이너에 대해 정리를 하고, 이를 예제를 통한 실제 코드로 테스트하면서 알아보도록 할 것이다.


예제 Class Diagram

먼저 예제의 구조는 다음 그림과 같다. 서비스 구조 위 구조에서도 역할과 구현의 분리가 명확히 드러난다.

각 인터페이스 이름은 주문 서비스 역할은 MemberService 회원 저장소 역할은 MemberRepository 할인 정책 역할은 DiscountPolicy 이다.

앞에서 비즈니스 로직과 의존관계를 제어하는 AppConfig 클래스로 관심사를 나누었다. AppConfig의 코드는 아래와 같다.

AppConfig

이는 스프링을 이용한 AppConfig인데, 만약 Spring을 사용하지 않는다면 어노테이션을 제거만 하면 된다. 물론, 이때에는 직접 AppConfig 객체를 만들어 꺼내와야한다.


스프링 빈 등록

보면 각 역할을 맡은 인터페이스마다 @Bean 어노테이션이 붙어있는 것을 볼 수 있다. 이는 스프링 컨테이너에 등록을 한다는 의미이고 다형성을 활용하기 위해 모두 인터페이스로 등록을 한 것이다. 그리고 같은 이름으로는 등록하지 않는 것이 좋다.

@Bean 어노테이션이 붙은 메소드는 Spring 컨테이너에 메소드명이 key 리턴값이 value로 저장된다. 즉, 스프링이 관리할 수 있도록 등록을 하는 것이다(제어권을 넘김). 등록을 하는데는 순서가 보장되지 않는다.

🤔그렇다면 의문이 하나 생길 수 있다. memberService와 orderSerivec가 memberRepository함수를 호출하고 meberRepository를 등록하면서까지 총 3번MemoryMemberRepository객체를 생성하게 되는데 그렇다면 모두 다른 저장소 객체3개가 생겨나게 되는 것일까?

실제로는 그렇지 않다. 실제로는 @Configuration 어노테이션이 바이트 코드를 조작하여 AppConfig 클래스를 변형시켜 등록하여 싱글톤을 유지 시킨다. 싱글톤에 대해서는 뒤에서 더 알아보겠다.


스프링 빈 조회

원하는 클래스/인터페이스를 스프링 빈에 등록하는 방법을 알아 보았다. 그렇다면 등록한 빈을 사용하는 법도 알아 보아야겠다. 등록한 빈을 사용하는 방법은 다음과 같다. 스프링 빈 조회 ApplicationContext를 구현한 AnnotationConfigApplicationContext(너무 길다..)를 생성하며 만들어 놓은 AppConfig 클래스를 파라미터로 넘겨주면, 자기자신(정확히는 조작된 클래스로)과 내부에 빈으로 등록된 구성정보가 등록된다.

그리고 생성한 객체에서_ getBean()_ 메서드를 통해 등록한 빈을 조회할 수 있다. 여기서 조회할 수단이 두가지가 있는데 첫번째 등록한 빈 메서드명(빈 이름), 등록한 빈 타입이다.

조회를 했는데 가져올 빈이 없다면 NoSuchBeanDefinitionException 예외가 발생하고,

가져오는 빈 객체가 유일하지 않고 2개이상이면 NoUniqueBeanDefinitionException 예외가 발생한다. 이 때 등록된 빈 역시 상속 관계를 따르기 때문에 주의해야한다.

빈 상속관계

그래서 부모타입을 조회 했을 경우 자식 타입까지 모두 조회한다.

타입으로 조회했을 때, 상속 관계에 의해 타입이 겹치는 경우, 이름까지 명시해 주면 된다.

그리고 등록된 여러개의 빈을 조회 하려면 getBeansOfType() 메서드를 사용하면된다. 조회할 클래스를 파라미터로 넘겨주면 Map 형식으로 반환한다. key값에 빈 이름, value값에 조회한 빈 객체가 담겨있다.

bean 등록

조회 테스트 코드이다. 3번째 테스트의 경우 부모가 같은 자식클래스가 둘이 등록되어 있을 때, 자식 중 특정 타입으로 조회하면 중복은 피할 수 있지만 강의에서 영한님이 좋은 코드는 아니라고 하였다.

보기에 2번째 테스트와 큰 차이는 없어 보이지만 아마 클래스를 구체적으로 지정함으로써 다형성이 바로 드러나지 않기 때문에 2번째 방법이 더 바람직한 것 같다.

그렇다면 우리가 빈을 조회하여 꺼내올 때마다 새로운 객체를 생성하여 가져오는 것일까?

답은, 그럴수도 있고 아닐수도 있다. 이부분에 대해서 알아보자.


싱글톤

  • 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴

  • 구현하는 방법에는 여러가지가 있다. 가장 단순하게는 Eager Initilization 방법(미리 정적으로 생성)을 사용한다.

  • 여러 요청을 처리할 때 하나의 객체가 처리하도록 하여, 메모리 효율성을 높인다.

  • 구체 클래스에 의존하게 되어 DIP, OCP를 위반하게 된다.

싱글톤 컨테이너

  • 싱글톤 패턴의 문제점을 해결하면서, 싱글톤을 유지한다.

  • 추가코드 없음, DIP, OCP 지킴, 테스트와 private 생성자로부터 자유롭다. (개사기 ㄷㄷ..)

우리가 스프링 컨테이너에 빈을 등록할 때 디폴트 Bean Scope를 설정할 수 있는데, 디폴트 설정이 “singleton”이다.

그러므로 아무 설정도 하지 않고 빈으로 등록한 뒤 빈을 조회하여 가져오면 같은 객체를 가져오도록 보장되어 있다.

만약 빈 스코프가 싱글톤이 아닌 프로토타입이면, 조회할 때마다 다른 객체를 가져온다.

그리고 그외의 경우에도 Bean Scope의 설정을 따른다.

Bean Scope란?

- 말 그대로, 빈이 존재할 수 있는 범위를 말한다.

Bean Scope 종류

  • 싱글톤 - 스프링 컨테이너의 시작과 종료까지 유지
  • 프로토타입 - 스프링 컨테이너가 빈의 생성과 의존관계 주입까지만 관여 (초기화는 따로)
  • request - 웹 요청이 들어오고 나갈 때까지 유지 .. </span>

Bean Scope는 뒤에서 더 자세히 다루도록 하겠다.

싱글톤 방식 주의점

  • 싱글톤 방식은 객체를 하나만 생성해서 공유하기 때문에 상태를 유지해선 안된다.

  • 처음 설계를 할 때부터 stateless하게 설계해야 한다.

    • 특정 클라이언트에 의존적인 필드가 있으면 안된다.
    • 특정 클라이언트가 변경할 수 없고 가급적 읽기만 가능해야한다. -> 공유되지 않는, 지역변수, 파라미터, ThreadLocal을 이용해야 한다.

나중에 찾기 어려운 큰 버그로 다가올 수 있다.

마지막으로, 앞에서 수동으로 빈을 등록했던 부분을 다시 떠올려 보자.

그 때 바이트 코드를 조작하여 중복으로 생성되는 것을 방지 한다고 하였었다.

그 이유가 바로 이것이다. 싱글톤을 유지하기 위해서 바이트 코드를 조작하면서까지 하나의 객체만 생성하기를 보장하기 위해서이다.

아래 그림은 Configuration 어노테이션을 적용하였을 때 조작된 AppConfig의 대략적인 로직이다.

조작된 코드

그럼 스프링 컨테이너의 등록과 조회, 싱글톤에 대해서 정리하고, 빈 스코프에 대해서도 간단히 알아보았다.

다음에는 빈을 자동으로 등록하는 방법, 등록된 빈에 의존 관계를 주입하는 방법, 빈의 생명주기에 대해서까지 정리하도록 해보자


자료 출처: 영한님 인프런 강의

This post is licensed under CC BY 4.0 by the author.