elevne's Study Note
토비의 Spring 2 (1: ) 본문
Spring 에서 Application Context 는 그 자체로 IoC, DI 를 위한 빈 팩토리이면서 그 이상의 기능을 지니기도 한다. 스프링의 빈 팩토리와 애플리케이션 컨텍스트는 각각 기능을 대표하는 BeanFactory 와 ApplicationContext 라는 두 개의 인터페이스로 정의되어 있다. ApplicationContext 는 BeanFactory 인터페이스를 상속한 서브 인터페이스이다. 스프링 애플리케이션은 최소한 하나 이상의 IoC 컨테이너, 즉 애플리케이션 컨텍스트 오브젝트를 갖는다.
가장 간단하게 IoC 컨테이너를 만드는 방법은 ApplicationContext 구현 클래스의 인스턴스를 만드는 것이다.
StaticApplicationContext applicationContext = new StaticApplicationContext();
IoC 컨테이너를 만들긴 했지만, 아직은 아무런 하는 일이 없는 빈 컨테이너일 뿐이다. IoC 컨테이너로서 동작하려면 POJO 클래스와 설정 메타정보가 필요하다. 여기서 POJO 는, 애플리케이션의 핵심 코드를 담고 있는 클래스이다. 이전 1 권에서의 User, UserService 등의 클래스로 생각하면 될 것 같다. 이러한 POJO 클래스들을 작성했다면 그 다음으로는 앞서 만든 그 클래스들 중 애플리케이션에서 사용할 것을 선정하고 이를 IoC 컨테이너가 제어할 수 있도록 적절한 메타정보를 만들어 제공해줘야 한다.
스프링의 설정 메타정보는 BeanDefinition 인터페이스로 표현되는 순수한 추상 정보이다. 스프링의 메타정보는 특정 파일 포맷, 형식에 제한되지 않는다. 어떤 형식이든 BeanDefinition 으로 정의되는 스프링의 설정 메타정보의 내용을 표현한 것이 있다면 사용 가능하다. 원본의 포맷, 구조, 자료의 특성에 맞게 읽어와 BeanDefinition 오브젝트로 변환해주는 BeanDefinitionReader 가 있으면 된다. 스프링 애플리케이션은 POJO 클래스와 설정 메타정보를 이용해 IoC 컨테이너가 만들어주는 오브젝트의 조합인 것이다.
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("hi", Hi.class);
Hi hi = applicationContext.getBean("hi", Hi.class);
assertNotNull(hi);
StaticApplicationContext 는 코드에 의해 설정 메타정보를 등록하는 기능을 제공하는 애플리케이션 컨텍스트이다. 이번에는 직접 BeanDefinition 의 구현 클래스인 RootBeanDefinition (가장 기본적인 BeanDefinition 구현 클래스) 와 BeanReference 를 이용하여 빈에 대한 설정정보를 넣어줘본다.
StaticApplicationContext applicationContext = new StaticApplicationContext();
BeanDefinition personDef = new RootBeanDefinition(Person.class);
personDef.getPropertyValues().addPropertyValue("name", "Spring");
applicationContext.registerBeanDefinition("person", personDef);
BeanDefinition hiDef = new RootBeanDefinition(Hi.class);
hiDef.getPropertyValues().addPropertyValue("person", new RuntimeBeanReference("person"));
applicationContext.registerBeanDefinition("hi", hiDef);
Hi hi = applicationContext.getBean("hi", Hi.class);
hi.print();
Spring 에는 다양한 용도로 쓸 수 있는 여러 ApplicationContext 구현 클래스가 있다. (직접 코드를 통해 ApplicationContext 오브젝트를 생성하는 경우는 거의 없다)
StaticApplicationContext: 이는 코드를 통해 빈 메타정보를 등록하기 위해 사용된다. (스프링의 기능에 대한 학습 테스트를 만들 때를 제외하면 실제로 사용되지 않는다고 함)
GenericApplicationContext: 가장 일반적인 애플리케이션 컨텍스트의 구현체로, 실전에서 사용할 수 있는 모든 기능을 갖추고 있다. 이는 StaticApplicationContext 와 달리 XML 파일과 같은 외부의 리소스에 있는 빈 설정 메타정보 리더를 통해 읽어들여 메타정보로 전환해 사용한다. XML 로 작성된 빈 설정정보를 읽어서 컨테이너에 전달하는 대표적인 빈 설정정보 리더는 XmlBeanDefinitionReader 이다. 이 리더를 GenericApplicationContext 가 이용하도록 하면 된다. (그 외에도 프로퍼티 파일에서 빈 설정 메타정보를 가져오는 PropertiesBeanDefinitionReader 도 제공함) 대부분의 스프링 개발자는 이 또한 이용할 일이 거의 없을 것이다. 이를 코드에서 직접 만들고 초기화하지는 않지만, 실제로는 자주 사용된다. 스프링 테스트 컨텍스트 프레임워크를 활용하는 JUnit 테스트는 테스트 내에서 사용할 수 있도록 애플리케이션 컨텍스트를 자동으로 만들어주는데, 이 때 사용되는 것이 GenericApplicationContext 이다.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="hi" class="org.example.examples.Hi" >
<property name="person" ref="person" />
</bean>
<bean id="person" class="org.example.examples.Person">
<property name="name" value="Spring XML Test" />
</bean>
</beans>
GenericApplicationContext applicationContext = new GenericApplicationContext();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(applicationContext);
reader.loadBeanDefinitions("/genericApplicationContext.xml");
applicationContext.refresh();
Hi hi = applicationContext.getBean("hi", Hi.class);
hi.print();
GenericXmlApplicationContext: GenericApplicationContext 와 XmlBeanDefinitionReader 이 결합된 클래스이다.
WebApplicationContext: 스프링에서 가장 많이 사용되는 애플리케이션 컨텍스트다. 이는 웹 환경에서 사용할 때 필요한 기능이 추가된 애플리케이션 컨텍스트다. 웹 애플리케이션은 지금까지 작성해본 것과는 근본적으로 동작 방식이 다르다. 독립 자바 프로그램은 자바 VM 에 main() 메소드를 가진 클래스르 시작시켜 달라고 요청할 수 있지만, 웹에서는 main() 메소드를 호출할 방법이 없다. 게다가 사용자도 여럿이며 동시에 웹 애플리케이션을 사용한다. 그래서 웹 환경에서는 main() 메소드 대신 서블릿 컨테이너가 브라우저로부터 오는 HTTP 요청을 받아서 해당 요청에 매핑되어 있는 서블릿을 실행해주는 방식으로 동작한다. 서블릿이 일정의 main() 메소드의 역할을 하는 것이다. 이를 위해서, 서블릿을 만들어두고 미리 애플리케이션 컨텍스트를 생성해둔 다음, 요청이 서블릿으로 들어올 때마다 getBean() 으로 필요한 빈을 가져와 정해진 메소드를 실행해준다. 스프링은 이런 웹 환경에서 애플리케이션 컨텍스트를 생성하고 설정 메타 정보로 초기화해주며, 클라이언트로 들어오는 요청마다 적절한 빈을 찾아 이를 실행해주는 기능을 가진 DispatcherServlet 이라는 이름의 서블릿을 제공한다. 스프링이 제공해준 서블릿을 web.xml 에 등록하는 것만으로 웹 환경에서 스프링 컨테이너가 만들어지고 애플리케이션을 실행하는 데 필요한 대부분의 준비가 끝난다.
모든 애플리케이션 컨텍스트는 부모 애플리케이션 컨텍스트를 가질 수 있다. DI 를 위해 빈을 찾을 때는 부모 애플리케이션 컨텍스트의 빈까지 모두 검색한다. (가장 위에 존재하는 루트 컨텍스트까지) 이 때, 자식 컨텍스트에는 빈 검색을 요청하지 않는다. 검색 순서는 항상 자신이 먼저이고, 그 다음이 직계부모의 순서다. 기존 설정을 수정하지 않고 사용하지만, 일부 빈 구성을 바꾸고 싶은 경우에는 애플리케이션 계층구조를 만드는 방법이 편리하다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="hi" class="org.example.examples.Hi" >
<property name="person" ref="person" />
</bean>
<bean id="person" class="org.example.examples.Person">
<property name="name" value="Spring XML Test 2" />
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="hi2" class="org.example.examples.Hi">
<property name="person" ref="person" />
</bean>
</beans>
ApplicationContext parent = new GenericXmlApplicationContext("/parentContext.xml");
GenericApplicationContext child = new GenericApplicationContext(parent);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(child);
reader.loadBeanDefinitions("/childContext.xml");
child.refresh();
Hi hi = child.getBean("hi2", Hi.class);
hi.print();
서버에서 동작하는 애플리케이션에서 스프링 IoC 컨테이너를 사용하는 방법은 크게 세 가지로 구분해볼 수 있다. 그 중 두 가지 방법은 웹 모듈 안에 컨테이너를 두는 것이고, 나머지 하나는 엔터프라이즈 애플리케이션 레벨에 두는 방법이다. 먼저 웹 애플리케이션 안에 WebApplicationContext 타입의 IoC 컨테이너를 두는 방법을 알아본다.
자바 서버에서는 하나 이상의 웹 모듈 (WAR) 을 배치해서 사용할 수 있다. 하나의 웹 모듈은 여러 개의 서블릿을 가질 수 있다. 스프링은 몇 개의 서블릿이 중앙집중식으로 요청을 다 받아서 처리하는 프론트 컨트롤러 패턴을 사용한다. 웹 애플리케이션 내에서 동작하는 IoC 컨테이너는 두 가지 방법으로 만들어진다. 1. 스프링 애플리케이션의 요청을 처리하는 서블릿 안에서 만들어지는 것 / 2. 웹 애플리케이션 레벨에서 만들어지는 것. 일반적으로는 이 두 가지 방식을 모두 사용해 컨테이너를 만든다고 한다. 그래서 스프링 웹 애플리케이션에는 두 개의 컨테이너, WebApplicationContext 가 만들어진다. 웹 애플리케이션 레벨에 등록되는 컨테이너는 보통 전체 계층구조 내에서 가장 최상단에 위치한 루트 컨텍스트가 되기에 루트 웹 애플리케이션 컨텍스트라고 불린다.
웹 애플리케이션 레벨에 만들어지는 루트 웹 애플리케이션 컨텍스트를 등록하는 가장 간단한 방법은 서블릿의 이벤트 리스너를 이용하는 것이다. 스프링은 웹 애플리케이션의 시작과 종료 시 발생하는 이벤트를 처리하는 리스너인 ServletContextListener 을 이용한다. 이를 이용해서 웹 애플리케이션이 시작될 때 루트 애플리케이션 컨텍스트를 만들어 초기화하고, 종료될 때 함께 컨텍스트를 종료하는 기능을 가진 리스너를 만들 수 있다. 스프링은 이러한 기능을 가진 리스너인 ContextLoaderListener 을 제공한다. 이를 이용하는 방법은 매우 간단하다. 웹 애플리케이션의 web.xml 에 리스너 선언을 넣어주기만 하면 된다. (설정방법은 책 pg.77~79)
스프링의 웹 기능을 지원하는 프론트 컨트롤러 서블릿은 DispatcherServlet 이다. 이는 web.xml 에 등록해서 사용할 수 있는 평범한 서블릿이라고 한다. (설정방법은 책 pg.80~82)
그 다음으로는 빈 등록 방법에 대해 알아본다. 빈 등록은 빈 메타정보를 작성해서 컨테이너에게 주면 되는 것이다. 가장 직접적이고 원시적인 방법은 BeanDefinition 구현 오브젝트를 직접 생성해서 사용하는 것이지만, 이는 실무에서 사용하기에는 무리가 있다. 이 외의 방법이 5 가지 있다.
XML <bean> 태그: 가장 단순하면서도 가장 강력한 방법이다. 스프링 빈 메타정보의 거의 모든 항목을 지정할 수 있으므로 세밀한 제어가 가능하다. (위에서 사용한 방법)
XML 네임스페이스와 전용 태그: <bean> 태그 외에도 다양한 스키마에 정의된 전용 태그를 사용해 빈을 등록할 수 있다. 예를 들어 AOP 를 사용하는 빈을 만들 때 다음과 같이
<bean id="myPointcut" class="org.springframework.aop.aspectj.AspectJExpressionPointcut" >
<property name="expression" value="execution(* *..*ServiceImpl.upgrade*(..))" />
</bean>
기존과 동일하게 빈을 등록해줄 수도 있긴하다. 스프링은 이러한 기술적인 설정과 기반 서비스를 빈으로 등록할 때를 위해 의미가 잘 드러나는 네임스페이스와 태그를 가진 설정 방법을 제공한다.
<aop:pointcut id="mypointcut" expression="execution(* *..*ServiceImpl.upgrade*(..))" />
위 전용태그 방식은 <bean> 으로 선언한 것과 동일한 빈 설정 메타정보로 변환된다. (이 외에도 전용태그 하나로 동시에 여러 개의 빈을 만들 수도 있다)
자동인식을 이용한 빈 등록; 스테레오타입 애노테이션과 빈 스캐너: 모든 빈을 XML 에 일일이 선언하는 것은 번거로울 수 있다. 특별한 애노테이션을 사용하여 클래스를 자동으로 찾아 빈으로 등록되게끔 할 수 있다. 이르 빈 스캐닝이라고 하고, 빈 스캐닝을 담당하는 오브젝트를 빈 스캐너라고 한다. 빈 스캐너에 내장된 디폴트 필터는 @Component 애노테이션 또는 @Component 를 메타 애노테이션으로 가진 애노테이션이 부여된 클래스 (스테레오타입 애노테이션이라고 함) 를 선택한다. AnnotationConfigApplicationContext 가 빈 스캐너를 내장하는 애플리케이션 컨텍스트 구현 클래스이다. 이러한 자동인식을 통한 빈 등록을 사용하기 위해서는 XML 을 통해 빈 스캐너를 등록할 수도, (<context:component-scan>) 빈 스캐너를 내장한 애플리케이션 컨텍스트를 사용하는 방법도 있다. (web.xml 안에 루트 컨텍스트가 XML 대신 빈 스캐너를 이용하도록 설정)
자바 코드에 의한 빈 등록; @Configuration 클래스의 @Bean 메소드: 이 방식을 사용하면 컴파일러나 IDE 를 통한 타입 검증이 가능해지고, 자동완성과 같은 IDE 지원 기능을 최대한 이용할 수 있다. 이해하기도 쉬워지고 복잡한 빈 설정이나 초기화 작업을 손쉽게 적용할 수 있다.
자바 코드에 의한 빈 등록; @Bean 메소드: @Configuration 이 붙지 않은 일반 POJO 클래스에서도 @Bean 을 사용할 수 있다. 단, @Configuration 이 붙지 않은 클래스 내의 @Bean 을, DI 를 위해 다른 @Bean 에서 호출한다면 싱글턴 빈이 아니라 매번 다른 오브젝트를 받게된다.
그렇다면 위 방법 중 어떤 것을 택해야 할까? 자주 사용되는 방법은 아래와 같다.
- XML 단독 사용: 모든 설정정보를 자바 코드에서 분리하고 순수한 POJO 코드를 유지할 수 있다.
- XML 과 빈 스캐닝의 혼용: 애플리케이션 3 계층의 핵심 로직을 담고있는 빈 클래스는 그다지 복잡한 빈 메타정보를 필요로하지 않는다. 대부분 싱글톤이며 클래스 당 하나만 만들어지므로 빈 스캐닝에 의한 자동인식 대상으로 적절하다. 반면 자동인식 방식으로는 등록하기 불편한 기술 서비스, 기반 서비스, 컨테이너 설정 등의 빈은 XML 을 사용하면 된다.
- XML 없이 빈 스캐닝 단독 사용: 타입에 안전한 설정정보를 만들 수 있지만, 스프링이 제공하는 스키마에 정의된 전용 태그를 사용할 수 없다는 단점이 있다.
Reference:
토비의 Spring
'Backend > Toby Spring' 카테고리의 다른 글
토비의 Spring (10: Spring Annotations) (0) | 2023.06.20 |
---|---|
토비의 Spring (9: AOP(5)) (0) | 2023.06.13 |
토비의 Spring (9: AOP(4)) (0) | 2023.06.09 |
토비의 Spring (9: AOP(3)) (0) | 2023.06.08 |
토비의 Spring (9: AOP(2)) (0) | 2023.06.07 |