토비의 스프링 1장 (오브젝트와 의존관계)

스프링

스프링은 자바 엔터프라이즈 애플리케이션 개발에 사용되는 프레임워크다. 애플리케이션 프레임워크는 애플리케이션 개발을 빠르고 효율적으로 할 수 있도록 애플리케이션의 바탕이 되는 틀과 공통 프로 그래밍 모델, 기술 API 등을 제공해준다.

스프링은 스프링 컨테이너 또는 애플리케이션 컨텍스트라고 불리는 스프링 런타임 엔진을 제공한다. 스프링 컨테이너는 설정정보를 참고로 해서 애플리케이션을 구성하는 오브젝트를 생성하고 관리한다.

스프링은 세 가지 핵심 프로그래밍 모델을 지원한다.

  • IOC/DI : 오브젝트의 생명주기와 의존관계에 대한 프로그래밍 모델
  • 서비스 추상화 : 환경이나 서버, 특정 기술에 종속되지 않고 이식성이 뛰어나며 유연한 애플리케이션을 만들 수 있다.
  • AOP : 애플리케이션 코드에 산재해서 나타나는 부가적인 기능을 독립적으로 모듈화하는 프로그래밍 모델

자바빈

다음과 같은 두 가지 관례를 따라 만들어진 오브젝트를 말한다. 간단히 빈이라고 부르기도 한다.

  • 디폴트 생성자 : 자바빈은 파라미터가 없는 디폴트 생성자를 갖고 있어야 한다. 툴이나 프레임워크에서 리플렉션을 이용해 오브젝트를 생성하기 때문에 필요하다.
  • 프로퍼티 : 자자빈이 노출하는 이름을 가진 속성을 프로퍼티라고 한다. 프로퍼티는 setter와 getter를 이용해 수정 또는 조회할 수 있다.

디자인 패턴 : 소프트웨어 설계 시 특정 상황에서 자주 만나는 문제를 해결하기 위해 사용할 수 있는 재사용 가능한 솔루션. 주로 객체지향 설계에 관한 것이고, 대부분 객체지향적 설계 원칙을 이용해 문제를 해결한다.

  • 템플릿 메소드 패턴
    상속을 통해 슈퍼클래스의 기능을 확장할 때 사용하는 가장 대표적인 방법이다. 변하지 않는 기능은 슈퍼클래스에 만들어두고 자주 변경되며 확장할 기능은 서브클래스에 만들도록 한다. 슈퍼클래스에서는 미리 추상메소드 또는 오버라이드 가능한 메소드를 정의해두고 이를 활용해 코드의 기본 알고리즘을 담고 있는 템플릿 메소드를 만든다.
  • 팩토리 메소드 패턴
    슈퍼클래스 코드에서는 서브클래스에서 구현할 메소드를 호출해서 필요한 타입의 오브젝트를 가져와 사용한다. 이 메소드는 주로 인터페이스 타입으로 오브젝트를 리턴하므로 서브클래스에서 정확히 어떤 클래스의 오브젝트를 만들어 리턴할지는 슈퍼클래스에서는 알지 못한다. 팩토리 메소드와 메소드 패턴의 팩토리 메소드는 의미가 다르므로 혼동하지 않도록 주의해야 한다.

클래스 사이의 관계와 오브젝트 사이의 관계를 구분할 수 있어야 한다.

  • 클래스 사이의 관계 : 코드에 다른 클래스의 이름이 나타나기 때문에 만들어지는 것이다.
  • 오브젝트 사이의 관계 : 코드에서는 특정 클래스를 전혀 알지 못하더라도 해당 클래스가 구현한 인터페이스를 사용했다면, 그 클래스의 오브젝트를 인터페이스 타입으로 받아서 사용할 수 있다.

개방 폐쇄 원칙 (OCP, Open-Closed Principle)

클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다. 인터페이스를 사용해 확장 기능을 정의한 대부분의 API는 바로 이 개방 폐쇄 원칙을 따른다.

개방 폐쇄 원칙은 높은 응집도와 낮은 결합도라는 소프트웨어 개발의 고전적인 원리로도 설명이 가능하다.

전략 패턴

개방 폐쇄 원칙의 실현에도 가장 잘 들어맞는 패턴이다. 전략 패턴은 자신의 기능 맥락(context)에서, 필요에 따라 변경이 필요한 알고리즘(독립적인 책임으로 분리가 가능한 기능)을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인패턴이다.

전략 패턴의 적용방법을 보면 클라이언트의 역할이 잘 설명되어 있다. 컨텍스트를 사용하는 클라이언트는 컨텍스트가 사용할 전략을 컨텍스트의 생성자 등을 통해 제공해주는게 일반적이다.

제어의 역전 (IOC, Inversion Of Control)
제어의 역전이라는 건, 간단히 프로그램의 제어 흐름 구조가 뒤바뀌는 것이다. 제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다. 당연히 생성하지도 않는다. 또 자신도 어떻게 만들어지고 어디서 사용되는지를 알 수 없다. 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문이다.

서블릿이나 JSP, EJB처럼 컨테이너 안에서 동작하는 구조는 간단한 방식이긴 하지만 제어의 역전 개념이 적용되어 있다고 볼 수 있다.

프레임워크도 제어의 역전 개념 적용된 대표적인 기술이다. 라이브러리와 프레임워크의 차이점에 대해 설명하면, 라이브러리를 사용하는 애플리케이션 코드는 애플리케이션 흐름을 직접 제어한다. 단지 동작하는 중에 필요한 기능이 있을 때 능동적으로 라이브러리를 사용할 뿐이다. 반면에 프레임워크는 거꾸로 애플리케이션 코드가 프레임워크에 의해 사용된다. 보통 프레임워크 위에 개발한 클래스를 등록해주고, 프레임워크가 흐름을 주도하는 중에 개발자가 만든 애플리케이션 코드를 사용하도록 만드는 방식이다.

제어의 역전에서는 프레임워크 또는 컨테이너와 같이 애플리케이션 컴포넌트의 생성과 관계설정, 사용, 생명주기 관리 등을 관장하는 존재가 필요하다. 스프링은 IoC를 모든 기능의 기초가 되는 기반기술로 삼고 있으며, IoC를 극한까지 적용하고 있는 프레임워크다.

스프링의 IoC

오브젝트 팩토리를 이용한 스프링 IoC

스프링에서는 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트를 빈(bean)이라고 부른다. 자바빈 또는 엔터프라이즈 자바빈(EJB)에서 말하는 빈과 비슷한 오브젝트 단위의 애플리케이션 컴포넌트를 말한다. 동시에 스프링 빈은 스프링 컨테이너가 생성과 관계설정, 사용 등을 제어해주는 제어의 역전이 적용된 오브젝트를 가리키는 말이다.

