마무리하며

  • 지금까지 스프링 프레임워크의 가장 핵심적인 코어 부분을 다루어 보았다.
  • 간략하게 각 파트들을 정리하면서 스프링 프레임워크의 핵심적 원리들은 여기서 마무리한다.

 

 

 

DI와 IoC 그리고 스프링

  • 객체지향 설계를 위시한 SOLID부터 시작하여 일반적인 다형성으로는 그 한계가 있음을 보았고 이를 해결하기 위해 DI를 생각하였다. 그리고 이 DI를 개발자가 아닌 외부에서 자동으로 컨트롤하는 것이 더 편리하다는 생각을 하게 되고 이는 IoC의 개념이 되었다.
  • 이러한 DI와 IoC를 스프링 프레임워크에서는 스프링 DI 컨테이너를 통해 지원하였다. 스프링 프레임워크에서 DI 컨테이너를 통하여 저 두 개의 개념을 지원함으로서 개발자는 저 개념을 신경쓰지 않고도 편리하게 개발을 수행할 수 있게 되었다.
  • 스프링 컨테이너에 스프링 빈을 등록하는 것은 @Bean 어노테이션을 통해 수행이 가능하며 의존관계 주입 역시 @Configuration 어노테이션이 붙은 설정 파일에서 수행이 가능하다.
  • 만약 동일한 타입의 빈들이 감지되는 경우, 빈에 이름을 붙여 해당 빈의 이름을 명시해놓는 것으로 구별할 수 있도록 한다.

 

 

 

스프링과 싱글톤

  • 수많은 요청에 대해 일일히 객체들을 생성하여 반환하는 것은 메모리 면에서나 성능 면에서나 좋지 않은 방식이다. 특히나 그것이 동일한 객체를 계속해서 요청하는 경우에는 더 그럴 것이다.
  • 하나의 객체를 만들고 동일한 요청에 대해 객체를 더 만들 필요 없이 만들어진 객체를 반환하기만 한다면 메모리와 성능을 꽤나 효율적으로 올릴 수 있게 할 수 있다. 이렇게 구현한 패턴을 싱글톤 패턴이라고 한다.
  • 하지만 싱글톤 패턴은 해당 패턴을 구현하기 위한 추가적인 코드들과 생성자를 사용할 수 없음으로 인한 확장성의 어려움, 경직성으로 인하여 사용하기가 까다로운 패턴이었다.
  • 스프링 컨테이너는 저런 패턴을 구현한게 아닌 객체의 생성을 단 한 번만 수행하고 그 뒤에는 수행하지 않는 방식을 선택함으로서 패턴을 구현하지 않아도 각 객체를 싱글톤처럼 관리할 수 있었다.
    • 이는 @Configurtaion을 통한 수동적인 빈을 생성할 때 확실히 확인할 수 있는데 @Configuration 어노테이션이 붙은 스프링 빈을 바이트코드 조작을 통해 가짜 스프링 빈을 넣고, 요청이 들어오면 해당 객체를 반환하는 형식으로 수행된다.

 

 

컴포넌트 스캔과 자동 의존관계 주입

  • 수동 빈 등록과 DI를 수행할 경우 다음과 같은 Configruation 코드가 필요했다.
@Configuration
public class AppConfig{
    @Bean
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository(), memberPolicy());
    }
    
    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
    
    @Bean
    public MemberPolicy memberPolicy(){
        return new GradeMemberPolicy();
    }
}
  • 스프링 프레임워크는 이렇게 수동적으로 등록과 DI를 수행할수도 있지만 스프링 프레임워크에서 자동으로 빈들을 등록하며 의존관계 주입까지 자동으로 수행해주는 기능들을 지원한다.
  • 자동 빈 등록은 컴포넌트 스캔(Component Scan)이라고 하며 수행하기 위해서는 @ComponentScan 이라는 어노테이션을 사용해야 한다.
    • 스프링부트에서는 앱의 시작점인 Application.java 파일에서 @SpringBootApplication 어노테이션이 붙는데 그 내부에 @ComponentScan이 들어가 있다.
    • @ComponentScan은 해당 어노테이션이 붙은 파일이 소속된 디렉토리를 포함하여 그 아래 자식 디렉토리까지를 모두 스캔하여 @Component라는 어노테이션이 붙은 클래스들을 빈으로 등록한다.
  • 컴포넌트 스캔을 수행할 경우, @Component 어노테이션이 붙은 객체가 의존관계 주입이 필요할 경우 이를 자동으로 수행하도록 할 수 있는데 @Autowired를 통해서 가능하다.
