스프링 핵심 원리 - 기본편을 듣고 (김영한님)

 

1. 들어가며

 

저번에 하나의 글로 정리한 입문 강의에 이어 스프링 핵심 원리 - 기본편 강의를 정리해보고자 한다. 이번 강의는 하나의 글로 정리하기엔 내용이 많아 중요한 주제의 경우 추가로 자료 조사를 해서 글을 따로 쓸 예정이다. 본 글에서는 강의의 핵심만 추려서 나중에 보기 편하게 정리하는 것이 목표이다.

 

2. 강의 내용

 

2.1 스프링이란?

 

스프링은 JAVA의 가장 큰 특징인 "객체 지향 언어"의 맛을 잘 살리는데 집중한 언어이다.

그렇다면 객체 지향 언어의 묘미는 무엇일까? 추상화, 캡슐화, 상속, 다형성 등 다양한 성질이 있고 모두 실제 코드에서 쓰일 수 있지만 스프링에서의 핵심은 바로 다형성이다. 각각의 컴포넌트가 유연하게 변경되면서 개발할 수 있는 것. 이를 위해 스프링은 역할과 구현을 분리한다.

 

• 클라이언트는 대상의 역할(인터페이스)만 알면 된다.

• 클라이언트는 구현 대상의 내부 구조를 몰라도 된다.

• 클라이언트는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않는다.

• 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다.

 

이는 잘 만들어진 인터페이스와 구현 클래스의 오버라이딩으로 달성할 수 있다. 예를 들어 영화 배역이 정해졌다면 그것을 구현하는 배우들은 연기력이 된다면 누구든 그 배역을 행할 수 있는 것과 같다. 영화 배역은 잘 만들어진 인터페이스이고 구현 클래스가 연기력이 되는 배우라 할 수 있다. 

실제 코드에서는 저장소의 틀을 잡는 인터페이스와 실제 그 저장소의 기능을 구현한 여러 클래스(DB에 따라 코드가 달라질 여러 구체적인 구현 클래스들)들의 구조이다.

 

이에 더해 그 구현 객체를 유연하게 변경할 수 있는 DI(Dependency Injection)을 스프링에서 적극 지원하여 좋은 객체 지향 설계가 가능하다. 스프링은 DI 컨테이너를 통해 역할 분리를 수행한다. 위의 배역 예시를 이어나가자면, 영화 배우가 직접 영화 배역을 정하는 것이 아닌 감독이 등장하여 각 배역에 배우들을 배정하는 것이라 할 수 있다.

실제 코드에서는 각 클래스가 필요로 하는 구현 객체들을 런타임에 DI 컨테이너가 배정해주는 구조이다.

 

이러한 구현을 스프링은 어노테이션을 통해 명료한 코드로 작성할 수 있게 해준다.

 

2.2 스프링 컨테이너

 

- 스프링 컨테이너란?

 

그렇다면 각자의 역할을 배정하는 스프링 컨테이너가 구체적으로 어떻게 작동하는지 알아야할 것이다. 구체적인 내용은 강의를 참조하길 바라고 핵심 내용만 정리하고자 한다.

 

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(설정클래스명.class);

로 정의되어있다고 해보자.

 

• 가장 기본적인 조회 방법 : ac.getBean(빈이름, 타입) || ac.getBean(타입) 

• 동일한 타입이 둘 이상이라면? ac.getBeanOfType()으로 해당 타입의 모든 빈이 조회되며 구체적으로 이름을 지정하여 하나만 조회할 수도 있다. 

• 부모 타입을 조회한다면 자식 타입도 함께 조회할 수 있다.

• ApplicationContext는 BeanFactory를 상속하며 이에 메시지소스를 활용한 국제화 기능, 환경 변수, 어플리케이션 이벤트, 편리한 리소스 조회 기능 등을 추가로 지원한다.

• 위의 코드블럭에서 정의한 annotation 방식 뿐만 아니라 XML, 자바 코드 방식 등 다양한 방식을 지원한다.

• 이러한 다양한 설정 형식은 BeanDefinition이라는 추상화를 통해 메타정보를 만들어 구현된다.