스프링에서는 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트를 빈 팩토리(bean factory)라고 부른다. 보통 빈 팩토리보다는 이를 좀 더 확장한 애플리케이션 컨텍스트(applcation context)를 주로 사용한다.

애플리케이션 컨텍스트는 그 자체로는 애플리케이션 로직을 담당하지는 않지만 IoC 방식을 이용함으로써, 별도의 정보를 참고해서 빈의 생성과 관계설정 등의 제어 작업을 총괄한다.

  • **@Configuration **: 애플리케이션 컨텍스트 또는 빈 팩토리가 사용할 설정정보라는 표시
  • @Bean : 오브젝트 생성을 담당하는 IoC용 메소드라는 표시

애플리케이션 컨텍스트는 ApplicationContext 타입의 오브젝트다. ApplicationContext를 구현한 클래스는 여러 가지가 있는데 DaoFactory처럼 @Configuration이 붙은 자바 코드를 설정정보로 사용하려면 AnnotationConfigApplicationContext를 이용하면 된다.

getBean() 메소드는 ApplicationContext가 관리하는 오브젝트를 요청하는 메소드다. getBean()은 기본적으로 Object 타입으로 리턴하게 되어 있어서 매번 리턴되는 오브젝트에 다시 캐스팅을 해줘야 하는 부담이 있다. 그러나 자바 5 이상의 제네릭 메소드 방식을 사용해 getBean()의 두 번째 파라미터에 리턴 타입을 주면, 지저분한 캐스팅 코드를 사용하지 않아도 된다.

오브젝트 팩토리에서 사용했던 IoC 원리를 그대로 적용하는데 애플리케이션 컨텍스트를 사용하는 이유는 범용적이고 유연한 방법으로 IoC 기능을 확장하기 위해서다. 애플리케이션 컨텍스트를 사용했을 때 얻을 수 있는 장점은 다음과 같다.

  • 클라이언트는 구체적인 팩토리 메서드를 알 필요가 없다.
  • 애플리케이션 컨텍스트는 종합 IoC 서비스를 제공해준다.
  • 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다.

스프링 IoC의 용어 정리


  • 빈 또는 빈 오브젝트는 스프링이 IoC 방식으로 관리하는 오브젝트라는 뜻이다. 주의할 점은 스프링을 사용하는 애플리케이션에서 만들어지는 모든 오브젝트가 다 빈은 아니라는 사실이다. 그 중에서 스프링이 직접 그 생성과 제어를 담당하는 오브젝트만을 빈이라고 부른다.
  • 빈 팩토리
    스프링의 IoC를 담당하는 핵심 컨테이너를 말한다. 빈을 등록하고, 생성하고, 조회하고 돌려주고, 그 외에 부가적인 빈을 관리하는 기능을 담당한다.
  • 애플리케이션 컨텍스트
    빈 팩토리를 확장한 IoC.컨테이너다. 스프링이 제공하는 각종 부가 서비스를 추가로 제공한다. 애플리케이션 컨텍스트 오브젝트는 하나의 애플리케이션에서 보통 여러 개가 만들어져 사용된다.
  • 설정정보/설정 메타정보
    스프링의 설정정보란 애플리케이션 컨텍스트 또는 빈 팩토리가 IoC를 적용하기 위해 사용하는 메타정보를 말한다. IoC 컨테이너에 의해 관리되는 애플리케이션 오브젝트를 생성하고 구성할 때 사용된다.
  • 컨테이너 또는 IoC 컨테이너
    IoC 방식으로 빈을 관리한다는 의미에서 애플리케이션 컨텍스트나 빈 팩토리를 컨테이너 또는 IoC 컨테이너라고도 한다. 그냥 컨테이너 또는 스프링 컨테이너라고 할 때는 애플리케이션 컨텍스트를 가리키는 것이라고 보면 된다.

싱글톤 레지스트리로서의 애플리케이션 컨텍스트

오브젝트의 동일성과 동등성

자바에서는 두 개의 오브젝트가 같은가라는 말을 주의해서 써야 한다. 자바에서는 두개의 오브젝트가 완전히 같은 동일한 오브젝트라고 말하는 것(동일성)과, 동일한 정보를 담고 있는 오브젝트라고 말하는 것(동등성)은 분명한 차이가 있다. 물론 동일한 오브젝트는 동등하기도 하다.

동일성은 == 연산자로, 동등성은 equals() 메소드를 이용해 비교한다.

스프링의 애플리케이션 컨텍스트는 기존에 직접 만든 오브젝트 팩토리와는 중요한 차이점이 있다. 스프링은 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다는 것이다. 애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리이기도 하다.

스프링은 기본적으로 별다른 설정을 하지 않으면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다. (디자인 패턴에서 나오는 싱글톤 패턴과 비슷한 개념이지만 그 구현 방법은 확연히 다르다.)

싱글톤 패턴의 한계

일반적인 싱글톤 패턴 구현 방식에는 다음과 같은 문제(한계)가 있다.

  • private 생성자를 갖고 있기 때문에 상속할 수 없다.
  • 싱글톤은 테스트하기가 힘들다.
  • 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
  • 싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못하다.

싱글톤 레지스트리

스프링은 서버환경에서 싱글톤이 만들어져서 서비스 오브젝트 방식으로 사용되는 것은 적극 지지한다. 그러나 자바의 기본적인 싱글톤 패턴의 구현 방식은 여러 가지 단점이 있기 때문에, 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다. 그것이 바로 싱글톤 레지스트리

싱글톤 레지스트리의 장점은 스태틱 메소드와 private 생성자를 사용해야 하는 비정상적인 클래스가 아니라 평범한 자바 클래스를 싱글톤으로 활용하게 해준다는 것이다.

스프링의 싱글톤 레지스트리 덕분에 싱글톤 방식으로 사용될 애플리케이션 클래스라도 public 생성자를 가질 수 있다.

싱글톤과 오브젝트의 한계

싱글톤은 멀티스레드 환경이라면 여러 스레드가 동시에 접근해서 사용할 수 있다. 따라서 상태 관리에 주의를 기울여야 한다. 기본적으로 싱글톤이 멀티스레드 환경에서 서비스 형태의 오브젝트로 사용되는 경우에는 상태정보를 내부에 갖고 있지 않은 무상태(stateless) 방식으로 만들어져야 한다.

무상태 방식으로 만들기 위해서는 메소드 안에서 생성되는 로컬 변수를 사용하면 된다. 로컬 변수는 매번 새로운 값을 저장할 독립적인 공간이 만들어지기 때문에 싱글톤이라고 해도 여러 스레드 변수의 값을 덮어쓸 일은 없다.

따라서 스프링의 싱글톤 빈으로 사용되는 클래스를 만들 때는 개별적으로 바뀌는 정보는 로컬 변수로 정의하거나, 파라미터로 주고받으면서 사용하게 해야 한다.