@Component
public class MemberServiceImpl implement MemberService{
    private final MemberRepository memberRepository;
    private final MemberPolicy memberPolicy;
    
    @Autowired
    MemberServiceImpl(MemberRepository memberRepository, MemberPolicy memberPolicy){
        this.memberRepository = memberRepository;
        this.memberPolicy = memberPolicy;
    }
    
    ...
}

 

 

 

의존관계 자동 주입과 @Autowired

  • 의존관계 자동 주입은 스프링 프레임워크에서 자동적으로 의존관계 주입을 수행해주는 것을 말한다.
  • 의존관계 주입 방식은 4가지가 존재한다.
    • 생성자 주입
    • 수정자 주입
    • 필드 주입
    • 일반 메서드 주입
  • 수정자 주입, 필드 주입은 각각의 문제점으로 인하여 잘 사용되지 않고, 생성자 주입이 선호된다.
    • 생성자 주입은 final 키워드를 통해서 주입받는 객체들에 대한 불변성을 보장받을 수 있다. 어플리케이션에서 한번 설정된 의존 관계들은 특수한 상황이 아니라면 불변적이기 때문에 실수로 변경되는 상황을 원천적으로 차단할 수 있다.
    • 또한 final 키워드는 생성자 또는 리터럴로 초기화가 되지 않는다면 컴파일 시점에서 에러를 낼 수 있기 때문에 의존관계 주입 누락에 대해서도 안전하게 처리가 가능하다.
  • @Autowired는 타입으로 주입할 빈을 탐색하는데 다형성의 활용으로 인해 주입할 빈으로 탐색된 빈들이 2개 이상이 탐색될 수 있다.
  • 주입할 빈을 구체 클래스를 통해 명시할 수 있지만 그 경우 DIP가 위배되면서 OCP도 같이 깨지는 문제가 생긴다.
  • 이를 막기 위해 스프링 프레임워크는 @Primary와 @Qualifier를 지원한다.
    • @Qualifier는 추가적인 구분자를 통해 주입할 빈을 동일한 타입이어도 구분이 가능하도록 만들 수 있다.
    • @Primary는 우선순위를 설정하여 동일한 타입이 존재하여도 @Primary 어노테이션이 붙은 빈을 우선적으로 주입하도록 만든다.
    • 이 둘은 둘 중 하나만 사용하는게 아닌 상황에 따라 둘 다 사용하여 유연하게 대처할 수 있도록 하는게 좋다.

 

 

 

스프링 빈의 생명 주기

  • 스프링 빈의 생명 주기는 일반적으로는 다음의 순서로 이루어진다.
    • 스프링 빈 객체의 생성
    • 의존관계 주입 수행
  • 스프링 컨테이너는 해당 빈 객체가 생성과 의존관계 주입이 종료되면 사용이 가능하다는 콜백 함수를 수행시킬 수 있는데 3개의 방식을 통해 지원한다.
    • 첫 번째는 인터페이스를 통한 콜백 함수 수행이다.
    • 두 번째는 @Bean 내부의 init, close에 수행할 함수를 명시하는 콜백 함수 정의이다.
    • 세 번째는 @PostConstruct, @PreDestroy의 자바 표준 어노테이션을 통한 콜백 함수 수행이다.
  • 세 번째의 @PostConstruct, @PreDestory가 자바 표준이라 스프링에 종속적이지 않고, 설정하기에도 편리하여 많이 사용한다.
  • 이러한 콜벡 함수까지 포함할 경우 스프링 빈의 생명 주기는 다음과 같이 구성된다.
    • 객체 생성
    • 의존관계 주입 수행
    • 객체 초기화 콜백 메서드 수행
    • 객체 사용
    • 스프링 컨테이너 종료 이전 객체 소멸 콜백 메서드 수행
    • 객체 소멸
    • 스프링 컨테이너 종료

 

 

 