- BeanClassName: 생성할 빈의 클래스 명(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
- factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름
- scope: 싱글톤(기본값),
- lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생 성을 지연처리 하는지 여부
- InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
- DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
- Constructor arguments, Properties: 의존관계 주입에서 사용한다. (자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)

 

- 싱글톤 컨테이너

 

유저의 매 요청마다 새로운 객체를 만든다면 리소스 낭비가 심할 것이다. 따라서 스프링의 경우 싱글톤 패턴을 지원한다. 또한  싱글톤 패턴의 단점인 구현 코드가 길고 구체 클래스에 의존, 테스트의 어려움, 유연성 저하 등을 싱글톤 컨테이너를 통해 극복한다. 이는 위에서 설명한 스프링 컨테이너가 각 객체를 하나만 생성해서 관리하는 구조이다.

 

주의할 점은 싱글톤인 만큼 객체가 하나이기 때문에 무상태로 설계해야 된다는 점이다.

즉 특정 클라이언트에 의존적인 필드가 있으면 안되며 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.

 

이는 스프링이 CGLIB이라는 바이트 코드 조작 라이브러리를 통해 임의의 클래스가 싱글톤이 보장되도록 구현해줌으로써 기능한다.

 

- 컴포넌트 스캔

 

스프링 컨테이너에 등록하고 싶다면 @Bean으로 지정해줘도 되지만 @Component를 통해 자동으로 스프링 빈을 등록할 수 있다.@Component 외에도

• @Controller : 스프링 MVC 컨트롤러에서 사용

• @Service : 스프링 비즈니스 로직에서 사용

• @Repository : 스프링 데이터 접근 계층에서 사용

• @Configuration : 스프링 설정 정보에서 사용

또한 컴포넌트 스캔 대상에 포함된다.

 

물론 @Bean 등의 방법을 통해 수동으로 빈을 등록하는 것도 가능하다. 하지만 프로젝트가 커지고 설정 정보가 많아지면 관리가 부담이 된다. 따라서 정해진 패턴이 다수 등장하는 업무 로직 빈에서는 자동 빈 등록을 지향하고 기술 지원 로직의 경우 수동 빈 등록을 통해 명확하게 역할을 드러내는 것이 좋을 때가 많다. 

 

- 의존관계 자동 주입

 

@Autowired를 사용하면 의존관계도 자동으로 주입할 수 있다.

의존관계 주입에는 크게 

• 생성자 주입

• 수정자 주입(Setter 주입)

• 필드 주입

• 일반 메서드 주입

이 있다. 

대체로 생성자 주입을 권장하는데 순수 자바 코드로 테스트하기 편하며 불변성이 보장된다는 점이 긍정적인 설계 방향이기 때문이다. 생성자 주입일 경우 생성자가 단 하나라면 @Autowired가 없어도 자동 주입된다.

 조회 빈이 2개 이상이라면 NoUniqueBeanDefinitionException 오류가 발생한다. 이땐 @Qualifier와 @Primary 어노테이션을 통해 해결할 수 있다.

 

2.3 스프링 빈

 

- 빈 스코프 및 생명주기

 

1) 스코프

 

스프링 빈은 기본적으로 싱글톤이기 때문에 컨테이너 시작과 함께 생성되어 종료될 때까지 유지된다. 하지만 그 외에도 다양한 스코프를 지원한다.

 싱글톤: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프이다.

 프로토타입: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프이다.

 웹 관련 스코프

  - request: 웹 요청이 들어오고 나갈때 까지 유지되는 스코프이다.

  - session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프이다.

  - application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프이다.

 

싱글톤 스코프의 빈 안에 프로토타입의 빈을 사용하고 싶다면 싱글톤 빈이 생성될 때 프로토타입 빈이 생성되고 그대로 유지된다는 문제가 있다. 이걸 해결하기 위해서는

- ObjectFactory, ObjectProvider

- JSR-330 Provider

등을 활용하면 된다.

 

의문점 : 주변 지인과 대화를 나눴던 주제인데 과연 프로토타입의 스코프는 언제 사용될까? 이다. 안티 패턴과 효율성 저하에 빠지기 쉬운 스코프라는 생각이 든다. 관련하여 구글링, stackOverFlow 등 열심히 찾아보았지만 적절한 활용처는 잘 보이지 않는다..

 

웹 스코프의 경우 프록시 방식을 통해 가짜 객체를 먼저 주입해놓고 사용한다면 마치 싱글톤 빈을 사용하듯 편리하게 쓸 수 있다.

 

2) 생명 주기

스프링 빈은 간단하게 다음과 같은 라이프사이클을 가진다.

 

객체 생성 -> 의존관계 주입

 

즉 의존 관계 주입이 끝나고 나서 스프링 빈은 사용될 준비가 완료된다. 그럼 그 주입이 완료된 시점을 어떻게 알 수 있을까?

스프링은 콜백 메소드를 통해 스프링 빈의 초기화 시점과 종료 시점을 알려준다. 요약하자면 다음과 같다.

 

사이클 스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 사용 -> 소멸전 콜백  -> 스프링 종료

 

이러한 콜백은 다음 3가지 방법으로 지원되는데

 

•  인터페이스(InitializingBean, DisposableBean)

• 설정 정보에 초기화 메서드, 종료 메서드 지정

• @PostConstruct, @PreDestroy 애노테이션 지원

이다.

 

3. 나가며

 

구체적인 코드는 저작권 염려가 있어 최대한 배제하였다. 위 개념만 제대로 익히고 있다면 스프링 컨테이너와 스프링 빈의 핵심 개념은 확실히 익힐 수 있을 것이다!!