그러나, 자신이 사용하는 다른 싱글톤 빈을 저장하려는 용도라면 인스턴스 변수를 사용해도 좋다. 스프링이 한 번 초기화해주고 나면 이후에는 수정되지 않기 때문에 멀티스레드 환경에서 사용해도 아무런 문제가 없다.

스프링 빈의 스코프

스프링이 관리하는 오브젝트, 즉 빈이 생성되고, 존재하고, 적용되는 범위를 빈의 스코프(scope)라고 한다. 스프링 빈의 기본 스코프는 싱글톤이다.

경우에 따라서는 싱글톤 외의 스코프를 가질 수 있다. 그 예로 웹을 통해 새로운 HTTP 요청이 생길 때마다 생성되는 요청(request) 스코프가 있고, 웹의 세션과 스코프가 유사한 세션(session) 스코프도 있다.

의존관계 주입 (DI)

IoC가 매우 느슨하게 정의돼서 폭넓게 사용되는 용어이기 때문에 스프링을 IoC 컨테이너라고만 해서는 스프링이 제공하는 기능의 특징을 명확하게 설명하지 못한다.

그래서 스프링이 제공하는 IoC 방식을 핵심을 짚어주는 의존관계 주입(Dependency Injection)이라는, 좀 더 의도가 명확한 이름을 사용하기 시작했다.

스프링이 다른 프레임워크와 차별화돼서 제공해주는 기능은 의존관계 주입이라는 새로운 용어를 사용할 때 분명하게 드러난다.

DI는 오브젝트 레퍼런스를 외부로부터 제공(주입)받고 이를 통해 다른 오브젝트와 다이내믹하게 의존관계가 만들어지는 것이 핵심이다.

런타임 의존관계 설정

모델이나 코드에서 클래스와 인터페이스를 통해 드러나는 의존관계 말고, 런타임 시에 오브젝트 사이에서 만들어지는 의존관계도 있다. 설계 시점의 의존관계가 실체화된 것이다.

런타임 시에 의존관계를 맺는 대상, 즉 실제 사용대상인 오브젝트를 의존 오브젝트라고 한다.

의존관계 주입은 구체적인 의존 오브젝트와 그것을 사용할 주체, 보통 클라이언트라고 부르는 오브젝트를 런타임 시에 연결해주는 작업을 말한다.

의존관계 주입이란 다음의 세 가지 조건을 충족하는 작업을 말한다.

  • 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야 한다.
  • 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.
  • 이존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.

의존관계 주입의 핵심은 설계 시점에는 알지 못했던 두 오브젝트의 관계를 맺도록 도와주는 제3의 존재가 있다는 것이다. 스프링의 애플리케이션 컨텍스트, 빈 팩토리, IoC 컨테이너 등이 모두 외부에서 오브젝트 사이의 런타임 관계를 맺어주는 책임을 지닌 제 3의 존재이다.

DI는 자신이 사용할 오브젝트에 대한 선택과 생성 제어권을 외부로 넘기고 자신은 수동적으로 주입받은 오브젝트를 사용한다는 점에서 IoC의 개념에 잘 들어맞는다.

의존관계 검색과 주입

스프링이 제공하는 IoC 방법에는 의존관계 주입만 있는 것이 아니다.

의존관계를 맺는 방법이 외부로부터의 주입이 아니라 스스로 검색을 이용하기 때문에 의존관계 검색이라고 불리는 것도 있다. 의존관계 검색은 자신이 필요로 하는 의존 오브젝트를 능동적으로 찾는다.

의존관계 검색은 런타임 시 의존관계를 맺을 오브젝트를 결정하는 것과 오브젝트의 생성작업은 외부 컨테이너에게 IoC로 맡기지만, 이를 가져올 때는 메소드나 생성자를 통한 주입 대신 스스로 컨테이너에게 요청하는 방법을 사용한다.

스프링의 IoC와 DI 컨테이너를 적용했다고 하더라도 애플리케이션의 기동 시점에서 적어도 한 번은 의존관계 검색 방식을 사용해 오브젝트를 가져와야 한다. static 메소드인 main()에서는 DI를 이용해 오브젝트를 주입받을 방법이 없기 때문이다.

의존관계 검색(DL)과 의존관계 주입을 적용할 때 발견할 수 있는 중요한 차이점이 하나 있다. 의존관계 검색 방식에서는 검색하는 오브젝트는 자신이 스프링의 빈일 필요가 없다.

반면에 의존관계 주입에서는 오브젝트 사이에 DI가 적용되려면 반드시 두 오브젝트 모두 컨테이너가 만드는 빈 오브젝트여야 한다.

참고

토비의 스프링 3.1 Vol. 1 스프링의 이해와 원리

Spring - MVC

DispatcherServlet 이란?

Spring MVC는 DispatcherServlet의 등장으로 web.xml의 역할이 축소되었습니다. 이전에는 서블릿을 URL로 활용하기 위해서는 반드시 web.xml에 등록해야 했지만, 이제는 DispatcherServlet이 해당 어플리케이션으로 들어오는 요청을 모두 핸들링 해주기 때문입니다.

web.xml의 역할이 축소되었지만, <servlet>으로 DispatcherServlet을 등록해줘야 하며, 이 객체의 URL 적용범위 또한 web.xml에 설정해야 합니다. 또한 encoding과 관련된 <filter><listener>를 등록하기 위해서 web.xml은 필요합니다.

그러나 web.xml에서 중요하게 사용되었던 <servlet> 매핑은 이제 DispatcherServlet이 대신 맡아서 처리하게 되었습니다. web.xml에 DispatcherServlet의 을 ‘/‘로 설정함으로써 동시에 이제 모든 요청은 DispatcherServlet으로 전달됩니다. 물론 DispatcherServlet을 web.xml에 등록해도 계속 서블릿을 web.xml에 매핑해서 사용할 수 있지만, 이런 옛 방식을 버리고 DispatcherServlet을 이용해 웹 개발을 한다면 앞으로 서블릿 파일을 만들 필요도 없어지고 동시에 놀라운 @MVC의 혜택을 얻을 수 있습니다.

DispatcherServlet을 이용한다는 것은 스프링에서 제공하는 @MVC를 이용하겠단 뜻입니다. @MVC는 그동안 추상적으로 알아오고 발전했던 MVC(Model, View, Controller) 설계 영역을 노골적으로 분할하여 사용자가 무조건 MVC로 어플리케이션을 설계하게끔 유도하는 방식입니다. 즉, @MVC를 이용해 어플리케이션을 개발한다면 MVC 설계의 원칙대로 웹 어플리케이션을 제작할 수 있게 된다는 뜻입니다.