빈 스코프 - 프로토타입 스코프와 싱글톤 스코프

  • 빈 스코프는 스프링 빈이 어디까지 유지될 수 있는지를 나타내는 개념이다.
  • 일반적으로 스프링 빈은 스프링 컨테이너의 시작부터 끝까지 유지되는데 이를 싱글톤 스코프라고 말한다.
  • 그와는 다르게 스프링 컨테이너에서 객체의 생성과 의존관계 주입까지만 담당하고 이후에는 관리하지 않는 스코프가 있는데 그것을 프로토타입 스코프라고 한다.
    • 해당 스코프로 설정하는 방법은 @Scope("prototype")이라고 함으로서 설정이 가능하다. 
    • 이 프로토타입 스코프에 소속된 스프링 빈들은 해당 빈을 요청할 때 마다 새로운 인스턴스를 생성하여 반환한다. 즉 싱글톤으로 관리되고 있지 않다.
    • 이 반환받은 인스턴스는 스프링 컨테이너의 관리를 받지 않기 때문에 받은 클라이언트 객체에서 해당 인스턴스를 관리해야 한다.
  • 프로토타입 스코프는 클라이언트가 다이렉트로 해당 스코프의 빈을 요청하면 문제가 없으나, 싱글톤 스코프에 소속된 빈과 같이 사용해야 하는 경우에 문제가 발생할 수 있다.
  • 스프링 컨테이너는 모든 빈을 싱글톤으로 관리한다고 하였다. 그런데 싱글톤 스코프의 빈이 프로토타입 스코프의 빈을 주입받고 있는 상태라고 가정해 보자. 이 경우 클라이언트가 싱글톤 스코프의 빈을 계속해서 호출해도 프로토타입 스코프의 빈이 새로 주입되지 않는다.
    • 이는 스프링 컨테이너가 객체를 관리하는 방식 때문이다.
  • 이를 해결하는 방식이 Provider이다.
    • 스프링에서는 ObjectProvider 또는 ObjectFactory를 통해 의존관계를 주입받을 객체를 직접 찾는 동작을 수행한다.
    • 다른 방식으로는 자바 표준 기술인 Provider이다. 이 기술은 자바 표준으로 스프링에 독립적이기 때문에 이식성을 생각한다면 해당 방식을 사용하는게 좋다. 다만 라이브러리를 추가해야 한다.

 

 

 

빈 스코프 - 웹 스코프

  • 웹 스코프는 웹 상에서 사용되는 스코프인데 request 스코프를 예시로 설명한다.
  • request스코프는 HTTP request가 들어오면 생성되며 해당 request가 모두 처리되고 나가면 소멸되는 생명 주기를 가지는 스코프이다.
    • Session스코프, appliocation스코프나 다른 스코프들도 비슷하다. 다만 생명 주기의 범위가 다를 뿐이다.
  • 이 request 스코프 역시 싱글톤 스코프에 소속된 빈과 연관되게 되면 문제가 발생한다.
    • 싱글톤 스코프는 컨테이너가 시작되면 같이 객체가 생성되면서 이후 의존관계 주입을 받게 되는데 주입받을 빈이 request 스코프가 될 경우, 아직 HTTP reqeust가 들어오지 않아 객체가 생성되어있지 않은 상태가 된다.
    • 이로 인해 주입받을 빈이 존재하지 않아 예외가 발생하게 된다.
  • 해당 문제도 Provider로 해결이 가능하다. 싱글톤 스코프에 소속된 빈에서 request 스코프에 소속된 빈을 주입받는게 아닌 Provider를 주입받고, 요청이 들어오면 처리하면서 Provider를 통해 request 스코프에 소속된 빈을 가져온다.
  • 즉 request 스코프에 소속된 빈이 정말로 필요해지는 시점까지 해당 빈을 가져오는 것을 지연시킨다.
  • 다른 방식으로는 @Scope에 있는 proxyMode의 설정을 통해 프록시를 사용하여 해결할 수 있다.
    • 이 프록시는 가짜 스프링 빈을 생성하여 먼저 주입시키고, 해당 스프링 빈이 필요해지면 진짜 스프링 빈을 찾아 해당 빈의 로직을 수행하도록 한다.
    • 이 방법 역시 미리 가짜나 무언가를 주입하여 주입 단계에서 예외가 발생하지 않도록 막고, 정말로 해당 빈이 필요해질때 해당 빈을 찾아 호출함으로서 문제를 해결한다.

 

 

 

정리하며

  • 이렇게 스프링 프레임워크의 각 파트들을 짤막하게 요약하였다.
  • 웹을 생각하고 스프링 프레임워크를 파고들면 막상 코어 부분은 웹과 관련이 거의 없다는 것을 눈치첼 수 있는데 이는 스프링 코어단이 웹을 지원하기 위해서 생긴 부분이 아니기 때문이다.
    • 웹을 지원하는 부분은 SpringWeb MVC 부분에서 볼 수 있다.
  • 스프링 코어 부분은 스프링 컨테이너와 여러 기능을 통해 개발자가 객체지향적 프로그래밍을 더 효율적이고 편리하게 사용하는데 초점이 맞추어져 있다. 이를 유념하면서 접근하면 더 이해하기가 쉬워질 수 있을 것이다.
  • 다음 포스트부터는 Spring Web MVC에 대하여 다른 카테고리에서 연재된다.
복사했습니다!