토비의 스프링 - IoC 컨테이너와 DI
by Gunju Ko
이 글은 “토비의 스프링” 책 내용을 정리한 글입니다.
만약 저작권 관련 문제가 있다면 “gunjuko92@gmail.com”로 메일을 보내주시면, 바로 삭제하도록 하겠습니다.
토비의 스프링 - IoC 컨테이너와 DI
1. IoC 컨테이너 : 빈 팩토리와 애플리케이션 컨텍스트
- 스프링 애플리케이션에서는 오브젝트의 생성과 관계설정, 사용, 제거 등의 작업을 애플리케이션 코드 대신 독립된 컨테이너가 담당한다. 이를 컨테이너가 코드 대신 오브젝트에 대한 제어권을 갖고 있다고 해서 IoC라고 부른다. 그래서 스프링 컨테이너를 IoC 컨테이너라고도한다.
- 스프링 컨테이너는 단순한 DI 작업 보다 더 많은 일을 한다. DI를 위한 빈 팩토리에 엔터프라이즈 애플리케이션을 개발하는 데 필요한 여러 가지 컨테이너 기능을 추가한 것을 애플리케이션 컨텍스트라고 부른다.
- 스프링의 빈 팩토리와 애플리케이션 컨텍스트는 각각 기능을 대표하는 BeanFactory와 ApplicationContext라는 두 개의 인터페이스로 정의되어 있다. ApplicationContext 인터페이스는 BeanFactory 인터페이스를 상속한 서브인터페이스다.
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
@Nullable
String getId();
String getApplicationName();
String getDisplayName();
long getStartupDate();
@Nullable
ApplicationContext getParent();
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}
- 실제로 스프링 컨테이너 또는 IoC 컨테이너라고 말하는 것은 바로 이 ApplicationContext 인터페이스를 구현한 클래스의 오브젝트다.
- 스프링 애플리케이션은 최소한 하나 이상의 IoC 컨테이너, 즉 애플리케이션 컨텍스트 오브젝트를 갖고 있다. 하나 이상이라고하는 이유는 한 개 이상의 애플라케이션 컨텍스트 오브젝트를 갖고 있는 경우도 많기 때문이다.
1.1 IoC 컨테이너를 이용해 애플리케이션 만들기
IoC 컨테이너를 동작시키기 위해서는 크게 2가지가 필요하다. 하나는 POJO 클래스이며 다른 하나는 설정 메타 정보이다.
POJO 클래스
- 각자 기능에 충실하게 독립적으로 설계된 POJO 클래스를 만들고, 결합도가 낮은 유연한 관계를 가질 수 있도록 인터페이스를 이용해 연결해준다.
설정 메타 정보
- POJO 클래스들 중에 애플리케이션에서 사용할 것을 선정하고 이를 IoC 컨테이너가 제어할 수 있도록 적절한 메타정보를 만들어 제공하는 작업이다.
- 스프링의 설정 메타정보는 BeanDefinition 인터페이스로 표현되는 순수한 추상 정보다.
- 스프링 IoC 컨테이너, 즉 애플리케이션 컨텍스트는 바로 이 BeanDefinition으로 만들어진 메타정보를 담은 오브젝트를 사용해 IoC와 DI 작업을 수행한다. 따라서 스프링의 메타정보는 특정한 파일 포맷이나 형식에 제한되거나 종속되지 않는다. 대신 XML이든 소스코드 애노테이션이든 자바 코드이든 프로퍼티 파일이든 상관없이 BeanDefinition으로 정의되는 스프링의 설정 메타정보의 내용을 표현한 것이 있다면 무엇이든 사용 가능하다.
- 원본의 포맷과 구조 자료의 특성에 맞게 읽어와 BeanDefinition 오브젝트로 변환해주는 BeanDefinitionReader가 있으면 된다. 당연히 BeanDefinitionReader도 인터페이스다. 따라서 이를 구현한 리더를 만들기만 하면 스프링의 설정 메타정보는 어떤 형식으로든 작성할 수 있다.
BeanDefinition 인터페이스로 정의되는, IoC 컨테이너가 사용하는 빈 메타정보는 대략 다음과 같다.
- 빈아이디, 이름, 별칭 : 빈오브젝트를 구분 할 수 있는 식별자
- 클래스 또는 클래스 이름 : 빈으로 만들 POJO 클래스 또는 서비스 클래스 정보
- 스코프 : 싱글톤, 프로토타입과 같은 빈의 생성 방식과 존재 범위
- 프로퍼티 값 또는 참조 : DI에 사용할 프로퍼티 이름과 값 또는 참조하는 빈의 이름
- 생성자 파라미터 값 또는 참조 : DI에 사용할 생성자 파라미터 이름과 값 또는 참조할 빈의 이름
- 지연된 로딩 여부, 우선 빈 여부, 자동와이어링 여부, 부모 빈 정보, 빈팩토리 이름 등
일반적으로 설정 메타정보는 XML 파일이나 애노테이션 같은 외부 리소스를 전용 리더가 읽어서 BeanDefinition 타입의 오브젝트로 만들어 사용한다. 원한다면 직접 코드에서 BeanDefinition 메타정보를 생성할 수도 있다.
- 빈은 오브젝트 단위로 등록되고 만들어지기 때문에 같은 클래스 타입이더라도 하나 이상의 빈으로 등록할 수 있다.
애플리케이션을 구성하는 빈 오브젝트를 생성하는 것이 IoC 컨테이너의 핵심기능이다. IoC 컨테이너는 일단 빈 오브젝트가 생성되고 관계가 만들어지면 그 뒤로는 거의 관여하지 않는다. 기본적으로 싱글톤 빈은 애플리케이션 컨텍스트의 초기화 작업 중에 모두 만들어진다.
1.2 IoC 컨테이너의 종류와 사용 방법
StaticApplicationContext
StaticApplicationContext는 코드를 통해 빈 메타정보를 등록하기 위해 사용한다. 스프링의 기능에 대한 학습 테스트를 만들 때를 제외하면 실제로 사용되지 않는다.
스태틱 애플리케이션 컨텍스트는 실전에서는 사용하면 안 된다. 테스트 목적으로 코드를 통해 빈을 등록하고 컨테이너가 어떻게 동작하는지 확인하고 싶을 때를 대비해 이런 컨테이너가 있다는 정도만 기억해두자.
GenericApplicationContext
- GenericApplicationContext는 가장 일반적인 애플리케이션 컨텍스트의 구현 클래스다. 실전에서 사용될 수 있는 모든 기능을 갖추고 있는 애플리케이션 컨텍스트다. 컨테이너의 주요 기능을 DI를 통해 확장할 수 있도록 설계되어 있다.
- GenericApplicationContext는 StaticApplicationContext외는 달리 XML 파일과 같은 외부의 리소스에 있는 빈 설정 메타정보를 리더를 통해 읽어들여서 메타정보로 전환해서 사용한다.
- 특정 포맷의 빈 설정 메타정보를 읽어서 이를 애플리케이션 컨텍스트가 사용할 수있는 BeanDefinition 정보로 변환하는 기능을 가진 오브젝트는 BeanDefinitionReader 인터페이스를 구현해서 만들고, 빈 설정 정보 리더라고 불린다. XML로 작성된 빈 설정 정보를 읽어서 컨테이너에게 전달하는 대표적인 빈 설정정보 리더는 XmlBeanDefinitionReader다.
- 스프링 IoC 컨테이너가 사용할 수 있는 BeanDefinition 오브젝트로 변환만 될 수 있다면 설정 메타정보는 어떤 포맷으로 만들어져도 상관없다. 스프링은 XML 말고도 프로퍼티 파일에서 빈 설정 메타정보를 가져오는 PropertiesBeanDefinitionReader도 제공한다. 이를 이용하면 프로퍼티 파일 안에 아래와 같이 빈 설정 메타정보를 작성할 수도 있다.
printer.(Class)=springbook.learningtest.spring.ioc.bean.StringPrinter
hello.(Class)=springbook.learningtest.spring.ioc.bean.Hello
hello.name=Spring
hello.printer(ref)=printer
- GenericApplicationContext는 빈 설정 리더를 여러 개 사용해서 여러 리소스로부터 설정 메타정보를 읽어들이게도 할 수 있다. 모든 설정 메타정보를 가져온 후에 refresh( ) 메소드를 한 번 호출해서 애플리케이션 컨텍스트가 필요한 초기화 작업을 수행하게 해주면 된다.
- 아래와 같이 테스트 클래스를 만들었다면 테스트가 실행되면서 GenericApplicationContext가 생성되고 @ContextConfiguration에 지정한 XML 파일로 초기화돼서 테스트 내에서 사용할 수 있도록 준비된다.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/test-applicationContext.xml")
public class UserServiceTest {
@Autowired ApplicationContext applicationContext;
}
GenericXmlApplicationContext
- 코드에서 GenericApplicationContext 를 사용하는 경우에는 번거롭게 XmlBeanDefinitionReader를 직접 만들지 말고, 이 두 개의 클래스가 결합된 GenericXmlApplicationContext를 사용하면 편리하다.
- GenericXmlApplicationContext는 XmlBeanDefinitionReader를 내장하고 있기 때문에, XML 파일을 읽어들이고 refresh()를 통해 초기화하는 것까지 한 줄로 끝낼 수 있다.
GenericApplicatonContext ac = new GenericXmlApplicationContext(
"springbook/learningtest/spring/ioc/genericApplicationContext.xml");
Hello hello = ac.getBean("hello", Hello.class)
WebApplicationContext
스프링 애플리케이션에서 가장 많이 사용되는 애플리케이션 컨텍스트는 바로 WebApplicationContext다. WebApplicationContext는 ApplicationContext를 확장한 인터페이스이므로 정확히는 WebApplicationContext를 구현한 클래스를 사용하는 셈이다. 이름 그대로 웹 환경에서 사용할 때 필요한 기능이 추가된 애플리케이션 컨텍스트다. 스프링 애플리케이션은 대부분 서블릿 기반의 독립 웹 애플리케이션(WAR)으로 만들어지기 때문이다.
public interface WebApplicationContext extends ApplicationContext {
// skip...
/**
* Return the standard Servlet API ServletContext for this application.
*/
@Nullable
ServletContext getServletContext();
}
-
가장 많이 사용되는건, XML 설정 파일을 사용하도록 만들어진 XmlWebApplicatoinContext다. 애노테이션을 이용한 설정 리소스만 사용한다면 AnnotationConfigWebApplicationContext를 쓰면 된다.
-
스프링 IoC 컨테이너는 빈 설정 메타정보를 이용해 빈 오브젝트를 만들고 DI 작업을 수행한다. 하지만 그것만으로는 애플리케이션이 동작하지 않는다. 마치 자바 애플리케이션의 main() 메소드처럼 어디에선가 특정 빈 오브젝트의 메소드를 호출함으로써 애플리케이션을 동작시켜야 한다. IoC 컨테이너의 역할은 초기에 빈 오브젝트를 생성하고 DI 한 후에 최초로 애플리케이션을 기동할 빈 하나를 제공해주는 것까지다.
-
웹 환경에서는 main() 메소드 대신 서블릿 컨테이너가 브라우저로부터 오는 HTTP 요청을 받아서 해당 요청에 매핑되어 있는 서블릿을 실행해주는 방식으로 동작한다. 서블릿이 일종의 main() 메소드와 같은 역할을 하는 셈이다.
-
그렇다면 웹 애플리케이션에서 스프링 애플리케이션을 기동시키는 방법은 무엇일까? 일단 main() 메소드 역할을 하는 서블릿을 만들어두고, 미리 애플리케이션 컨텍스트를 생성해둔 다음, 요청이 서블릿으로 들어올 때마다 getBean()으로 필요한 빈을 가져와 정해진 메소드를 실행해주면 된다.
-
위의 그림은 웹 환경에서 스프링 빈으로 이뤄진 애플리케이션이 동작하는 구조다.
- 서블릿 컨테이너는 브라우저와 같은 클라이언트로부터 들어오는 요청을 받아서 서블릿을 동작시켜주는 일을 맡는다.
- 서블릿은 웹 애플리케이션이 시작될 때 미리 만들어둔 웹 애플리케이션 컨텍스트에게 빈 오브젝트로 구성된 애플리케이션의 기동 역할을 해줄 빈을 요청해서 받아둔다.
- 그리고 미리 지정된 메소드를 호출함으로써 스프링 컨테이너가 DI 방식으로 구성해둔 애플리케이션의 기능이 시작되는 것이다.
-
스프링은 이런 웹 환경에서 애플리케이션 컨텍스트를 생성하고 설정 메타 정보로 초기화해주고, 클라이언트로부터 들어오는 요청마다 적절한 빈을 찾아서 이를 실행해주는 기능을 가진 DispatcherServlet이라는 이름의 서블릿을 제공한다.
-
WebApplicationContext의 특징은 자신이 만들어지고 동작하는 환경인 웹 모률에 대한 정보에 접근할 수 있다는 점이다. 이를 이용해 웹 환경으로부터 필요한 정보를 가져오거나, 웹 환경에 스프링 컨테이너 자신을 노출할 수 있다. 컨테이너가 웹 환경에 노출되면 같은 웹 모듈에 들어 있는 스프링 빈이 아닌 일반 오브젝트와 연동될 수 있다.
1.3 IoC 컨테이너 계층구조
부모 컨텍스트를 이용한 계층구조 효과
- 모든 애플리케이션 컨텍스트는 부모 애플리케이션 컨텍스트를 가질 수 있다. 이를 이용하면 트리구조의 컨텍스트 계층을 만들 수 있다.
- 예를 들어 아래와 같은 구조의 애플리케이션 컨텍스트 계층이 만들어질 수 있다.
- 계층구조 안의 모든 컨텍스트는 각자 독립적인 설정정보를 이용해 빈 오브젝트를 만들고 관리한다. 각자 독립적으로 자신이 관리하는 빈을 갖고 있긴 하지만 DI를 위해 빈을 찾을 때는 부모 애플리케이션 컨텍스트의 빈까지 모두 검색한다.
- 먼저 자신이 관리하는 빈 중에서 필요한 빈을 찾아보고, 없으면 부모 컨텍스트에게 빈을 찾아달라고 요청한다. 부모 컨텍스트에서도 원하는 빈을 찾을 수 없다면 부모 컨텍스트의 부모 컨텍스트에게 다시 요청한다. 이렇게 계층구조를 따라서 가장 위에 존재하는 루트 컨텍스트까지 요청이 전달된다. 단 자신의 부모 컨텍스트에게만 빈 검색을 요청하지 자식 컨텍스트에게는 요청하지 않는다. 검색 순서는 항상 자신이 먼저이고, 그런 다음 직계 부모의 순서다.
- 부모 컨텍스트와 같은 이름의 빈을 자신이 정의해서 갖고 있다면 자신이 가진 것이 우선이고 부모 컨텍스트가 정의한 것은 무시된다. 미리 만들어진 애플리케이션 컨텍스트의 설정을 그대로 가져다가 사용하면서 그중 일부 빈만 설정을 변경하고 싶다면, 애플리케이션 컨텍스트를 두 개 만들어서 하위 컨텍스트에서 바꾸고 싶은 빈들을 다시 설정해줘도 된다.
- 계층구조를 이용하는 또 한 가지 이유는 여러 애플리케이션 컨텍스트가 공유하는 설정을 만들기 위해서다.
- 애플리케이션 컨텍스트의 계층구조를 사용할 때는 주의하지 않으면 자칫 예상치 못한 방식으로 동작할 수도 있기 때문에 조심해야 한다. 자신이 만든 스프링 애플리케이션이 어떻게 컨텍스트가 만들어지고, 어느 것이 루트 컨텍스트이고, 어느 것이 그 자식 컨텍스트가 되는지를 분명하게 알아야 한다. 자식 컨텍스트는 부모 컨텍스트의 빈을 사용할 수 있지만 그 반대는 안 된다는 사실을 기억해야 한다. AOP처럼 컨텍스트 안의 많은 빈에 일괄적으로 적용되는 기능은 대부분 해당 컨텍스트로 제한된다는 점도 주의하자.
- 기본적으로 스프링 웹 애플리케이션은 부모/자식 관계를 가진 두 개의 애플리케이션 컨텍스트로 구성된 계층구조로 만들어진다.
- 루트 컨텍스트는 반드시 스스로 완전한 빈 의존관계를 보장해야 한다.
- 자식 컨텍스트의 빈이 부모 컨텍스트 빈과 중복될 때는 자식 컨텍스트의 것이 우선한다는 사실을 확인 할 수 있다. 단 이때는 자식 컨텍스트에 빈 요청을 해야한다.
스프링이 일정한 규칙과 제어 방법을 제공하면서 중복 빈 정의를 계층 구조 내에서 허용한다고 하더라도 부모/자식 컨텍스트에 중복해서 빈이 정의되는 일은 가능한 한 피해야 한다. 개발자가 직관적으로 사용할 수 있는 구조를 제외하면 복잡한 계층구조 사이의 혼란스러운 빈 정의는 발견하기 매우 힘든 버그를 만들어낼 기능성이 높기 때문이다.
1.4 웹 어플리케이션의 IoC 컨테이너 구성
- 많은 웹 요청을 한 번에 받을 수 있는 대표 서블릿을 등록해두고, 공통적인 선행 작업을 수행하게 한 후에 각 요청의 기능을 담당하는 핸들러라고 불리는 클래스를 호출하는 방식으로 개발하는 경우가 일반적이다. 몇 개의 서블릿이 중앙 집중식으로 모든 요청을 다 받아서 처리하는 이런 방식을 프론트 컨트롤러 패턴이라고 한다. 스프링도 프론트 컨트롤러 패턴을 사용한다. 따라서 스프링 웹 애플리케이션에 사용되는 서블릿의 숫자는 하나이거나 많아야 두셋 정도다.
웹 애플리케이션 컨텍스트 계층구조
- 웹 애플리케이션 안에서 동작하는 IoC 컨테이너는 두 가지 방법으로 만들어진다. 하나는 스프링 애플리케이션의 요청을 처리하는 서블릿 안에서 만들어지는 것이고, 다른 하나는 웹 애플리케이션 레벨에서 만들어지는 것이다. 일반적으로는 이 두 가지 방식을 모두 사용해 컨테이너를 만든다.
- 웹 애플리케이션 레벨에 등록되는 컨테이너는 보통 루트 웹 애플리케이션 컨텍스트라고 불린다. 이 컨텍스트는 서블릿 레벨에 등록되는 컨테이너들의 부모 컨테이너가 되고, 일반적으로 전체 계층구조 내에서 가장 최상단에 위치한 루트 컨텍스트가 되기 때문이다.
- 웹 애플리케이션에는 하나 이상의 스프링 애플리케이션의 프론트 컨트롤러 역할을 하는 서블릿이 등록될 수 있다. 이 서블릿에는 각각 독립적으로 애플리케이션 컨텍스트가 만들어진다. 이런 경우 각서블릿이 공유하게 되는 공통적인 빈들이 있을 것이고, 이런 빈들을 웹 애플리케이션 레벨의 컨텍스트에 등록하면 된다. 이런 경우 공통되는 빈들이 서블릿별로 중복돼서 생성되는 걸 방지할 수 있다.
- 아래 그림은 하나의 웹 애플리케이션 내에 두 개의 스프링 서블릿이 존재하는 경우에 만들어지는 애플리케이션 컨텍스트와 그 관계를 보여준다. 서블릿 A와 서블릿 B는 각각 자신의 전용 애플리케이션 컨텍스트를 갖고 있다. 동시에 두 컨텍스트가 공유해서 사용하는 빈을 담아놓을 수 있는 별도의 컨텍스트가 존재한다. 이 컨텍스트는 각 서블릿에 존재하는 컨텍스트의 부모 컨텍스트로 만든다. 또, 최상단에 위치하므로 계층구조에서 볼 때 루트 컨텍스트다.
- 서블릿 A와 서블릿 B의 컨텍스트는 서로 독립적으로 빈을 생성해서 동작하고, 공통적인 빈은 부모 컨텍스트인 루트 컨텍스트 가 만든 것을 공유해서 사용할 수 있다.
- 일반적으로는 스프링의 애플리케이션 컨텍스트를 가지면서 프론트 컨트롤러 역할을 하는 서블릿은 하나만 만들어 사용한다.
- 스프링은 웹 애플리케이션마다 하나씩 존재하는 서블릿 컨텍스트를 통해 루트 애플리케이션 컨텍스트에 접근할 수 있는 방법을 제공한다. 다음과 같이 스프링의 간단한 유틸리티 메소드를 이용하면 스프링 밖의 어디서라도 웹 애플리케이션의 루트 애플리케이션 컨텍스트를 얻을 수 있다.
WebApplicationContextUtils.getWebApplicationContext(SevletContext sc)
- ServletContext는 웹 애플리케이션마다 하나씩 만들어지는 것으로, 서블릿의 런타임 환경정보를 담고 있다.
- 아래 그림은 스프링 외의 웹 기술을 포함해서 조합 가능한 웹 애플리케이션의 설정 구조를 보여준다.
웹 애플리케이션의 컨텍스트 구성 방법
- 서블릿 컨텍스트와 루트 애플리케이션 컨텍스트 계층 구조 : 가장 많이 사용되는 기본적인 구성 방법이다. 스프링 웹 기술을 사용하는 경우 웹 관련 빈들은 서블릿의 컨텍스트에 두고 나머지는 루트 애플리케이션 컨텍스트에 등록한다. 루트 컨텍스트는 모든 서블릿 레벨 컨텍스트의 부모 컨텍스트가 된다
- 루트 애플리케이션 컨텍스트 단일 구조 : 스프링 웹 기술을 사용하지 않고 서드파티 웹 프레임워크나 서비스 엔진만을 사용해 서 프레젠테이션 계층을 만든다면 스프링 서블릿을 둘 이유가 없다. 따라서 서블릿의 애플리케이션 컨텍스트도 사용하지 않게 된다.
- 서블릿 컨텍스트 단일 구조 : 스프링 웹 기술을 사용하면서 스프링 외의 프레임워크나 서비스 엔진에서 스프링의 빈을 이용할 생각이 아니라면 루트 애플리케이션 컨택스트를 생략할 수도 있다. 이때는 서블릿 안에 만들어지는 애플리케이션 컨텍스트가 부모 컨텍스트를 갖지 않기 때문에 스스로 루트 컨텍스트가 된다. 이렇게 만들어지는 서블릿 컨텍스트는 컨텍스트 계층 관점에서 보자연 루트 컨텍스트이지만 웹 애플리케이션 레벨에 두는 공유 가능한 루트 컨텍스트와는 구별된다.
루트 애플리케이션 컨텍스트 등록
- 웹 애플리케이션 레벨에 만들어지는 루트 웹 애플리케이션 컨텍스트를 등록하는 가장 간단한 방법은 서블릿의 이벤트 리스너(event listener)를 이용하는 것이다.
- 스프링은 웹 애플리케이션의 시작과 종료 시 발생하는 이벤트를 처리하는 리스너인 ServletContextListener를 이용한다. 이를 이용해서 웹 애플리케이션이 시작될 때 루트 애플리케이션 컨텍스트를 만들어 초기화하고 웹 애플리케이션이 종료될 때 컨텍스트를 함께 종료하는 기능을 가진 리스너를 만들 수 있다. 스프링은 이러한 기능을 가진 리스너인 ContextLoaderListener를 제공한다.
- ContextLoaderListener는 웹 애플리케이션이 시작할 때 자동으로 루트 애플리케이션 컨텍스트를 만들고 초기화해준다. 그렇다면 이 리스너가 만들어주는 컨텍스트는 어떤 종류이고 어떤 설정파일을 사용할까? 별다른 파라미터를 지정하지 않으면, 디폴트로 설정된 다음의 값이 적용된다.
- 애플리케이션 컨텍스트 클래스 : XmlWebApplicationContext
- XML 설정파일 위치 : /WEB-INF/applicationContext.xml
- 컨텍스트 클래스와 설정파일 위치는 서블릿 컨텍스트 파라미터를 선언해서 변경할 수 있다. ContextLoaderListener가 이용할 파라미터를 <context-param> 항목 안에 넣어주면 디폴트 설정 대신 파라미터로 지정한 내용이 적용된다.
서블릿 애플리케이션 컨텍스트 등록
- 스프링의 웹 기능을 지원하는 프론트 컨트롤러 서블릿은 DispatcherServlet이다. DispatcherServlet은 이름에서 알 수 있듯이 web.xml에 등록해서 사용할 수 있는 평범한 서블릿이다.
- 서블릿 이름을 다르게 지정해주면 하나의 웹 애플리케이션에 여러 개의 DispatcherServlet을 등록할 수도 있다. 각 DispatcherServlet은 서블릿이 초기화 될 때 자신만의 컨텍스트를 생성하고 초기화한다. 동시에 웹 애플리케이션 레벨에 등록된 루트 애플리케이션 컨텍스트를 찾아서 이를 자신의 부모 컨텍스트로 사용한다.
- DispatcherServlet을 등록할 때 신경 써야 할 사항은 다음 두가이다.
- <servlet-name> : DispatcherServlet에 의해 만들어지는 애플리케이션 컨텍스트는 모두 독립적인 네임스페이스를 갖게 된다. 이 네임스페이스는 서블릿 단위로 만들어지는 컨텍스트를 구분하는 키가 된다. 네임스페이스는 <servlet-name>으로 지정한 서블릿 이름에 “-servlet”을 붙여서 만든다. 서블릿 이름이 spnng이라면 네임스페이스는 spring- servlet이 된다. 네임스페이스가 중요한 이유는 DispatcherServlet이 사용할 디폴트 XML 설정 파일의 위치를 네임스페이스를 이용해서 만들기 때문이다. 서블릿 컨텍스트가 사용할 디폴트 설정파일은 다음와 같은 규칙으로 만들어진다. “/WEB-INF/ + 서블릿 네임스페이스 + “.xml””
- <load-on-startup> : 서블릿 컨테이너가 등록된 서블릿을 언제 만들고 초기화할지, 또 그 순서는 어떻게 되는지를 지정하는 정수 값이다. 이 항목을 아예 생략하거나 음의 정수로 넣으면 해당 서블릿은 서블릿 컨테이너가 임의로 정한 시점에서 만들어지고 초기화된다. 반대로 0 이상의 값을 넣으면 웹 애플리케이션이 시작되는 시점에서 서블릿을 로딩하고 초기화한다. 또한 여러 개의 서블릿이 등록되어 있다면 작은수를 가진 서블릿이 우선적으로 만들어진다. DispatcherServlet은 서블릿의 초기화 작업 중에 스프링 컨텍스트를 생성한다. 컨텍스트의 설정이나 환경에 문제가 있다면 컨텍스트 생성 시 대부분 확인이 가능하다. 따라서 웹 애플리케이션이 시작되고 가능한 한 빨리 서블릿 컨텍스트의 초기화가 진행되는 것이 바람직하다. 그래야만 컨텍스트와 빈의 초기화 작업을 통해 문제를 빨리 파악 할 수 있기 때문이다. 그래서 보통 1을 넣는다.
2. IoC/DI를 위한 빈 설정 메타 정보 작성
- 컨테이너는 빈 설정 메타정보를 통해 빈의 클래스와 이름을 제공받는다. 빈을 만들기 위한 설정 메타정보는 파일이나 애노태이션 같은 리소스로부터 전용 리더를 통해 읽혀서 BeanDefinition 타입의 오브젝트로 변환된다. 이 BeanDefinition 정보를 IoC 컨테이너가 활용한다.
- 적절한 리더나 BeanDefinition 생성기를 사용할 수만 있다면 빈 설정 메타정보를 담은 소스는 어떤 식으로 만들어도 상관없다. 대표적인 소스는 XML 문서, 애노테이션, 자바 코드이다.
2.1 빈 설정 메타정보
- BeanDefinition에는 IoC 컨테이너가 빈을 만들 때 필요한 핵심 정보가 담겨 있음
- 몇 가지 필수항목을 제외하면 컨테이너에 미리 설정된 디폴트 값이 그대로 적용
- BeanDefinition은 여러 개의 빈을 만드는 데 재사용될 수 있음
- BeanDefinition에는 빈의 이름이나 아이디를 나타내는 정보는 포함되지 않음
- IoC 컨테이너에 이 BeanDefinition 정보가 등록될 때 이름을 부여해줄 수 있음
빈 설정 메타 정보 항목
- beanClassName : 빈 오브젝트의 클래스 이름
- parentName : 빈 메타정보를 상속받을 부모 BeanDefinition의 이름. 빈의 메타정보는 계층구조로 상속할 수 있다.
- factoryBeanName : 팩토리 역할을 하는 빈을 이용해 빈오브젝트를 생성하는 경우에 팩토리 빈의 이름을 지정한다.
- factoryMethodName : 다른 빈 또는 클래스의 메소드를 통해 빈오브젝트를 생성하는 경우 그 메소드 이름을 지정한다.
- scope : 빈 오브젝트의 생명주기를 결정하는 스코프를 지정한다. 크게 싱글톤과 비싱글톤 스코프로 구분할 수 있다
- lazylnit : 빈 오브젝트의 생성을 최대한 지연할 것인지를 지정한다 이 값이 true이면 컨테이너는 빈 오브젝트의 생성을 꼭 필요한 시점까지 미룬다.
- dependsOn : 먼저 만들어져야 하는 빈을 지정 할 수있다. 빈오브젝트의 생성 순서가 보장돼야 하는 경우 이용한다.
- autowireCandidate : 명시적인 설정이 없어도 미리 정해진 규칙을 가지고 자동으로 DI 후보를 결정하는 자동와이어링의 대상으로 포함시킬지의여부
- primary : 자동와이어링 작업 중에 DI 대상 후보가 여러 개가 발생하는 경우가 있다. 이때 최종 선택의 우선권을 부여할지 여부. primary가 지정된 빈이 없이 여러 개의 후보가 존재하면 자동와이어링 예외가 발생한다.
- abstract : 메타정보 상속에만 사용할 추상 빈으로 만들지의 여부. 추상 빈이 되면 그 자체는 오브젝트가 생성되지 않고 다른 빈의 부모 빈으로만 사용된다.
- autowireMode : 오토와이어링 전략. 이름, 타입, 생성자, 자동인식 등의 방법이 있다.
- dependencyCheck : 프로퍼티 값 또는 레퍼런스가 모두 설정되어 있는지를 검증하는 작업의 종류
- initMethod : 빈이 생성되고 DI를 마친 뒤에 실행할 초기화 메소드의 이름
- destoryMethod : 빈의 생명주기가 다 돼서 제거하기 전에 호출할 메소드의 이름
- propertyValues : 프로퍼티의 이름과 설정 값 또는 레퍼런스. 수정자 메소드를 통한 DI 작업에서 사용한다.
- constructorArgumentValues : 생성자의 이름과 설정 값 또는 레퍼런스 생성자를 통한 DI 작업에서 사용한다.
- annotationMetadata : 빈 클래스에 담긴 애노테이션과 그 애트리뷰트 값. 애노테이션을 이용하는 설정에서 활용한다.
2.2 빈 등록 방법
- BeanDefinition 구현 오브젝트를 직접 생성
- 보통 직접 구현 오브젝트를 생성하지 않음
- 보통 XML 문서, 프로퍼티 파일, 소스코드 애노태이션과같은외부 리소스로 빈 메타정보를 작성하고 이를 적절한 리더나 변환기를 통해 애플리케이션 컨텍스트가 사용할 수 있는 정보로 변환해주는 방법을 사용
XML을 이용한 빈 등록
- <bean> 태그
- 스프링은 기술적인 설정과 기반 서비스를 빈으로 등록할 때를 위해 의미가 잘드러나는 네임스페이스와 태그를 가진 설정 방법을 제공 - 컨텍스트가 사용하는 설정 정보를 담은 빈과 애플리케이션 로직을 담은 빈을 구분하기 위함
- <aop-pointcut> : AOP를 위한 포인트컷 오브젝트 빈을 생성할 때 사용
- <aop:pointcut> 태그는 앞에서 <bean>으로 선언한 것과 동일한 빈 설정 메타정보로 변환된다. 하지만 네임스페이스와 전용 태그, 전용 애트리뷰트를 이용해 선언됐기 때문에 내용이 매우 분명하게 드러나고 선언 자체도 깔끔해진다.
- <jdbc:embedded-database> : 내장형 DB 선언
- 전용태그 하나로 동시에 여러 개의 빈을 만들수도 있다. 대표적으로 <context:annotation-config>가 있다.
자동인식을 이용한 빈 등록
빈으로 사용될 클래스에 특별한 애노테이션을 부여해주면 이런 클래스를 자동으로 찾아서 빈으로 등록해주게 할 수 있다. 이렇게 특정 애노태이션이 붙은 클래스를 자동으로 찾아서 빈으로 등록해주는 방식을 빈 스캐닝(scanner)을 통한 자동인식 빈 등록 기능이라고 하고, 이런 스캐닝 작업을 담당하는 오브젝트를 빈 스캐너(scanner)라고 한다.
- 스프링의 빈 스캐너는 지정된 클래스패스 아래에 있는 모든 패키지의 클래스를 대상으로 필터를 적용해서 빈 등록을 위한 클래스들을 선별해낸다. 빈 스캐너에 내장된 디폴트 필터는 @Component 애노테이션이 또는 @Component를 메타 애노태이션으로 가진 애노테이션이 부여된 클래스를 선택하도록 되어 있다.
- @Component를 포함해 디폴트 필터에 적용되는 애노태이션을 스프링에서는 스테레오타입 애노테이션이라고 부른다.
- 빈 스캐너는 기본적으로 클래스 이름을 빈의 아이디로 사용한다. 정확히는 클래스 이름의 첫 글자만 소문자로 바꾼 것을 사용한다.
- 빈 스캐너는 클래스패스의 모든 클래스를 다 검색할 수 있다. 하지만 모든 클래스패스를 다 검색하는 것은 비효율적이다. 그보다는 빈으로 등록될 클래스들이 있는 패키지를 지정해서 검색하도록 만드는 게 바람직하다. 지정된 패키지와 그 서브패키지의 클래스들이 자동 검색 대상이 된다.
- AnnotationConfigApplicationContext는 빈 스캐너를 내장하고 있다.
- @Component 외에도 아래와 같은 스테레오타입 애노테이션을 사용할 수 있다. 자동인식을 위한 애노태이션을 이렇게 여러 가지 사용하는 이유는 계층별로 빈의 특성이나 종류를 나타내려는 목적도 있고 AOP의 적용 대상 그룹을 만들기 위해서이기도 하다.
- @Repository : 데이터 액세스 계층의 DAO 또는 리포지토리 클래스에 사용된다. DataAccessException 자동변환과 같은 AOP의 적용 대상을 선정하기 위해서도 사용된다.
- @Service : 서비스 계층의 클래스에 사용된다.
- @Controller : 프레젠테이션 계층의 MVC 컨트롤러에 사용된다. 스프링 웹 서블릿에 의해 웹 요청을 처리하는 컨트롤러 빈으로 선정된다.
- 원한다면 스트레오타입 애노테이션을 직접 정의해서 사용할 수도 있다.
웹 애플리케이션에서 자동인식 기능을 사용하는 경우에는 이중 컨텍스트 계층 구조와 빈 검색 우선순위를 잘 이해하고 빈 스캐닝 설정을 제공할 때 중복 등록이 발생하지 않도록 주의해야 한다.
자바 코드에 의한 빈 등록
자바 코드에 의한 빈 등록 기능은 하나의 클래스 안에 여러 개의 빈을 정의할 수 있다. 또 애노테이션을 이용해 빈 오브젝트의 메타정보를 추가하는 일도 가능하다.
- 빈 설정 메타정보를 담고 있는 자바 코드는 @Configuration 애노태이션이 달린 클래스를 이용해 작성한다.
- @Configuration 애노테이션이 달리 클래스도 빈으로 등록된다.
- @Bean이 붙은 메소드를 이용해서 빈을 만들 때, 싱글톤 빈이라면 한 개의 오브젝트만 생성이 되고 더 이상 새로운 오브젝트가 만들어지지 않도록 특별한 방법으로 @Bean 메소드를 조작해둔다. 이 때문에 @Bean이 붙은 메소드를 여러번 호출해도 매번 동일한 빈 오브젝트가 리턴된다.
- @Configuration 클래스는 빈 스캐닝을 통해 자동 등록될 수도 있다. @Configuration의 메타 애노테이션에 @Component가 있기 때문에 빈 스캐너의 애노태이션 필터를 통과한다.
- 장점
- 컴파일러나 IDE를 통한 타입 검증이 가능하다.
- 자동완성과 같은 IDE 지원 기능을 최대한 이용할 수 있다.
- 이해하기 쉽다.
- 복잡한 빈 설정이나 초기화 작업을 손쉽게 적용할 수 있다.
- @Configuration이 붙은 클래스가 아닌 일반 POJO 클래스에도 @Bean을 사용할 수 있다.
- @Configuration이 붙지 않은 @Bean 메소드는 @Configuration 클래스의 @Bean과 미묘한 차이점이 있기 때문에 주의해야 한다.
- @Configuration이 붙지 않은 클래스에 @Bean을 사용했을 때는 다른 @Bean 메소드를 호출해서 DI 하는 코드에 문제가 발생한다. @Configuration이 붙은 클래스에서는 @Bean이 붙은 메소드를 여러 번 호출돼도 하나의 오브젝트만 리턴되는 것이 보장된다. 하지만 일단 빈 클래스에서 @Bean을 사용한 경우, DI 설정을 위해 다른 @Bean 메소드를 직접 호출하면 매번 다른 오브젝트를 받게 된다.
- 일반 클래스에서 @Bean을 사용할 경우 이런 위험성이 있기 때문에 함부로 남용해서는 안된다. @Bean 메소드가 정의된 클래스 밖에서는 실수로라도 메소드를 호출할 수 없도록 private으로 선언해두고 클래스 내부에서도 DI를 통해 참조해야지 메소드를 직접 호출하지 않도록 주의를 기울여야 한다.
빈 등록 방법은 한가지만 선택해야 하는건 아니다. 한 가지 이상의 방법을 조합해 사용할 수도 있다.
2.3 빈 의존관계 설정 방법
DI라고 하면 스프링이 관리하는 빈 오브젝트 사이의 관계를 말하지만, 넓게 보자면 빈 외의 오브젝트 또는 단순값을 주입하는 것도 포함된다.
- DI 할 대상을 선정하는 방법으로 분류해보면 명시적으로 구체적인 빈을 지정하는 방법과 일정한 규칙에 따라 자동으로 선정하는 방법으로 나눌 수 있다. 보통 전자는 DI할 빈의 아이디를 직접 지정 하는 것이고 후자는 주로 타입 비교를 통해서 호환되는 타입의 빈을 DI 후보로 삼는 방법이다. 후자의 방법은 보통 자동와이어링이라고 불린다.
- 자동와이어링 : 미리 정해진 규칙을 이용해 자동으로 DI 설정을 컨테이너가 추가해준다.
- 이름을 사용하는 자동 와이어링 : 빈의 모든 프로퍼티에 대해 이름이 동일한 빈을 찾아서 연결해준다. 프로퍼티와 이름이 같은 빈이 없는 경우는 무시한다.
- 자동와이어링이 어려운 프로퍼티 값이나 특별한 이름을 가진 프로퍼티의 경우에는 명시적으로 <property>를 선언해주면 된다.
- 타입을 사용하는 자동 와이어링 : 타입에 의한 자동와이어링은 프로퍼티의 타입과 각 빈의 타입을 비교해서 자동으로 연결해주는 방법이다.
- 이름을 사용하는 자동 와이어링 : 빈의 모든 프로퍼티에 대해 이름이 동일한 빈을 찾아서 연결해준다. 프로퍼티와 이름이 같은 빈이 없는 경우는 무시한다.
@Resource
- 주입할 빈을 아이디로 지정하는 방법이다. @Resource는 자바 클래스의 수정자뿐만 아니라 필드에도 붙일 수 있다.
- 애노테이션으로 된 의존관계 정보를 이용해 DI가 이뤄지게 하려면 세가지 방법중 하나를 선택해야 한다.
- <context:annotation-config /> : @Resource와 같은 애노테이션 의존 관계 정보를 읽어서 메타정보를 추가해주는 기능을 가진 빈 후처리기를 등록해주는 전용 태그다. 빈 후처리기는 이미 등록된 빈의 메타 정보에 프로퍼티 항목을 추가해주는 작업을 한다.
-
: 빈 스캐닝을 통한 빈 등록 방법을 지정하는 것인데, 내부적으로 <context:annotation-config /> 태그로 만들어지는 빈을 함께 등록해준다.
- AnnotationConfigApplicationContext 또는 AnnotationConfigWebApplicationContext : 빈 스캐너와 애노테이션 의존관계 정보를 읽는 후처리기를 내장한 애플리케이션 컨텍스트를 사용
- 필드 주입 : @Resource를 필드에 붙여서 원하는 빈을 DI하는 방식. @Resource 애노테이션의 name 엘리먼트를 생략하면 DI할 빈의 이름이 프로퍼티나 필드 이름과 같다고 가정한다.
- @Resource가 붙어 있으면 반드시 참조할 빈이 존재해야 한다. 만약 DI 할 빈을 찾을 수 없다면 예외가 발생한다.
- 만약에 빈 이름으로 참조할 빈을 찾을 수 없는 경우에는 타입을 이용해 다시 한번 빈을 찾기도 한다.
@Autowired
- @Autowired는 XML의 타입에 의한 자동와이어링 방식을 생성자, 필드, 수정자 메소드, 일반 메소드의 네 가지로 확장한 것이다.
- 생성자 : @Autowired는 단 하나의 생성자에만 사용할 수 있다는 제한이 있다. 여러 개의 생성자에 @Autowired가 붙으면 어느 생성자를 이용해서 DI 해야 할지 스프링이 알 수 없기 때문
- 일반 메소드 : 파라미터를 가진 메소드를 만들고 @Autowired를 붙여주면 각 파라미터의 타입을 기준으로 자동와이어링을 해서 DI 해줄 수 있다. 생성자 주입과 달리 일반 메소드는 오브젝트 생성 후에 차례로 호출이 가능하므로 여러 개를 만들어도 된다. 한 번에 여러 개의 오브젝트를 DI 할 수 있으므로 코드도 상대적으로 깔끔해진다. 수정자 메소드 주입과 생성자 주입의 장점을 모두 갖춘 방식이다.
- @Autowired를 이용하면 같은 타입의 빈이 하나 이상 존재할 때 그 빈들을 모두 DI 받도록 할 수 있다. @Autowired의 대상이 되는 필드나 프로퍼티, 메소드의 파라미터를 컬렉션이나 배열로 선언하면 된다.
- Map을 이용하면 빈의 이름을 키로 하는 맵으로 DI 받을 수 있다. 빈의 이름이 필요한 경우에 유용
- 컬렉션과 배열을 단지 같은 타입의 빈이 여러 개 등록되는 경우에 충돌을 피하려는 목적으로 사용해서는 안 된다. 의도적으로 타입이 같은 여러 개의 빈을 등록하고 이를 모두 참조하거나 그중에서 선별적으로 필요한 빈을 찾을 때 사용하는 것이 좋다.
- @Qualifier : Qualifier는 타입 외의 정보를 추가해서 자동와이어링을 세밀하게 제어할 수 있는 보조적인 방법이다.
- 빈 이름과는 별도로 추가적인 메타정보를 지정해서 의미를 부여해놓고 이를 @Autowired에서 사용할 수 있게 하는 @Qualifier가 훨씬 직관적이고 깔끔하다.
- @Qualifier를 @Autowired 애노테이션과 함께 사용하면 스프링은 빈 중에서 <qualifier> 태그가 있고, 그 값이 @Qualifier value와 일치하는 빈을 찾아서 DI한다.
- @Compoenent을 이용해서 빈으로 등록하는 경우 @Component 애노테이션과 @Qualifier 애노테이션을 함께 사용하면 한정자를 부여할 수 있다.
- @Qualifier를 사용했는데, 해당 한정자를 가진 빈이 없을 경우 마지막으로 @Qualifer의 값과 같은 이름의 빈이 있는지 한 번 더 확인하고 있다면 그 빈을 DI 대상으로 선택한다.
- 이름을 이용해 빈을 지정하고 싶다면 @Resource를 사용하고, 타입과 한정자를 활용하고 싶을 때만 @Autowired를 시용하는 것이 바람직하다. 혼합해서 사용할 경우 코드를 읽을때 혼란을 줄 수 있기 때문
- @Qualifier는 빈에 부가적인 속성을 지정해주는 효과가 있다. 그래서 단순하게 @Qualifier에 값을 넣는 수준이 아니라, 좀 더 의미 있는 애노테이션을 만드는 것도 좋다. 스프링은 @Qualifier를 메타 애노테이션으로 갖는 애노테이션도 @Qualifier 취급을 해준다. 아래는 @Database라는 이름으로 새로운 한정자 애노테이션을 선언하는 예다.
@Target({ElementType.Field, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Database {
String value();
}
- 위와 같은 애노테이션을 사용하면 @Qualifer를 사용했을때보다 좀 더 의미 있게 DI할 대상이 어떤것인지 나타낼 수 있다.
- @Qualifier는 부여 대상이 필드와 수정자 파라미터뿐이다. 생성자와 일반 메소드의 경우에는 @Qualifier를 부여하는 것이 의미가 없다. 각 파라미터마다 하나의 빈이 매핑되기 때문에 이때는 생성자나 메소드가 아니라 파라미터에 직접 @Qualifier를 붙여야 한다.
- @Autowired(required = false) : 타입에 의한 자동와이어링으로 빈을 찾을 수 없더라도 상관없는 경우에 사용
- @Inject : @Inject는 필드, 생성자, 수정자와 임의의 설정용 메소드에 사용돼서 타입에 의한 자동와이어링을 선언한다는 점에서 @Autowired와 매우 유사하다. 하지만 @Autowired의 required 엘리먼트에 해당하는 선택 기능은 없다.
자바 코드에 의한 의존관계 설정
- 애노테이션에 의한 설정 @Autowired, @Resource : 빈은 자바 코드에 의해 생성되지만 의존관계는 빈 클래스의 애노테이션을 이용
- @Bean 메소드 호출 : @Configuration과 @Bean을 사용하는 자바 코드 설정 방식의 기본은 메소드로 정의된 다른 빈을 메소드 호출을 통해 참조하는 것이다.
- @Bean이 붙은 메소드는 기본적으로 스프링의 싱글톤 방식을 따라야 하기 때문에 여러 번 호출해도 매번 new Printer()에 의해 새로운 오브젝트가 만들어지지 않고, 딱 한 개의 오브젝트가 반복적으로 돌아온다. 스프링이 @Configuration이 붙은 클래스를 조작해서 특별한 방식으로 동작하도록 조작해뒀기 때문
- @Bean 메소드와 자동 와이어링 : 메소드로 정의된 다른 빈을 가져와 자바 코드로 의존정보를 생성할 때 직접 @Bean이 붙은 메소드를 호출하는 대신 그 빈의 레퍼런스를 파라미터로 주입받는 방식을 사용
2.4 프로퍼티 값 설정 방법
애노테이션 : @Value
- 빈 의존관계는 아니지만 어떤 값을 외부에서 주입해야 하는 경우는 크게 두 가지가 있다.
- 하나는 환경에 따라 매번 달라질 수 있는 값이다. 대표적으로 DataSource 타입의 빈에 제공하는 DriverClass, URL, UserName, Password를 들 수 있다.
- 다른 하나는 클래스의 필드에 초기값을 설정해두고 대개는 그 값을 사용하지만 특별한 경우. 예를 들면 테스트나 특별한 이벤트 때는 초기값 대신 다른 값을 지정하고 싶을 경우
- @Value : 소스코드의 애노테이션을 이용해서 프로퍼티 값을 지정하는 방법
public class Hello {
private String name;
@Value("Everyone")
public void setName(String name) {
this.name = name;
}
}
- @Value 애노태이션은 스프링 컨테이너가 참조하는 정보이지 그 자체로 클래스의 필드에 값을 넣어주는 기능이 있는 것은 아니다. 따라서 테스트 코드와 같이 컨테이너 밖에서 사용된다면 @Value 애노태이션은 무시된다.
- @Value 애노테이션의 주요 용도는 자바 코드 외부의 리소스나 환경정보에 담긴 값을 사용하도록 지정해주는데 있다.
@Value("#{systemProperties['os.name']}")
String name; // 시스템 프로퍼티의 os.name 값을 가져와 name에 넣어준다.
@Value("${database.username}")
String username; // 환경정보를 담은 프로퍼티 파일을 따로 만들어두고 그 값을 가져올 수 있다.
PropertyEditor와 ConversionService
- 스프링은 두 가지 종류의 타입 변환 서비스를 제공한다. 디폴트로 사용되는 타입 변환기는 PropertyEditor라는 java.beans의 인터페이스를 구현한 것이다.
- 스프링이 기본적으로 지원하는 변환 가능한 타입은 아래와 같다.
- 기본 타입
- 배열 : 기본 타입의 배열로 선언된 프로퍼티에 한 번에 값을 주입할 수 있다. 값은 콤마로 구분한다.
- 기타 : Charset, Class, Currency, File, InputStream, Locale, Pattern, Resource, Timezone, URI, URL
- 문자열로 각 타입의 값을 어떻게 나타낼 수 있는지는 각 프로퍼티 에디터의 API 문서를 참조하면 된다. 예를 들어 문자열을 Charset 타입의 오브젝트로 변환하는 일은 CharsetEditor가 담당한다. 스프링 API 문서에서 CharsetEditor 클래스를 찾아보면 문자열로 Charset을 작성하는 방법을 알 수 있다.
- 스프링이 기본적으로 지원하지 않는 타입의 오브젝트를 직접 값으로 주입하고 싶다면 PropertyEditor 인터페이스를 구현해서 직접 변환기를 만들어 사용할 수 있다. 하지만 별로 권장하진 않는다.
- ConversionService는 자바빈에서 차용해서 사용해오던 PropertyEditor와 달리 스프링이 직접 제공하는 타입 변환 API다
- 스프링은 List, Set, Map, Properties와 같은 컬렉션 타입을 XML로 작성해서 프로퍼티에 주입하는 방법을 제공한다. 이때는 value 애트리뷰트를 통해 스트링 값을 넣는 대신 컬렉션 선언용 태그를 사용해야 한다.
- 컬렉션을 사용할 때는 가능한 한 타입 파라미터를 제공해서 컨테이너가 적합한 타입 변환기를 적용 할 수 있게 해야 한다. 예를 들어 List 보다는 List<String> 이런식으로 사용하는게 좋다.
프로퍼티 파일을 이용한 값 설정
- 환경에 따라 자주 변경될 수 있는 내용은 프로퍼티 파일로 분리하는 것이 가장 깔끔하다.
- 프로퍼티 파일에서 설정 값을 분리하면 얻을 수 있는 장점은 @Value를 효과적으로 사용할 수 있다는 점이다. @Value는 소스코드 안에 포함되는 애노테이션이어서 값을 수정하면 매번 새로 컴파일 해야한다. 하지만 @Value에서 프로퍼티 파일의 내 용을 참조하게 해주면 소스코드의 수정 없이 @Value를 통해 프로퍼티에 주입되는 값을 변경 할 수있다.
- PropertyPlaceHolderConfigurer : 프로퍼티 치환자(placeholer)는 프로퍼티 파일의 키 값을 ${} 안에 넣어서 만들어준다. 스프링 컨테이너는 초기화 작업중에 지정된 properties 파일을 읽고, 각 키에 ${}을 붙인 값과 동일한 value 선언을 찾는다. 그리고 발견된 value의 값을 프로퍼티 파일에 정의해둔 값으로 바꿔치기 한다.
- 프로퍼티 치환자를 이용해 설정 값을 프로퍼티 파일로 분리해주면 DB 연결정보가 변경되더라도 XML을 수정하는 대신 database.properties 파일만 수정해주면 된다. 또는 개발환경, 테스트환경, 운영환경에 각각 다른 database.properties파일을 두면 환경에 따라 다른 DB 연결정보를 이용할 수 있다.
- 스프링은 어떻게 ${}으로 선언된 값을 프로퍼티 파일의 내용으로 바꿔치기하는 것일까? 이 기능은 <context:property-placeholder> 태그에 의해 자동으로 등록되는 PropertyPlaceHolderConfigurer 빈이 담당한다. 이 빈은 빈 팩토리 후
처리기다. 빈 팩토리 후처리기는 빈 후처리기와 비슷하지만 동작하는 시점과 다루는 대상이 다르다.
- 빈 후처리기 : 빈 후처리기는 매 빈 오브젝트가 만들어진 직후에 오브젝트의 내용이나 오브젝트 자체를 변경할 때 시용된다.
- 빈 팩토리 후처리기 : 빈 설정 메타정보가 모두 준비됐을때 빈 메타정보 자체를 조작하기 위해 사용된다.
- PropertyPlaceHolderConfigurer는 프로퍼티 파일의 내용을 읽은 뒤에 빈 메타정보의 프로퍼티 값 정보에서 ${}으로 둘러싸인 치환자를 찾는다. 그리고 빈 메타정보의 프로퍼티 값 자체를 프로퍼티 파일의 내용을 이용해 변경해준다.
- SpEL : 프로퍼티 대체위치를 설정해두고 빈 팩토리 후처리기에서 바꿔주기를 기다리는 수동적인 방법과 달리, 다른 빈 오브젝트에 직접 접근할 수 있는 표현식을 이용해 원하는 프로퍼티 값을 능동적으로 가져오는 방법
- SpEL은 기본적으로 #{} 안에 표현식을 넣도록 되어있다.
- #{hello.name}이라는 표현식은 hello 빈의 name 프로퍼티를 의미한다.
- 스프링의 SpEL은 일반 프로그래밍 언어 수준에 가까운 강력한 표현식 기능을 지원한다. 다른 빈의 프로퍼티에 접근하는 것은 물론이고 메소드 호출도 가능하고 다양한 연산도 지원하며 클래스 정보에도 접근할 수 있다. 심지어 생성자를 호출해서 오브젝트를 만들 수도 있다.
- SpEL을 이용한 방법은 @Value에도 적용 가능하다.
2.5 컨테이너가 자동등록하는 빈
ApplicationContext, BeanFactory
- 스프링에서는 컨테이너 자신을 빈으로 등록해두고 필요하면 일반 빈에서 DI 받아서 사용 할 수 있다.
- ApplicationContext의 구현 클래스는 기본적으로 BeanFactory의 기능을 직접 구현하고 있지 않고 내부에 빈 팩토리 오브젝트를 별도로 만들어두고 위임하는 방식을 사용한다.
- 컨텍스트 내부에 만들어진 빈 팩토리 오브젝트를 직접 사용하고 싶다면, BeanFactory 타입으로 DI 해줄 필요가 있다.
ResourceLoader, ApplicationEventPublisherㄲ
- 스프링 컨테이너는 ResourceLoader이기도 하다. 따라서 서버 환경에서 다양한 Resource를 로딩할 수 있는 기능을 제공한다. 만약 코드를 통해 서블릿 컨텍스트의 리소스를 읽어오고 싶다면 컨테이너를 ResourceLoader 타입으로 DI 받아서 활용하면 된다.
- ApplicationEventPublisher : ApplicationListener 인터페이스를 구현한 빈에게 이벤트를 발생시킬 수 있는 publishEvent() 메소드를 가진 인터페이스다. 이 역시 ApplicationContext가 상속하고 있는 인터페이스의 한 가지다.
systemProperties, systemEnvironment
- 스프링 컨테이너가 직접 등록하는 빈 중에서 타입이 아니라 이름을 통해 접근할 수 있는 두 가지 빈이 있다. systemProperties 빈과 systemEnvironment 빈이다. 각각 Properties 타입과 Map 타입이기 때문에 타입에 의한 접근 방법은 적절치 않다.
- systemProperties 빈은 System.getProperties() 메소드가 돌려주는 Properties 타입의 오브젝트를 읽기전용으로 접근할 수 있게 만든 빈 오브젝트다. JVM이 생성해주는 시스템 프로퍼티 값을 읽을 수 있게 해준다.
- 시스템 프로퍼티의 특정 프로퍼티 값만 필요한 경우에는 SpEL을 사용하는 게 훨씬 간편하다. systemProperties 빈은 Map이므로 [] 연산자를 사용해 프로퍼티 값에 접근할 수 있다.
@Value("#{systemProperties['os,name']}") String osName;
- systemEnvironment는 System.getenv()에서 제공하는 환경변수가 담긴 Map 오브젝트다. JVM이 직접 정의해주는 시스템 프로퍼티와 달리 환경변수의 이름은 OS의 종류나 서버환경 설정에 따라 달라질 수 있기 때문에 서버환경이 바뀌면 주의해야 한다.
@Value("#{systemEnvironment['Path']}") String path;