그럼 간단하게 DispatcherServlet이 담당하는 역할이 무엇인지 알아봅시다. 먼저 DispatcherServlet에 대해 간단히 정의해보자면, 각각 분리하여 만든 Model, View, Controller를 조합하여 브라우저로 출력해주는 역할을 수행합니다.

Spring MVC 구조

등장 요소

  • DispatcherServlet : 프런트 컨트롤러 담당, 모든 HTTP 요청을 받아들여 그 밖의 오브젝트 사이의 흐름을 제어, 기본적으로 스프링 MVC의 DispatcherServlet 클래스를 그대로 적용
  • HandlerMapping : 클라이언트의 요청을 바탕으로 어느 컨트롤러를 실행할지 결정
  • Model : 컨트롤러에서 뷰로 넘겨줄 오브젝트를 저장하기 위한 오브젝트, HttpServletRequest와 HttpSession처럼 String 형 키와 오브젝트를 연결해서 오브젝트를 유지
  • ViewResolver : View 이름을 바탕으로 View 오브젝트를 결정
  • View : 뷰에 화명 표시 처리를 의뢰
  • 비즈니스 로직 : 비즈니스 로직을 실행. 애플리케이션 개발자가 비즈니스 처리 사양에 맞게 작성
  • 컨트롤러(Controller) : 클라이언트 요청에 맞는 프레젠테이션 층의 애플리케이션 처리를 실행해야 함. 애플리케이션 개발자가 애플리케이션 처리 사양에 맞게 작성
  • 뷰 / JSP 등 : 클라이언트에 대해 화면 표시 처리. 자바에서는 JSP 등으로 작성하는 일이 많으며, 애플리케이션 개발자가 화면의 사양에 맞게 작성

동작 순서

  1. DispatcherServlet은 브라우저로부터 요청을 받아들입니다.
  2. DispatcherServlet은 요청된 URL을 HandlerMapping 오브젝트에 넘기고 호출 대상의 컨트롤러 오브젝트를 얻어 URL에 해당하는 메서드를 실행합니다.
  3. 컨트롤러 오브젝트는 비즈니스 로직으로 처리를 실행하고, 그 결과를 바탕으로 뷰에 전달할 오브젝트를 Model 오브젝트에 저장합니다. 끝으로 컨트롤러 오브젝트는 처리 결과에 맞는 View 이름을 반환합니다.
  4. DispatcherServlet은 컨트롤러에서 반환된 View 이름을 ViewResolver에 전달해서 View 오브젝트를 얻습니다.
  5. DispatcherServlet은 View 오브젝트에 화면 표시를 의뢰합니다.
  6. View 오브젝트는 해당하는 뷰를 호출해서 화면 표시를 의뢰합니다.
  7. 뷰는 Model 오브젝트에서 화면 표시에 필요한 오브젝트를 가져와 화면 표시 처리를 실행합니다.

참고

Spring - RequestBody & ResponseBody

웹 서비스와 RESTful한 방식이 시스템을 구성하는 주요 요소로 자리 잡으면서 웹 시스템간에 XML이나 JSON 등의 형식으로 데이터를 주고 받는 경우가 증가했습니다.

이에 따라 스프링 MVC도 **클라이언트에서 전송한 XML 데이터나 JSON 또는 기타 데이터를 컨트롤러에서 DOM 객체나 자바 객체로 변환해서 받을 수있는 기능(수신)**을 제공하고 있으며, 비슷하게 **자바 객체를 XML이나 JSON 또는 기타 형식으로 변환해서 전송할 수 있는 기능(송신)**을 제공하고 있습니다.

@RequestBody 어노테이션과 @ResponseBody 어노테이션은 각각 HTTP 요청의 body 부분을 자바 객체로 변환하고 자바 객체를 HTTP 응답 body로 변환하는데 사용됩니다.

@RequestBody

Spring MVC 컨트롤러에서 HTTP 요청의 body 부분을 자바 객체로 mapping할 때 @RequestBody 어노테이션을 사용합니다. @RequestBody 어노테이션의 기능은 다음과 같습니다.

  • @ReuqestBody를 사용하지 않는 경우 : query parameter, form data를 객체에 mapping한다.
  • @ReuqestBody를 사용하는 경우 : body에 있는 data를 HttpMessageConverter를 이용해 선언한 객체에 mapping한다.

@ResponseBody

@ResponseBody는 @RequestBody와 비슷한 방식으로 동작합니다. @ResponseBody가 메소드 레벨에서 부여되면 메소드가 리턴하는 오브젝트는 ContentNegotiatingViewResolver를 이용해 뷰를 통해 결과를 만들어내는 것이 아닌, message converter를 통해 바로 HTTP 응답의 메시지 본문으로 변환됩니다.

ContentNegotiatingViewResolver는 등록되어 있는 ViewResolver중에서 controller 메소드의 리턴값을 통해 등록된 ViewResolver 중에서 적합한 형태로 처리해서 반환하는 반면, @ResponseBody는 @RequestBody가 선택한 형식으로 결과값을 변환하여 반환한다고 보면 됩니다.

@RestController는 @Controller와 @ResponseBody를 동시에 사용하는 것과 같습니다. @Controller를 사용하는 경우에만 @ResponseBody를 추가하면 됩니다.

HttpMessageConverter를 이용한 변환 처리

AnnotationMethodHandlerAdapter에는 HttpMessageConverter 타입의 메시지 변환기인 message converter가 여러 개 등록되어 있습니다. @RequestBody가 붙은 파라미터가 있으면 HTTP 요청의 미디어 타입과 파라미터의 타입을 먼저 확인하고, message converter 중에서 해당 미디어 타입과 파라미터 타입을 처리할 수 있다면, HTTP 요청의 body 부분을 통째로 변환해서 지정된 메소드 파라미터로 전달해줍니다.

HttpMessageConverter의 종류

AnnotationMethodHandlerAdapter 클래스는 @RequestBody 어노테이션이 적용된 파라미터나 @ResponseBody 어노테이션이 적용된 메서드에 대해 HttpMessageConverter를 사용해서 변환을 처리합니다. 주요 HttpMessageConverter 구현 클래스는 다음과 같습니다.

  • ByteArrayHttpMessageConverter : HTTP 메시지와 byte 배열 사이의 변환을 처리한다. 컨텐츠 타입은 application/octet-stream이다.
  • StringHttpMessageConverter : HTTP 메시지와 String 사이의 변환을 처리한다. 컨텐츠 타입은 text/plain;charset=ISO-8859-1이다.
  • SourceHttpMessageConverter : HTTP 메시지와 javax.xml.transform.Source 사이 변환을 처리한다. 컨텐츠 타입은 application/xml 또는 text/xml이다.
  • FormHttpMessageConverter : HTML 폼 데이터를 MultiValueMap으로 전달받을 때 사용된다. 지원하는 컨텐츠 타입은 application-x-www-form-urlencorded이다.
  • MappingJacksonHttpMessageConverter : Jackson 라이브러리를 이용해서 JSON HTTP 메시지와 객체 사이의 변환을 처리한다. 컨텐츠 타입은 applicaion/json이다.
  • MarshallingHttpMessageConverter : 스프링의 Marshaller와 unMarshaller를 이용해서 XML HTTP 메시지와 객체 사이의 변환을 처리한다. 컨텐츠 타입은 application/xml 또는 text/xml이다.

Content-Type과 Accept header 기반의 변환 처리

AnnotationMethodHandlerAdapter가 HttpMessageConverter를 이용해서 request의 body 데이터를 @RequestBody 어노테이션이 적용된 자바 객체로 변환할 때에는, HTTP 요청 header의 Content-Type에 명시된 미디어 타입(MIME)을 지원하는 HttpMessageConverter 구현체를 선택합니다.
예를 들어, 요청 미디어 타입이 application/json이고 @RequestBody 어노테이션이 적용된 경우 MappingJacksonHttpMessageConverter가 선택됩니다.

비슷하게 @ResponseBody 어노테이션을 이용해서 리턴한 객체를 HTTP 응답 객체로 변환할 때에는 HTTP 요청 header의 Accept에 명시된 미디어 타입(MIME)을 지원하는 HttpMessageConveter 구현체를 선택합니다.
예를 들어, Accept에 명시된 값이 application/json이고 @ResponseBody 어노테이션이 적용된 메서드의 리턴 타입이 자바 객체인 경우 MappingJacksonHttpMessageConverter가 선택됩니다.

참고

Http Message Converters with the Spring Framework

Spring - AOP

AOP의 등장 배경

몇 년에 걸쳐 객체지향 프로그래밍(Object Oriented Programming, OOP)은 절차적 프로그래밍 방법론을 거의 완벽히 대체하며 프로그래밍 방법론의 새로운 패러다임으로 떠오르게 되었습니다. 객체지향적 방식의 가장 큰 이점 중 하나는 소프트웨어 시스템이 여러 개의 독립된 클래스들의 집합으로 구성된다는 것입니다. 이들 각각의 클래스들은 잘 정의된 고유 작업을 수행하게 되고, 그 역할 또한 명백히 정의되어 있습니다.

객체지향 어플리케이션에서는 어플리케이션이 목표한 동작을 수행하기 위해 이런 클래스들이 서로 유기적으로 협력합니다. 하지만 시스템의 어떤 기능들은 특정 한 클래스가 도맡아 처리할 수 없습니다. 이들은 시스템 전체에 걸쳐 존재하며 해당 코드들을 여러 클래스들에서 사용합니다. 이런 현상을 횡단적(cross-cutting)이라 표현합니다. 분산 어플리케이션에서의 동기화(locking) 문제, 예외 처리, 로깅 등이 그 예입니다. 물론 필요한 모든 클래스들에 관련 코드를 집어 넣으면 해결될 문제입니다. 하지만 이런 행위는 각각의 클래스는 잘 정의된(well-defined) 역할만을 수행한다는 기본 원칙에 위배됩니다. 이런 상황이 바로 Aspect Oriented Programming (AOP)가 생겨난 원인이 되었습니다.

AOP에서는 aspect라는 새로운 프로그램 구조를 정의해 사용합니다. 쉽게 class, interface 등과 같이 특정한 용도의 구조라 생각하면 됩니다. Aspect 내에는 프로그램의 여러 모듈들에 흩어져 있는 기능(하나의 기능이 여러 모듈에 흩어져 있음을 뜻함)을 모아 정의하게 됩니다. 전체적으로, 어플리케이션의 각각의 클래스는 자신에게 주어진 기능만을 수행하고, 추가된 각 aspect들이 횡단적인 행위(기능)들을 모아 처리하며 전체 프로그램을 이루는 형태가 만들어집니다.

AOP가 필요한 사례

이해를 돕기 위해 어플리케이션의 여러 스레드들이 하나의 데이터를 공유하는 상황을 가정해봅시다. 공유 데이터는 Data라는 객체(Data 클래스의 인스턴스)로 캡슐화되어 있습니다. 서로 다른 여러 클래스의 인스턴스들이 하나의 Data 객체를 사용하고 있으나, 이 공유 데이터에 접근할 수 있는 객체는 한 번에 하나씩이어야만 합니다. 그렇다면 어떤 형태이건 동기화 메커니즘이 도입되어야 할 것입니다. 즉, 어떤 한 객체가 데이터를 사용중이라면 Data 객체는 잠겨(lock)져야 하며, 사용이 끝났을 때 해제(unlock)되어야 합니다. 전통적인 해결책은 공유 데이터를 사용하는 모든 클래스들이 하나의 공통 부모 클래스(“worker” 라 부르겠습니다)로부터 파생되는 형태로 만드는 것입니다. worker 클래스에는 lock()과 unlock() 메소드를 정의하여 작업의 시작과 끝에 이 메소드를 호출토록 하면 됩니다. 하지만 이런 형태는 다음과 문제들을 파생시킵니다.

공유 데이터를 사용하는 메소드는 상당히 주의해서 작성되어야 합니다. 동기화 코드를 잘못 삽입하면 데드락(dead-lock)이 발생하거나 데이터 영속성이 깨질 수 있습니다. 또한 메소드 내부는 본래의 기능과 관련 없는 동기화 관련 코드들로 더럽혀질 것입니다.
Java와 같은 단일 상속 모델에서는 worker를 만든다는 것이 불가능할 수 있습니다. 어떤 클래스들은 이미 다른 클래스들로부터 확장되었을 수도 있기 때문입니다. 이는 특히 클래스 계층 구조 설계가 마무리된 후, 뒤늦게 동기화의 필요성을 깨달았을 때 흔히 발생합니다. 동기화를 신경 쓰지 않은 범용 클래스 라이브러리를 통해 공유 데이터에 접근하려 하는 경우가 한 예가 될 수 있습니다.
앞서 가정한 어플리케이션에서 동기화 개념은 다음과 같은 속성들을 갖습니다.

  1. 동기화는 worker 클래스에 할당된 최우선 작업이 아니다.
  2. 동기화 메커니즘은 worker 클래스의 최우선 작업과 독립적이다.
  3. 한 객체에 대한 동기화 관련 코드가 시스템 전체에 횡단적으로 존재한다. 다수의 클래스와 더 많은 수의 메소드들이 이 동기화 메커니즘에 영향 받는다.

AOP에서는 이런 형태의 문제를 해결하기 위해 새로운 형태의 접근 방법을 제기하고 있습니다. AOP는 새로 도입된 프로그램 구조를 통해 시스템에 횡단되어 있는 기능들을 정의해 처리하도록 했습니다. 이 새로운 구조를 aspect라 부릅니다.

위의 예시에 Lock이라는 aspect를 도입해보겠습니다. Lock aspect에는 다음과 같은 역할이 할당될 것입니다.

  1. Data 객체를 사용하는 클래스들을 위해 lock 및 unlock 메커니즘을 제공한다(lock(), unlock()).
  2. Data 객체를 수정하는 모든 메소드들이 수행 전에 lock()을 호출하고, 수행 후에는 unlock()을 호출함을 보장한다.
  3. 이상의 기능을 Data 객체를 사용하는 클래스의 자바 소스를 변경하지 않고 투명하게 수행한다.

Aspect는 또 어떤 일들을 수행할 수 있을까?

특정 메소드(ex. 객체 생성 과정 추적) 호출을 로깅할 경우 aspect가 도움이 될 수 있습니다. 기존 방법대로라면 log() 메소드를 만들어 놓은 후, 자바 소스에서 로깅을 원하는 메소드를 찾아 log()를 호출하는 형태를 취해야할 것입니다. 그러나 여기서 AOP를 사용하면 원본 자바 코드를 수정할 필요 없이 원하는 위치에서 원하는 로깅을 수행할 수 있습니다. 이런 작업 모두는 aspect라는 외부 모듈에 의해 수행됩니다.
또 다른 예로 예외 처리가 있습니다. Aspect를 이용해 여러 클래스들의 산재된 메소드들에 영향을 주는 catch() 조항(clause)을 정의해 어플리케이션 전체에 걸쳐 지속적이고 일관적으로 예외를 처리할 수 있습니다.

AOP 용어

  • JoinPoint : 메소드 호출이나 특정 예외를 던지는 것과 프로그램이 실행되는 지점을 이야기한다.
  • Advice : Logging과 같은 횡단관심사의 경우 거의 모든 클래스에 분산되어 있는 것을 볼 수 있다. 이와 같은 횡단관심사를 여러 영역에 분산해 구현하는 것이 아니라 한 곳에 모아서 구현하는 것을 Advice라고 한다. 즉, JoinPoint에서 실행되는 코드를 말한다.
  • Point-cut : 횡단관심사에 해당하는 기능을 구현한 부분이 Advice라고 했다. 그렇다면 이렇게 구현되어 있는 Advice를 어떤 패턴을 가지는 클래스와 메소드에 적용할지를 결정하는 것이 Point-cut이다. 즉 해당 Advice가 적용되어야 하는 곳을 가리키는 것이 Point-cut이다. Point-cut은 JoinPoin와 Advice의 중간에 있으면서 처리가 JoinPoint에 이르렀을 때 Advice를 호출할지를 결정한다.
  • Aspect : Aspect는 Advice와 Point-cut을 합쳐서 하나의 Aspect라고 칭한다. Advice와 Point-cut을 이용하여 Logging이라는 관심사를 분리하여 독립적으로 구현할 수 있었다. 이처럼 Advice와 Point-cut을 이용하여 원하는 관심사를 구현하는 것을 하나의 Aspect라고 한다. 지금까지 살펴본 Logging은 Logging Aspect가 될 것이다.
  • Introduction : 실행되고 있는 클래스에 새로운 인터페이스를 추가하여 원래의 Object가 가지고 있는 속성, 행위 이외의 다른 일이 가능하도록 하게 된다.

Spring AOP의 Advice는 여러개의 Advice를 가집니다. Spring에서 지원하고 있는 Advice는 다음과 같습니다.

  • Before advice : JoinPoint 앞에서 수행되는 Advice. 하지만 JoinPoint를 위한 수행 흐름 처리(execution flow proceeding)를 막기위한 능력(만약 예외를 던지지 않는다면)을 가지지는 않는다.
  • After returning advice : JoinPoint가 완전히 정상 종료한 다음 실행되는 Advice. (메소드가 예외를 던지는것 없이 반환된다면 완성된 후에 수행되는 advice.)
  • Around advice : JoinPoint 앞뒤에서 실행되는 Advice. Around advice는 메소드 호출 전후에 사용자 지정 행위를 수행한다.
  • Throws advice : JoinPoint에서 예외가 발생했을 때 실행되는 Advice. Spring은 강력한 타입의 Throws advice를 제공한다. 그래서 Throwable 나 Exception으로 부터 형변환 할 필요가 없는 관심가는 예외(그리고 하위클래스)를 처리하는 코드를 쓸 수 있다.

Spring - IoC & DI

IoC란?

IoC 컨테이너 개념을 이해하기 위하여 이와 같은 컨테이너가 왜 등장하게 되었는지를 먼저 이해하는 것이 중요합니다.

애플리케이션 코드를 작성할 때, 특정 기능이 필요하면 라이브러리 사용하곤 합니다. 이때는 프로그램의 흐름을 제어하는 주체가 애플리케이션 코드입니다. 하지만 프레임워크(Framework) 기반의 개발에서는 프레임워크 자신이 흐름을 제어하는 주체가 되어, 필요 할 때마다 애플리케이션 코드를 호출하여 사용합니다.

프레임워크에서 이 제어권을 가지는 것이 바로 컨테이너(Container)입니다. 객체에 대한 제어권이 개발자로부터 컨테이너에게 넘어가면서 객체의 생성부터 생명주기 관리까지의 모든 것을 컨테이너가 맡아서 하게됩니다. 이를 일반적인 제어권의 흐름이 바뀌었다고 하여 IoC(Inversion of Control : 제어의 역전)라고 합니다.

먼저 지금까지 일반적으로 개발하던 방식에 대해서 생각해보아야 합니다. 모든 인스턴스에 대한 생성 권한은 지금까지 모든 개발자들에게 있었습니다. 즉, 작성하는 코드상에서 개발자가 직접 생성했다는 것입니다. EJB나 IoC 컨테이너를 사용하지 않았던 개발자들은 지금까지 이와 같은 방식을 사용했습니다.

EJB는 각 개발자들이 모든 인스턴스의 생성 권한에 제약을 가하는 첫번째 프레임워크입니다. EJB는 서비스를 위해 생성되는 컴포넌트에 대한 생성 권한을 EJB 컨테이너에게 위임했습니다. 생성된 인스턴스는 EJB 컨테이너가 생명주기를 관리했습니다. EJB가 EJB 컨테이너에 의하여 관리됨으로 인해 큰 장점을 얻을 수 있었습니다. 그러나 장점 이외에 EJB가 가지고 있는 한계에 부딪히게 되었으며, 이 같은 요구사항을 해결하기 위해 EJB의 한계를 극복하기 위한 시도가 발생했습니다.

그래서 등장한 것이 경량(LightWeight) IoC 컨테이너 입니다. 경량 IoC 컨테이너는 EJB 컨테이너가 가지고 있던 단점을 보완하기 위하여 탄생한 컨테이너 개념입니다. Spring 프레임워크에서 지원하는 IoC 컨테이너는 우리들이 흔히 개발하고 사용해왔던 일반 POJO(Plain Old Java Object) 클래스들이 지금까지 EJB를 통하여 실행했던 많은 기능들을 서비스 가능하도록 지원합니다.
또한, EJB 컨테이너가 지원하고 있던 Transaction, Object Pooling, 인스턴스 생명주기 관리등의 기능들을 Spring 컨테이너가 지원하며 부가적으로 테스트의 용이성(애플리케이션 품질의 향상), 개발 생산성을 향상 시킬 수 있습니다.

사용하는 목적

IoC를 사용하는 목적에 대해서는 지금까지의 클래스호출 방식의 변화를 살펴보면 더 쉽게 이해할 수 있습니다.

클래스 호출 방식

클래스내에 선언과 구현이 같이 있기 때문에 다양한 형태로 변화가 불가능합니다.

인터페이스 호출 방식

클래스를 인터페이스와 인터페이스를 상속받아 구현하는 클래스로 분리했습니다. 구현클래스 교체가 용이하여 다양한 변화가 가능합니다. 그러나 구현클래스 교체시 호출클래스의 코드에서 수정이 필요합니다. (부분적으로 종속적)

팩토리 호출 방식

팩토리 방식은 팩토리가 구현클래스를 생성하기 때문에 호출클래스는 팩토리를 호출 하는 코드로 충분합니다. 구현클래스 변경시 팩토리만 수정하면 되기 때문에 호출클래스에는 영향을 미치지 않습니다. 그러나 호출클래스에서 팩토리를 호출하는 코드가 들어가야 하는 것 또한 팩토리에 의존함을 의미합니다.

IoC

팩토리 패턴의 장점을 더해 어떠한 것에도 의존하지 않는 형태가 되었습니다. 실행시점에 클래스간의 관계가 형성이 됩니다. 즉, 의존성이 삽입된다는 의미로 IoC를 DI라는 표현으로 사용합니다.

IoC 용어 정리

  • bean : 스프링에서 제어권을 가지고 직접 만들어 관계를 부여하는 오브젝트
    Java Bean, EJB의 Bean과 비슷한 오브젝트 단위의 애플리케이션 컴포넌트이다. 하지만 스프링을 사용하는 애플리케이션에서 만들어지는 모든 오브젝트가 빈은 아니다. 스프링의 빈은 스프링 컨테이너가 생성하고 관계설정, 사용을 제어해주는 오브젝트를 말한다.
  • bean factory : 스프링의 IoC를 담당하는 핵심 컨테이너
    Bean을 등록/생성/조회/반환/관리 한다. 보통 bean factory를 바로 사용하지 않고 이를 확장한 application context를 이용한다. BeanFactory는 bean factory가 구현하는 interface이다. (getBean()등의 메서드가 정의되어 있다.)
  • application context : bean factory를 확장한 IoC 컨테이너
    Bean의 등록/생성/조회/반환/관리 기능은 bean factory와 같지만, 추가적으로 spring의 각종 부가 서비스를 제공한다. ApplicationContext는 application context가 구현해야 하는 interface이며, BeanFactory를 상속한다.
  • configuration metadata : application context 혹은 bean factory가 IoC를 적용하기 위해 사용하는 메타정보
    스프링의 설정정보는 컨테이너에 어떤 기능을 세팅하거나 조정하는 경우에도 사용하지만 주로 bean을 생성/구성하는 용도로 사용한다.
  • container (ioC container) : IoC 방식으로 bean을 관리한다는 의미에서 bean factory나 application context를 가리킨다.
    application context는 그 자체로 ApplicationContext 인터페이스를 구현한 오브젝트를 말하기도 하는데, 하나의 애플리케이션에 보통 여러개의 ApplicationContext 객체가 만들어진다. 이를 통칭해서 spring container라고 부를 수 있다.

스프링을 사용하지 않을 때 일어날 수 있는 문제

스프링의 특징을 알아보기 앞서 스프링을 사용하지 않을 때 어떤 문제가 일어날 수 있는지 알아보겠습니다.

  • 오브젝트의 생명 주기 문제
  • 부품화 문제
  • 기술 은닉과 부적절한 기술 은닉 문제

이러한 문제를 해결하지 않는 한 웹 애플리케이션은 리소스를 잘 이용하지 못하고, 테스트하기 어려우며, 확장이나 변경 또한 어려울 것입니다. 스프링은 이러한 문제를 해결하기 위해 만들어진 컨테이너라고도 할 수 있습니다.
스프링은 위의 문제를 다음과 같이 해결합니다.

  • 오브젝트의 생명 주기 문제는 DI 컨테이너로 해결
  • 부품화 문제는 DI 컨테이너로 해결
  • 기술 은닉과 부적절한 기술 은닉 문제는 AOP로 해결

DI

IoC는 직관적이지 못하기 때문에 DI(Dependency Injection)라고도 부릅니다. DI는 오브젝트를 생성하고 오브젝트끼리의 관계를 생성해 소프트웨어의 부품화 및 설계를 가능하게 합니다. DI를 이용하면 인터페이스 기반의 컴포넌트를 쉽게 구현할 수 있습니다.
DI를 우리말로 옮기면 의존 관계의 주입입니다. 쉽게 말하면 오브젝트 사이의 의존 관계를 만드는 것입니다. 어떤 오브젝트의 프로퍼티(인스턴스 변수)에 오브젝트가 이용할 오브젝트를 설정한다는 의미입니다. 이를 학술적으로 말하면, 어떤 오브젝트가 의존(이용)할 오브젝트를 주입 혹은 인젝션(프로퍼티에 설정)한다는 것입니다.
DI를 구현하는 컨테이너는 단순한 인젝션 외에도 클래스의 인스턴스화 등의 생명 주기 관리 기능이 있는 경우가 많습니다.

클래스에서 new 연산자가 사라졌다는 사실이 중요합니다. 클래스에서 new 연산자가 사라짐으로써 개발자가 팩토리 메서드 같은 디자인 패턴을 구사하지 않아도 DI 컨테이너가 건내주는 인스턴스를 인터페이스로 받아서 인터페이스 기반의 컴포넌트화를 구현할 수 있게 됐습니다.

DI 컨테이너의 구상 클래스 인스턴스화는(디폴트로는) 1회만 실행합니다. 생성된 인스턴스는 필요한 곳에서 사용합니다. 이렇게 하는 것으로 서비스와 DAO처럼 Singleton으로 만들고 싶은 컴포넌트를 특별히 Singleton으로 만들지 않아도 간단히 실현되게 해줍니다.

스프링에는 크게 (1)XML로 작성된 Bean 정의 파일을 이용한 DI, (2)어노테이션을 이용한 DI, (3)JavaConfig에 의한 DI가 있습니다. 이번 포스팅에서는 어노테이션을 이용한 DI에 대해 알아보겠습니다.

@Autowired와 @Component

인스턴스 변수 앞에 @Autowired를 붙이면 DI 컨테이너가 그 인스턴스 변수의 형에 대입할 수 있는 클래스를 @Component가 붙은 클래스 중에서 찾아내 그 인스턴스를 인젝션해줍니다(정확히는 Bean 정의에서 클래스를 스캔할 범위를 정해야 합니다).
인스턴스 변수로의 인젝션은 접근 제어자가 private라도 인젝션 할 수 있으므로 Setter 메서드를 만들 필요는 없습니다. (과거에 캡슐화의 정보 은닉에 반하는 것이 아니냐는 논의가 있었지만, 현재는 편리함에 밀려 그런 논의를 보기 힘들어졌습니다.)

만약 @Component가 붙은 클래스가 여러 개 있어도 형이 다르면 @Autowired가 붙은 인스턴스 변수에 인젝션되지 않습니다. 이렇게 형을 보고 인젝션하는 방법을 byType이라고 합니다.

@Autowired

@Autowired는 인스턴스 변수 앞에 붙이는 것 외에도, 다음과 같이 적당한 메서드 선언 앞에도 붙일 수 있습니다.

1
2
3
4
5
6
7
8
9
@Autowired
public void setFoo(Foo foo) {
this.foo = foo;
}
@Autowired
public void setFoo(Foo foo, Bar bar) {
this.foo = foo;
this.bar = bar;
}

또한 생성자에도 이용할 수 있습니다.

1
2
3
4
public class Foo {
@Autowired
public Food(Bar b) {...}
}


그런데 위의 사진과 같이 인터페이스에 구현 클래스가 2개여서 @Autowired로 인젝션할 수 있는 클래스의 형이 2개 존재한다면 에러가 발생합니다. 인젝션할 수 있는 클래스의 형은 반드시 하나로 해야합니다. 하지만 이래서는 인터페이스의 구현 클래스를 테스트용 클래스 등 다른 클래스로 바꿀 경우에 불편합니다. 그래서 이를 회피하는 세 가지 방법에 대해 알아보겠습니다.

  1. 우선할 디폴트 Bean을 설정하는 @Primary를 @Bean이나 @Component에 부여하는 방법
    (Bean 정의 파일에서는 )
    1
    2
    3
    4
    5
    @Component
    @Primary
    public class ProductDaoImpl implements ProductDao {
    ...(생략)...
    }
  2. @Autowired와 병행해서 @Qualifier를 하는 방법
    단, 이 경우는 @Component에도 이름을 같이 지정해야 한다. 이렇게 인젝션할 클래스를 형이 아닌 이름으로 찾아주는 방법을 byName이라고 한다. (물론 @Component에 같은 이름이 붙은 클래스가 중복되면 오류가 발생한다.)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Autowired
    @Qualifier("productDao")
    private ProductDao productDao;

    -----------------------------------------

    @Component("productDao")
    public class ProductDaoImpl implements ProductDao {
    ...(생략)...
    }
  3. Bean 정의 파일인 context:component-scan을 이용하는 방법
    (context:component-scan을 어느 정도 크기의 컴포넌트마다 기술해두고, 만약 어떤 컴포넌트를 테스트용으로 바꾸고자 할 때는 그 컴포넌트 부분의 정의만 테스트용 부품을 스캔하게 수정하는 방법이다.)

확장된 @Component

@Component에는 확장된 어노테이션이 있습니다. 웹 애플리케이션 개발에는 @Component를 이용할 것이 아니라 클래스가 어느 레이어에 배치될지 고려해서 배치될 레이어에 있는 @Component 확장 어노테이션을 사용하는 것이 좋습니다. 예를 들어 ProductServiceImpl은 @Component가 아니라 @Service로 바꾸는 편이 좋고, ProductDaoImpl 클래스도 @Component가 아니라 @Repository로 바꾸는 편이 좋습니다.

  • @Controller : 프레젠테이션 층 스프링 MVC용 어노테이션
  • @Service : 비즈니스 로직 층 Service용 어노테이션, @Component와 동일
  • @Repository : 데이터 엑세스 층의 DAO용 어노테이션
  • @Configuration : Bean 정의를 자바 프로그램에서 실행하는 JavaConfig용 어노테이션

@Component와 함께 사용하는 어노테이션의 하나로 @Scope가 있습니다. @Scope 뒤에 Value 속성을 지정하면 인스턴스화와 소멸을 제어할 수 있습니다. @Scope를 생략하면 해당 클래스는 싱글턴이 됩니다.

1
2
3
4
5
@Component
@Scope("singletone")
public class ProductDaoImple implements ProductDao {
...(생략)...
}
  • singleton : 인스턴스를 싱글턴으로 함
  • prototype : 이용할 때마다 인스턴스화함
  • request : Servlet API의 request 스코프인 동안만 인스턴스가 생존함
  • session : Servlet API의 session 스코프인 동안만 인스턴스가 생존함
  • application : Servlet API의 application 스코프인 동안만 인스턴스가 생존함

value 속성의 값은 직접 문자열로 넣어도 되지만, 상수가 존재하기 때문에 상수를 사용하는 것이 좋습니다.

  • singleton : BeanDefinition.SCOPE_SINGLETON
  • prototype : BeanDefinition.SCOPE_PROTOTYPE
  • request : WebApplicationContext.SCOPE_REQUEST
  • session : WebApplicationContext.SCOPE_SESSION
  • application : WebApplicationContext.SCOPE_APPLICATION

생명 주기 관리

스프링 DI 컨테이너에는 인스턴스의 생성과 소멸 타이밍에 호출되는 메서드를 설정할 수 있는 @PosetConstruct와 @PreDestroy라는 2개의 어노테이션이 있습니다.

  • @PostConstruct : 초기 처리를 하는 메서드 선언. 메서드 이름은 임의로 지정할 수 있다. 단, 메서드 인수 없이 반환형은 void 형으로 해야한다.
  • @PreDestroy : 종료 처리를 하는 메서드 선언. 메서드 이름은 임의로 지정할 수 있다. 단, 메서드 인수 없이 반환형은 void 형으로 해야한다.

@PostConstruct는 DI 컨테이너에 의해 인스턴스 변수에 무언가 인젝션된 다음에 호출됩니다. 따라서 인젝션 된 값으로 초기 처리를 할 때 사용합니다. (생성자에서도 초기 처리를 할 수 있습니다.)
@PreDestroy는 소멸자가 없는 자바에서 종료 처리를 하기 위해 사용합니다.

참고