[Info]Tags categorized posts and contents patterns..

[AJAX] Ajax Code E xamples.. [Book] About the book.. [CSS] CSS Code E xamples.. [DB] Sql Code E xamples.. [DEV] All development stor...

2016년 4월 14일 목요일

[JAVA]4장 IoC 컨테이너 #8..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.

4.8 컨테이너 확장점(Extension Points)
보통 어플리케이션 개발자는 ApplicationContext 구현 클래스들의 서브클래스를 만들 필요가 없다. 대신 특수한 통합 인터페이스의 구현을 연결해서 스프링 IoC 컨테이너를 확장할 수 있다.

4.8.1 BeanPostProcessor를 사용한 빈 커스터마이징

BeanPostProcessor 인터페이스는 개발자가 원하는(또는 컨테이너의 기본로직을 오버라이드하는) 인스턴스화 로직, 의존성 처리로직 등을 구현할 수 있는 callback methods를 정의한다. 스프링 컨테이너가 인스턴스화, 설정, 빈 초기화 작업을 끝낸 뒤 어떤 커스텀 로직을 구현하려면 하나 이상의 BeanPostProcessor 구현체를 연결할 수 있다.

여러 개의 BeanPostProcessor 인스턴스를 설정할 수 있고 order 프로퍼티를 통해 이러한 BeanPostProcessor의 실행순서를 제어할 수 있다. 이 order 프로퍼티는 BeanPostProcessor가 Ordered 인터페이스를 구현했을 때만 설정할 수 있다. 직접 BeanPostProcessor를 작성한다면 Ordered 인터페이스도 같이 구현해야 한다. 더 자세한 정도는 BeanPostProcessor 인터페이스와 Ordered 인터페이스의 JAVADOC을 참고해라. 또한 BeanPostProcessors의 프로그래밍적인 등록을 참고해라.



Note
BeanPostProcessor는 빈(또는 객체) 인스턴스상에서 동작한다. 즉, 스프링 IoC 컨테이너는 빈 인스턴스를 인스턴스화한 다음에 BeanPostProcessor가 자신의 일을 수행한다.

BeanPostProcessor는 컨테이너에 한정된 (per-container) 범위를 갖는데 이는 컨테이너 계층(hierarchies)을 사용할 때만 적절한 표현이다. 하나의 컨테이너에서 BeanPostProcessor를 정의한다면 BeanPostProcessor는 해당 컨테이너의 빈들에 대한 후처리(post-process) 만 할 것이다. 다시 말하면 한 컨테이너에서 정의된 빈들은 다른 빈에서 정의된 BeanPostProcessor가 후처리를 하지 않고 두 컨테이너가 같은 계층에 속해있더라도 마찬가지이다.

실제 빈 정의를 바꾸려면(예를 들어 빈을 정의하는 청사진(blueprint)) Section 4.8.2, “BeanFactoryPostProcessor로 설정 메타데이터 커스터마이징하기”에서 설명했듯이 BeanFactoryPostProcessor를 사용해야 한다.

org.springframework.beans.factory.config.BeanPostProcessor 인터페이스는 정확히 2개의 콜백 메서드로 구성되어 있다. 이러한 클래스를 컨테이너의 후처리자(post-processor)로 등록했다면 컨테이너가 생성한 각 빈 인스턴스에 대해서 후처리자는 컨테이너 초기화 메서드(InitializingBean의 afterPropertiesSet()와 선언된 init 메서드같은)가 호출되기 이전뿐만 아니라 빈 초기화 콜백 이후에 대해 컨테이너로부터 콜백을 얻는다. 후처리자는 콜백을 완전히 무시해버리는 것을 포함해서 빈 인스턴스에 대한 어떤 액션도 할 수 있다. 빈 후처리자는 보통 콜백 인터페이스를 확인하거나 빈을 프록시로 감쌀 것이다. 몇몇 스프링 AOP 인프라 클래스는 프록시-랩핑(proxy-wrapping) 로직을 제공하려고 빈 후처리자로 구현되었다.

ApplicationContext는 설정메타데이터에서 선언된 빈중에 BeanPostProcessor 인터페이스를 구현한 빈을 자동으로 찾아낸다. ApplicationContext는 이러한 빈을 빈 생성후에 호출될 수 있도록 후처리자로 등록한다.빈 후처리자는 다른 빈들처럼 컨테이너에 배포될 수 있다.



프로그래밍적인 BeanPostProcessors 등록
BeanPostProcessor 등록은 ApplicationContext의 자동탐지(위에서 설명했듯이)를 통한 접근을 권장하지만 addBeanPostProcessor 메서드를 사용해서 ApplicationContext에 프로그래밍적으로 BeanPostProcessor를 등록하는 것도 가능하다. 이는 등록하기 전에 조건을 평가해 볼 필요가 있거나 계층사이의 컨텍스트를 넘어서 빈 후처리자를 복사해야 할 때 유용하다. 하지만 프로그래밍으로 추가된 BeanPostProcessors는 Ordered 인터페이스를 고려해서는 안된다. 여기서는 등록하는 순서가 실행의 순서이가. 프로그래밍으로 등록된 BeanPostProcessors는 명시적인 순서에 상관없이 항상 자동탐지로 등록된 BeanPostProcessors보다 먼저 처리된다.

BeanPostProcessors와 AOP 자동 프록싱(auto-proxying)
BeanPostProcessor 인터페이스를 구현한 클래스들은 특별하고 컨테이너가 다르게 취급한다. 모든 BeanPostProcessors와 BeanPostProcessors가 직접 참조하는 빈들은 ApplicationContext의 특별한 시작 단계의 일부로 시작할 때 인스턴스화된다. 그 다음 모든 BeanPostProcessors가 정렬된 방식으로 등록되고 컨테이너의 모든 빈에 적용된다. AOP 자동 프록싱은 BeanPostProcessor 자체로 구현되었기 때문에 BeanPostProcessor들과 BeanPostProcessor들이 직접 참조하는 빈들 모두 자동 프록싱에 적합하지 않으므로 관점(aspect)을 위빙(weave)하지 말아야 한다.

이러한 빈에 대해서는 정보성 로그 메시지를 봐야한다. : “Bean foo is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)”.

다음 예제는 ApplicationContext에서 BeanPostProcessors를 어떻게 작성해서 등록하고 사용하는 지를 보여준다.

4.8.1.1 예제 : BeanPostProcessor 스타일의 Hello world
첫 예제는 기본적인 사용방법을 설명한다. 예제는 컨테이너가 생성한 각 빈의 toString() 메서드를 호출하고 시스템콘솔에 결과 문자열을 출력하는 커스텀 BeanPostProcessor 구현체를 보여준다.

다음 커스텀 BeanPostProcessor 구현클래스 정의를 보자.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Java

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

  // 간단히 현상태의 인스턴스화 된 빈을 리턴한다
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    return bean; // 여기서 잠재적으로 어떤 객체의 참조도 리턴할 수 있다...
  }

  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("Bean '" + beanName + "' created : " + bean.toString());
    return bean;
  }
}

Xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:lang="http://www.springframework.org/schema/lang"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://www.springframework.org/schema/lang
         http://www.springframework.org/schema/lang/spring-lang-3.0.xsd">

  <lang:groovy id="messenger"
        script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
      <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
  </lang:groovy>

  <!--
      위의 빈(messenger)가 인스턴스화 되었을   커스텀 
      BeanPostProcessor 구현체는 시스템콘솔에 결과를 출력할 것이다
   -->
  <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

InstantiationTracingBeanPostProcessor를 얼마나 간단히 정의했는지 봐라. 그냥 다른 빈처럼 의존성 주입될 수 있는 빈이기 때문에 이름도 지정하지 않았다. (앞의 설정은 Grooby 스크립트가 지원하는 빈도 정의했다. 스프링 2.0의 동적언어 지원은 Chapter 27, Dynamic language support 챕터에서 자세히 설명한다.)

다음의 간단한 자바 어플리케이션의 앞의 코드와 설정을 실행한다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Java

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {
  public static void main(final String[] args) throws Exception {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
    Messenger messenger = (Messenger) ctx.getBean("messenger");
    System.out.println(messenger);
  }
}

앞의 어플리케이션의 결과는 다음과 같다.
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

4.8.1.2 예제 : RequiredAnnotationBeanPostProcessor
보통 스프링 IoC 컨테이너를 확장하기 위해서 커스텀 BeanPostProcessor 구현체와 겹합된 어노테이션이나 콜백 인터페이스를 사용한다. 예제는 (임의의) 어노테이션으로 표시된 빈의 JavaBean 프로퍼티를 보장하는 스프링 배포판에 포함된 스프링의 RequiredAnnotationBeanPostProcessor ? BeanPostProcessor 구현체는 실제로 값으로 의존성 주입(되도록 설정)된다.

4.8.2 BeanFactoryPostProcessor로 설정 메타데이터 커스터마이징하기
이제 살펴보려는 다음 확장점은 org.springframework.beans.factory.config.BeanFactoryPostProcessor 이다. 이 인터페이스의 의미는 BeanPostProcessor가 빈 설정 메타데이터상에서 수행된다는 주요한 차이점을 제외하면 BeanPostProcessor와 유사하다. 이는 스프링 IoC 컨테이너가 BeanFactoryPostProcessors가 설정 메타데이터를 읽고 컨테이너가 BeanFactoryPostProcessors를 제외한 모든 빈을 인스턴스화하기 전에 바꿀 수 있도록 한다.

여러 BeanFactoryPostProcessors를 설정할 수 있고 order 프로퍼티를 설정해서 이 BeanFactoryPostProcessor들의 실행순서를 제어할 수 있다. 하지만 BeanFactoryPostProcessor가 Ordered 인터페이스를 구현했을 때만 order 프로퍼티를 설정할 수 있다. 직접 BeanFactoryPostProcessor를 작성한다면 Ordered 인터페이스도 구현하는 것을 고려해야 한다. 더 자세한 내용은 BeanFactoryPostProcessor와 Ordered 인터페이스의 Javadoc을 참고해라.



Note
실제 빈 인스턴스를 바꾸고 싶다면 (예를 들어 설정메타데이터로 생성된 객체들) 대신 BeanPostProcessor를 사용할 필요가 있다. (앞의 Section 4.8.1, “BeanPostProcessor를 사용한 빈 커스터마이징”에서 설명했다.) 이는 기술적으로 BeanFactoryPostProcessor내에서 빈 인스턴스들과 연동될 가능성이 있으므로 너무 이른 시기에 빈을 인스턴스화해서 표준 컨테이너 라이프사이클을 위반하게 한다. 이는 빈의 후처리를 우회하는 것과 같은 부정적인 효과를 일으킬 것이다.

또한, BeanFactoryPostProcessors는 컨테이너에 한정된(per-container) 범위를 가진다. 이는 컨테이너 계층을 사용할 경우에만 관련있다. 한 컨테이너에서 BeanFactoryPostProcessor을 정의한다면 해당 컨테이너의 빈 정의에만 적용될 것이다. 한 컨테이너의 빈 정의는 두 컨테이너가 같은 계층에 속해있더라도 다른 컨테이너의 BeanFactoryPostProcessors에 의해서는 후처리되지 않을 것이다.

빈 팩토리 후처리자(post-processor)는 컨테이너를 정의하는 설정 메타데이터를 수정하기 위해 ApplicationContext 내부에서 선언되었을 때 자동적으로 실행된다. 스프링은 PropertyOverrideConfigurer와 PropertyPlaceholderConfigurer같은 빈 팩토리 후처리자를 미리 다수 정의해 놓고 있다. 커스텀 프로퍼티 에디터를 등록하는 것 같은 커스텀 BeanFactoryPostProcessor도 사용할 수 있다.

ApplicationContext는 ApplicationContext내의 빈 중에서 BeanFactoryPostProcessor 인터페이스를 구현한 빈을 자동으로 감지한다. ApplicationContext는 적절한 시기에 이러한 빈들을 빈 팩토리 후처리자로 사용한다. 다른 빈처럼 이러한 후처리자 빈을 배포할 수 있다.



Note
BeanPostProcessor처럼 보통 BeanFactoryPostProcessor들을 지연 초기화되도록 설정하지 않는다. Bean(Factory)PostProcessor을 참조하는 다른 빈이 없다면 이 후처리자는 절대 인스터스화되지 않을 것이다. 그러므로 지연 초기화되도록 설정해도 이는 무시될 것이고 <beans /> 요소의 선언에서 default-lazy-init 속성을 true로 설정했더라도 Bean(Factory)PostProcessor는 인스터스화 되려고 할 것이다.

4.8.2.1 예제: PropertyPlaceholderConfigurer
표준 자바 Properties 형식을 사용하는 분리된 파일의 빈 정의에서 프로퍼티 값들을 구체화히하기 위해서 PropertyPlaceholderConfigurer를 사용한다. PropertyPlaceholderConfigurer를 사용하면 복잡합이나 핵심 XML 정의 파일이나 컨테이너에 대한 파일들을 수정하는 위험성없이 데이터베이스 URL과 비밀번호같은 환경의존적인 프로퍼티들을 커스터마이징 하기 위해 사람들이 어플리케이션을 배포할 수 있게 한다.

플레이스홀더 값들과 함께 DataSource가 정의된 다음의 XML 기반 설정 메타데이터의 일부를 보자. 예제는 외부 Properties 파일에서 설정되 프로퍼티들을 보여준다. 런타임에서 PropertyPlaceholderConfigurer는 DataSource의 몇몇 프로퍼티들을 교체할 메타데이터를 적용한다. 교체할 값들은 다음의 Ant / log4j / JSP EL 스타일의 ${property-name} 혁식으로 placeholders로 지정한다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Xml

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
    class="org.apache.commons.dbcp.BasicDataSource">
  <property name="driverClassName" value="${jdbc.driverClassName}"/>
  <property name="url" value="${jdbc.url}"/>
  <property name="username" value="${jdbc.username}"/>
  <property name="password" value="${jdbc.password}"/>
</bean>

실제 값들은 표준 자바 Properties 형식의 다른 파일에서 가져온다.


C-like
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

그러므로 ${jdbc.username} 문자열은 런타임에서 'sa' 값으로 교체되고 프로퍼티 파일에서 키가 일치하는 다른 플레이스홀더 값들에도 똑같이 적용된다. PropertyPlaceholderConfigurer는 빈 정의에서 대부분의 프로퍼티와 속성의 플레이스 홀더를 확인한다. 게다가 플레이스홀더 접두사와 접미사는 커스터마이징 될 수 있다.

스프링 2.5에서 도입된 context 네임스페이스로 전용 설정 요소의 프로퍼티 플레이스홀더를 설정할 수 있다. location 속성에서 콤마로 분리된 리스트로 하나 이상의 위치를 제공할 수 있다.


1
2
3
Xml

<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>

PropertyPlaceholderConfigurer는 지정한 Properties 파일에서만 프로퍼티를 찾지 않는다. 지정한 프로퍼티 파일들에서 프로퍼티를 찾을 수 없다면 기본적으로 자바 System 프로퍼티들도 확인한다. 다음 지원하는 세가지 정수값 중 하나로 설정해서 이러한 동작을 수정할 수 있다.
  • never (0): 시스템 프로퍼티들을 확인하지 않는다
  • fallback (1): 지정한 프로퍼티 파일들에서 처리할 수 없으면 시스템 프로퍼티를 확인한다. 이 값이 기본값이다.
  • override (2): 지정한 프로퍼티 파일들보다 시스템 프로퍼티를 먼저 확인한다. 이는 시스템 프로퍼티가 다른 프로퍼티 소스를 오버라이드하도록 한다.
더 자세한 내용은 PropertyPlaceholderConfigurer의 Javadoc을 봐라.


클래스명 치환
런타임에서 특정 구현 클래스를 선택하는데 종종 유용한 클래스명을 치환에 PropertyPlaceholderConfigurer를 사용할 수 있다. 예를 들어


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Xml

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="locations">
      <value>classpath:com/foo/strategy.properties</value>
  </property>
  <property name="properties">
      <value>custom.strategy.class=com.foo.DefaultStrategy</value>
  </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

런타임에서 유효한 클래스를 찾지 못하면 지연초기화 되지 않는 빈에 대해 ApplicationContext의 preInstantiateSingletons() 단계에서 클래스가 생성될 때 빈 처리가 실패한다.

4.8.2.2 예제: PropertyOverrideConfigurer
또다른 빈 팩토리 후처리자인 PropertyOverrideConfigurer는 PropertyPlaceholderConfigurer와 비슷하지만 원래의 정의가 빈 프로퍼티에 대해 기본값을 갖거나 값을 갖지 않을 수 있다는 점이 다르다. 오버라이딩 된 Properties 파일에 어떤 빈 프로퍼티에 대한 진입점이 없다면 기본 컨텍스트 정의가 사용된다.

빈 정의는 오버라이드되었다는 것을 알지 못한다. 그래서 오버라이드 설정자를 사용하는 XML 정의에서 당장은 명확하지 않다. 같은 빈 프로퍼티에 대해 다른 값을 정의한 여러 PropertyOverrideConfigurer 인스턴스가 있는 경우 오버라이딩 메카니즘에 따라 마지막 PropertyOverrideConfigurer의 값을 사용한다.

프로퍼티 파일 설정은 다음과 같은 형식이다.



C-like
beanName.property=value

예를 들어



C-like
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

이 예제 파일은 driver와 url 프로퍼티를 가진 dataSource라는 빈을 담고있는 컨테이너 정의와 함께 사용된다.

오버라이드된 최종 프로퍼티를 제외한 경로의 모든 컴포넌트가 이미 null이 아니라면 프로퍼티 이름의 혼합도 역시 지원한다.(아마도 생성자에 의해서 초기화될 것이다.) 이 예제에서...



C-like
foo.fred.bob.sammy=123

... foo 빈의 fred 프로퍼티의 bob 프로퍼티의 sammy 프로퍼티는 123이라는 스칼라(scalar) 값으로 설정된다.



Note
지정한 오버라이드 값은 항상 리터럴(literal) 값이다. 이 값들은 빈 참조로 변환되지 않는다. 이 관례는 XML 빈 정의에서 원래의 값이 빈 참조를 지정한 경우에도 적용된다.

스프링 2.5에서 도입된 context 네임스페이스를 사용하면 전용 설정 요소로 설정 프로퍼티를 오버라이딩 하는 것이 가능하다.


1
2
3
Xml

<context:property-override location="classpath:override.properties"/>

4.8.3 FactoryBean로 인스턴스화(instantiation) 로직 커스터마이징하기
org.springframework.beans.factory.FactoryBean 인터페이스를 구현한 객체들은 그 자체로 팩토리이다.

FactoryBean 인터페이스는 스프링 IoC 컨테이너의 인스턴스화 로직에 플러그인할 수 있는 지점(a point of pluggability)이다. (잠재적으로) 장황한 XML과는 반대로 자바로 더 좋게 표현한 복잡한 인스턴스화 코드가 있다면 자신만의 FactoryBean를 생성해서 클래스에 복잡한 인스턴스화 로직을 작성한 뒤 컨테이너에 커스터마이징한 FactoryBean를 연결한다.

FactoryBean 인터페이스는 세 가지 메서드를 제공한다.

  • Object getObject(): 이 팩토리가 생성한 객체의 인스턴스를 리턴한다. 인스턴스는 이 팩토리가 싱글톤을 리턴하는지 프로토타입을 리턴하는지에 따라 공유될 수도 있다.
  • boolean isSingleton(): FactoryBean가 싱글톤을 리턴하면 true를 리턴하고 싱글톤이 아니면 false를 리턴한다.
  • Class getObjectType(): getObject() 메서드로 리턴되는 객체의 타입을 리턴한다. 타입을 미리 알고 있지 않다면 null을 리턴한다.
FactoryBean의 개념과 인터페이스는 스프링 프레임워크내 곳곳에서 사용된다. 스프링에는 FactoryBean 인터페이스의 구현체가 50개 이상 포함되어 있다.

빈이 생성한 것 대신 컨테이너에 실제 FactoryBean 인스턴스 자체를 요청할 필요가 있는 경우 ApplicationContext의 getBean() 메서드를 호출할 때 앤드기호(&)가 있는 빈의 id로 시작해라. 그래서 myBean이라는 id로 주어진 FactoryBean에 대해 컨테이너에서 getBean("myBean")를 호출하면 FactoryBean의 결과를 리턴한다. 반면에 getBean("&myBean")를 호출하면 FactoryBean 인스턴스 자체를 리턴한다.

[Book] Pro Git..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

Pro Git - 10점
Chacon, Scott
Springer-Verlag New York Inc
박창우, 이성환 옮김

Pro GitGithub에서 일하는 Scott Chacon이 쓴 책으로 어떻게 출판사에서 이를 허락했는지 모르지만 Apress의 책으로도 출판되면서 Pro Git사이트에서 무료로 공개되어 있습니다. 이 책은 Github의 저장소에서 관리되고 있는데 올 초 pismute님과 lethee님이 이 책을 번역해서 공개했습니다. 정식으로 국내에 출판된 책이 아니기 때문에 위의 책 정보는 원서를 기준으로 링크했지만 무료로 공개된 번역서를 읽었습니다. 무료번역서는 여기서 다운로드 받을 수 있으며 현재 최신 버전은 2012-02-26에 빌드한 버전입니다.

결론부터 말하자면 이 책은 내용이 너무 좋은데다가 무료로 번역임을 감안하지 않더라도 번역의 품질이 좋은 편입니다. 국내의 유일한 Git책인 Git, 분산 버전 관리 시스템[과거에는 왜 안가져왔지..?? 아마도 그 때는 Git 에 대한 관심도가 전혀 없었던 듯.. ㅎㅎ]가 Git을 사용하는데 중점을 두고 있다면 Pro Git은 Git을 이해하는데 중점을 두고 있다고 느껴집니다. 이 2가지 접근에 대해서 어느 쪽이 과연 좋은가는 저도 잘 모르겠습니다. Git을 공부할 때 Git에 동작방식이나 내부 구조, DVCS의 특성들까지 다 이해하면서 배운다면 참 좋겠지만 저같은 경우는 기본적인 명령어인 clone, commit, push등으로 기본적인 DVCS를 다루다 보니 활용하는데 한계를 점점 느끼면서 내부적인 부분이 궁금해진 경우인데 마침 이 책이 번역되어서 읽었는데 궁금한 상당부분이 해소되었습니다.

이 두 가지 접근은 사실 둘다 괜찮은 방법이라고 생각합니다.(어쩌면 개개인의 성향에 따른 것일 수도 있겠습니다.) 물론 Pro Git에서도 기본적인 Git의 사용법은 당연히 다루고 있고 2장 Git의 기초와 3장 Git 브랜치를 읽고 나면 기본적인 사용을 할 수 있습니다. 하지만 앞에서 얘기했듯이 Git을 이해하게 하는데 중점을 두고 있기 때문에 설명할 때도 처음 Git을 배운다면 약간 어려울 수 있는 개념들이 함께 등장하고 있습니다. 설명이 좋지 않다는 얘기도 아니고 이 부분은 Git을 쓰려면 꼭 배워야 하는 개념들이지만 저같은 경우는 간단한 명령어들은 어느정도 쓰고 있었기에 가볍게 보면서 읽을 수 있었지만 아주 모르는 사람들이 보면 어려울 수도 있겠다는 생각이 듭니다.(사실 일단 Git 자체가 어렵습니다. ㅎ) 하지만 이 말은 최초 입문서로 적당한가? 하는 의문일 뿐 Git을 좀 제대로 이해하려면 반드시 Pro Git을 보라고 추천하고 싶을 정도로 내용과 설명이 깔끔합니다.

4장에서는 Git 서버를 구성하는 방법이 나옵니다. 여기서 Git서버를 사용하는 방법과 Git서버 프로그램들이 GitosisGiolite등을 소개하고 있습니다. 5장에서는 DVCS인 Git으로 팀 개발을 할 때 어떤 Workflow를 적용해야 좋은지를 몇가지 방법을 비교하면서 설명해 주고 있습니다. 개인적으로는 이 두 챕터는 약간 흐럽을 끊지 않나 생각합니다. 사실 이 부분의 대한 고민은 상당히 Git을 사용해 본 뒤에 하게되거나 실제로 팀에 적용할 때 고민할 부분인데 일단 GitHub라는 걸죽한 무료 호스팅이 존재하기 때문에 보통은 기본 명령어후에 좀 더 고급의 사용을 고민하게 되지 않나 생각합니다.

이부분은 6장부터 나옵니다. 히스토리를 조회하고 커밋메시지를 관리하거나 rebase, stashing같은 약간은 고급(?) 명령어들을 다뤄주고 있습니다. 상황을 예시로 잘 들어주면서 설명해 주기 때문에 이해하기가 좋고 책을 참고하면서 실제로 활용할 수 있을 정도로 잘 설명되어 있습니다. 7장은 Git의 설정과 Git Hook을 다루는 방법을 설명하고 있는데 단순히 사용방법뿐만 아니라 어떻게 Hook을 작성해서 추가하는지 자세히 보여주고 있습니다. 이런 부분들은 다른 곳에서는 많이 안다뤄주기 때문에 특히 유용합니다. 8장에서는 기존의 Subversion을 사용하고 있다면 git-svn으로 어떻게 혼합해서 사용할 수 있는지와 아니면 Subversion등의 VCS를 Git으로 변환하는 과정을 보여주고 있씁니다. 8장은 사실 git-svn말고는 많이 쓰진 않을 것 같지만 팀에 도입하려면 한번정도는 겪을만한 상황이라고 생각하고 있습니다.

개인적으로 가장 좋았던 장은 9장입니다. 이 장에서는 Git의 내부 구조를 다루고 있습니다. Pro git에서 언급하는 용어대로라면 앞에서 계속 배웠던 명령어들은 Porcelain명령어로 사용자가 쉽게 쓸 수 있도록 제공되는 언어이고 내부적으로는 저수준인 Plumbing를 사용하고 있습니다. 9장에서는 Git을 사용하면서 clone하고 커밋하고 푸쉬할 때 Plumbing명령어나 직접 코드를 작성해서 생성해 보면서 Git이 실제로 내부에서는 어떻게 동작하는지를 보여주고 있습니다. 어려운 내용이라 한번 보고 죽~ 이해하기는 어렵겠지만 정말 다른데서는 거의 설명되지 않은 부분이기 때문에 정말 유용한 부분이라고 생각하고 9장을 이해하면 Git의 활용폭도 크게 늘어날 것으로 보입니다.

저는 Git을 꽤 좋아하는 편인데(잘 쓰진 못하더라도) 작년까지는 어디서나 다 git만 썼으면 좋겠다고 생각했습니다. 그만큼 Git이 좋았고 Git을 쓰면서 Subversion이 얼마나 구린 VCS인지를 깨닫게 되었습니다. 하지만 올해는 좀 생각이 달라졌는데 Git을 제대로 쓰려면 공부를 상당히 많이 해야 되고 저야 좋아하니까 이런 투자를 할 가치를 느끼지만 Git을 팀에 도입하자고 적극적으로 나서면서 앞으로 Git을 공부하세요! 라고 다른 사람에게 강요(?)하는 건 어렵겠다라고 생각하고 있습니다.(아마 팀에 상당수가 Git에 대한 호감이 있지 않다면 도입은 아주 어려운 일일것입니다. 대부분의 새로운 것의 도입이 그렇긴 하지만요.) 그럼에도 현재 대부분의 오픈소스가 Git으로 이동하고 있기 때문에 Git을 능수능란하게 사용하지는 못하더라도 기본적인 사용은 꼭 알아두어야 할 필요가 있다고 봅니다.

아무튼 Git을 좀 더 이해하고 싶고 잘 쓰고 있지만 Pro Git은 꼭 읽어보시라고 추천하고 싶습니다. 이런 양질의 자료를 무료로 번역해서 공개해 주신 pismute님과 lethee님께 정말 감사드립니다. 앞으로 Git 사용할 때 완소 자료가 되겠네요 ㅎ


My Comment..
여기서 논하는 Git 자체를 알지는 못한다.. 음.. 알지 못한다라기보단 우찌됬건 햄 블로그를 통해서 Git 의 쓰임새를 대충은 봤으니 아예 모르는건 아니고, 내가 사용하지를 않으니 아무래도 관심도에서는 순위권 밖이라고 할 수 있다는 정도이다.. 그런데 혹시라도 언젠가 Git 을 사용하는 회사 또는 프로젝트를 가면, 해당 글이 좀 유용하게 쓰일듯해서 가져왔다.. 근데 웃기게도 해당글은 가져오면서 Git, 분산 버전 관리 시스템 책은 왜 안갖고 왔나 모르것넹.. 아마도 그 때는 Git 은 나랑 상관없는 딴 나라 세상 얘기라고 넘겨버렸나보다.. ㅎㅎ

[JAVA]4장 IoC 컨테이너 #7..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.

4.6 빈의 특성 커스터마이징하기
4.6.1 라이프사이클 콜백
컨테이너의 빈 라이프사이클 관리와 상호작용하기 위해 스프링의 InitializingBean과 DisposableBean을 구현할 수 있다. 빈의 생성과 소멸에서 어떤 행동을 하도록 컨테이너는 InitializingBean에 대해서 afterPropertiesSet()를 호출하고 DisposableBean에 대해서 destroy()를 호출한다. 초기화 메서드와 소멸 메서드의 객체 정의 메타데이터를 사용하면 클래스가 스프링 인터페이스와 커플링을 갖지 않으면서 이와 동일한 컨테이어와의 통합을 이룰 수 있다.

내부적으로 스프링 프레임워크는 콜백 인터페이스가 적절한 메서드를 찾고 호출할 수 있도록 처리하기 위해 BeanPostProcessor 구현체를 사용한다. 어떤 특징들을 커스터마이징하거나 스프링이 제공하지 않는 라이프사이클 동작이 필요하다면 직접 BeanPostProcessor를 구현할 수 있다. 더 자세한 내용은 Section 4.8, “Container Extension Points”를 참고해라.

초기화 콜백과 소멸화 콜백에 추가적으로 컨테이너의 라이프사이클에 의해서 주도되듯이 스프링이 관리하는 객체들을 시작과 종료 과정에 포함할 수 있도록 Lifecycle 인터페이스를 구현할 수도 있다.

라이프사이클 콜백 인터페이스는 이 섹션에서 설명한다.

4.6.1.1 초기화 콜백
org.springframework.beans.factory.InitializingBean 인터페이스는 컨테이너가 빈의 모든 필수 프로퍼티를 설정한 후에 빈이 초기화를 하도록 한다. InitializingBean 인터페이스는 단일 메서드를 지정한다.



Java

void afterPropertiesSet() throws Exception;

코드가 불필요하게 스프링에 의존성을 가지게 되기 때문에 InitializingBean 인터페이스를 사용하는 것을 권하지 않는다. 대신에 POJO 초기화 메서드를 지정해라. XML 기반의 설정 메타데이터에서는 void에 아규먼트가 없는 시그니처를 가진 메서드의 이름을 지정하는데 init-method 속성을 사용해라. 예를 들어 다음과 같이 정의한다.



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Xml

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>


Java

public class ExampleBean {
  public void init() {
    // 어떤 초기화 동작을 수행한다
  }
}

이는 다음과 동일하다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Xml

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>

Java

public class AnotherExampleBean implements InitializingBean {
  public void afterPropertiesSet() {
    // 어떤 초기화 동작을 수행한다
  }
}

하지만 코드가 스프링에 의존성을 갖지 않는다.

4.6.1.2 소멸화 콜백
org.springframework.beans.factory.DisposableBean 인터페이스을 구현하면 빈을 담고 있는 컨테이너가 파괴될 때 빈이 콜백을 얻을 수 있도록 한다. DisposableBean 인터페이스는 단일 메서드를 지정한다.



Java

void destroy() throws Exception;

DisposableBean 콜백을 사용하면 코드가 스프링에 불필요한 의존성을 가지기 때문에 추천하지 않는다. 대신에 빈 정의에서 지원하는 제너릭 메서드를 지정해라. XML 기반의 설정 메타데이터를 사용하면 <bean/>의 destroy-method 속성을 사용해라. 예를 들면 다음과 같이 정의한다.



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Xml

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>

Java

public class ExampleBean {
  public void cleanup() {
    // 어떤 소멸화 작업을 수행한다 (풀에 있는 커넥션을 릴리즈하는 것같은)
  }
}

이는 다음과 같다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Xml

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>

Java

public class AnotherExampleBean implements DisposableBean {
  public void destroy() {
    // 어떤 소멸화 작업을 수행한다 (풀에 있는 커넥션을 릴리즈하는 것같은)
  }
}

하지만 코드가 스프링에 의존하지 않는다.

4.6.1.3 기본 초기화 메서드와 파괴 메서드
스프링에 의존적인 InitializingBean와 DisposableBean 콜백 인터페이스를 사용하지 않은 초기화와 파괴과 메서드 콜백을 작성할 때 보통 init(), initialize(), dispose() 등과 같은 메서드 이름을 사용한다. 원칙적으로 모든 개발자가 같은 이름을 사용하고 일관정을 보장하기 위해 이러한 라이프사이클 콜백 메서드 이름은 프로젝트를 넘어 표준화되어 있다.

모든 빈에서 이름있는 초기화 콜백 메서드와 파괴 콜백 메서드를 기다리도록(look) 스프링 컨테이너를 설정할 수 있다. 이 말은 어플리케이션 개발자가 빈 정의마다 init-method="init" 속성을 설정할 필요없이 어플리케이션 클래스들을 작성하고 init()라는 초기화 콜백을 사용할 수 있다는 의미이다. 빈이 생성될 때 스프링 IoC 컨테이너는 이 메서드를 호출한다.(그리고 앞에서 설명한 표준 라이프사이클 콜백 계약에 따라서) 이 기능은 초기화와 파괴 메서드 콜백에 일관성있는 작명 관례를 강제하는 역할도 한다.

초기화 콜백 메서드가 init()이고 파괴 콜백 메서드가 destroy()라고 해보자. 클래스는 다음 예제의 클래스와 비슷할 것이다.



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Java

public class DefaultBlogService implements BlogService {

  private BlogDao blogDao;

  public void setBlogDao(BlogDao blogDao) {
    this.blogDao = blogDao;
  }

  // (놀랍지는 않지만) 이는 초기화 콜백 함수다
  public void init() {
    if (this.blogDao == null) {
      throw new IllegalStateException("The [blogDao] property must be set.");
    }
  }
}

Xml

<beans default-init-method="init">
  <bean id="blogService" class="com.foo.DefaultBlogService">
     <property name="blogDao" ref="blogDao" />
  </bean>
</beans>

최상위 <beans/> 엘리먼스 속성에 default-init-method 속성의 존재는 스프링 IoC 컨테이너가 빈의 init라는 메서드를 초기화 메서드 콜백으로 인식하도록 한다. 빈이 생성되고 구성되었을 때 빈 클래스가 이러한 메서드를 가지고 있다면 적절한 시기에 호출된다.
파괴 메서드 콜백도 비슷하게 최상위 <beans/> 요소에 default-destroy-method 속성을 사용함으로써 설정한다.(XML에서)

존재하는 빈 클래스가 관례와 다른 이름의 콜백 메서드를 이미 가지고 있다면 <bean/> 자체에 init-method and destroy-method 속성을 사용하는 메서드를 지정함으로써(XML에서) 기본값을 오버라이드 할 수 있다.

스프링 컨테이너는 설정된 초기화 콜백이 빈이 모든 의존성을 제공받을 후에 즉시 호출된다는 것을 보장한다. 그래서 초기화 콜백은 로우(raw) 빈 참조에서 호출된다. 로우(raw) 빈 참조는 AOP 인터셉터 등이 아직 빈에 적용되지 않았다는 의미이다. 타겟 빈이 우선적으로 완전히 생성되고 그 다음 AOP 프록시(예를 들면)가 인터셉터 체인과 함께 적용된다. 타겟빈과 프록시가 따로 정의되어 있다면 코드는 프록시를 우회해서 로우(raw) 타겟 빈과 상호작용할 수 있다. 이는 프록시/인터셉터를 가진 타겟빈의 라이프사이클과 결합될 수 있고 코드가 직접 로우(raw) 타겟 빈과 상호작용할 때 이상한 시맨틱을 남길 수 있기 때문에 init 메서드에 인터셉터들을 적용하는데 일관성이 없을 수 있다.

4.6.1.4 라이프사이클 메카니즘 조합하기
스프링 2.5처럼 빈의 라이프사이클 동작을 제어하는데 3가지 선택사항이 있다. InitializingBean과 DisposableBean 콜백 인터페이스. 커스텀 init()과 destroy() 메서드. @PostConstruct 와 @PreDestroy 어노테이션. 주어진 빈을 제어하는 이러한 메카니즘을 조합할 수 있다.



Note
하나의 빈에 여러가지 라이프사이클 메카니즘을 설정하고 각 메가니즘이 다른 메서드명으로 설정했다면 각각의 설정된 메서드는 다음 리스트의 순서로 실행된다. 하지만 이러한 라이프사이클 메카니즘 중같은 메서드명으로 설정한 것이 있다면(예를 들어 초기화 메서드는 init()) 이전 섹션에서 설명했듯이 그 메서드들은 동시에 실행된다.

같은 빈에 다른 초기화 메서드로 설정된 여러가지 라이프사이클 메카니즘들은 다음과 같이 호출된다.

  • @PostConstruct 어노테이션이 붙은 메서드
  • InitializingBean 콜백 인터페이스로 정의된 afterPropertiesSet()
  • 커스텀 설정된 init() 메서드
파괴 메서드도 같은 순서로 호출된다.
  • @PreDestroy 어노테이션이 붙은 메서드
  • DisposableBean 콜백 인터페이스로 정의된 destroy()
  • 커스텀 설정된 destroy() 메서드

4.6.1.5 시작(startup) 콜백과 종료(shutdown) 콜백
Lifecycle 인터페이스는 라이프사이클에 자신만의 요구사항이 있는 객체의 핵심 메서드를 정의한다. (예를 들어 어떤 백그라운드 프로세스의 시작과 종료)


1
2
3
4
5
6
7
Java

public interface Lifecycle {
  void start();
  void stop();
  boolean isRunning();
}

스프링이 관리하는 모든 객체는 이 인터페이스를 구현한다. 그 다음 ApplicationContext이 시작하고 종료될 때 컨텍스트에서 정의한 모든 라이프사이클 구현체에 단계적으로 이러한 메서드를 호출한다. 이는 LifecycleProcessor에 위임해서 수행한다.

1
2
3
4
5
6
Java

public interface LifecycleProcessor extends Lifecycle {
  void onRefresh();
  void onClose();
}

LifecycleProcessor는 Lifecycle의 확장이라는 것을 기억해야 한다. 컨텍스트를 갱신하고 닫는 행위를 하는 두가지 메서드를 추가했다.

시작과 종료의 호출 순서는 중요하다. 어떤 두 객체사이에 "의존하는" 관계가 있다면 의존하는 쪽이 의존성 다음에 시작될 것이고 의존성보다 먼저 종료될 것이다. 하지만 때때로 직접적인 의존성을 알지 못한다. 아마도 특정 타입의 객체가 다른 타입의 객체보다 먼저 시작되어야 한다는 것만 알 것이다. 이러한 경우에 SmartLifecycle 인터페이스는 다른 선택사항으로 부모 인터페이스 Phased에서 정의된 getPhase()를 정의한다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

public interface Phased {
  int getPhase();
}

public interface SmartLifecycle extends Lifecycle, Phased {
  boolean isAutoStartup();
  void stop(Runnable callback);
}

시작할 때는 가장 낮은 단계의 객체들이 먼저 시작되고 멈출 때는 반대 순서로 멈춘다. 그러므로 SmartLifecycle를 구현하고 Integer.MIN_VALUE를 리턴하는 getPhase()메서드가 있는 객체가 먼저 시작하고 나중에 멈출 것이다. 반대로 Integer.MAX_VALUE의 단계 값(phase value)은 객체가 나중에 시작되고 먼저 멈추어야 한다는 것을 가르킬 것이다.(실행하려는 다른 프로세스에 의존하고 있는 이유와 마찬가지로) 단계 값을 고려할 때 SmartLifecycle를 구현하지 않은 "보통"의 모든 Lifecycle 객체는 기본 단계가 0이 된다는 것도 알고 있어야 한다. 그러므로 단계 값이 음수이면 객체가 표준 컴포넌트전에 시작되어야 한다는 것 (표준 컴포넌트 다음에 종료된다.)을 가리키고 단계값이 양수라면 그 반대이다.

SmartLifecycle에 정의된 stop 메서드가 콜백을 받는 것을 볼 수 있듯이 모든 구현체는 구현체의 종료과정이 끝난후에 콜백의 run() 메서드를 반드시 호출해야 한다. 이는 LifecycleProcessor 인터페이스의 기본 구현체인 DefaultLifecycleProcessor가 콜백을 호출하기 위해 각 단계 내 객체그룹의 타임아웃 값까지 기다리기 때문에 필수적인 비동기 종료를 가능케한다. 단계마다 기본 타임아웃은 30초이다. 컨텍스트에서 "lifecycleProcessor"라는 이름의 빈을 정의함으로써 기본 라이프사이클 프로세서 인스턴스를 오버라이드할 수 있다. 타임아웃만 바꾸려면 다음과 같이 정의하는 것으로 충분할 것이다.


1
2
3
4
5
6
Xml

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
  <!-- timeout value in milliseconds -->
  <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

말했듯이 LifecycleProcessor 인터페이스는 컨텍스트를 갱신하고 닫기위한 콜백 메서드를 정의한다. 후자는 stop()을 명시적으로 호출하는 것으로 간단히 종료과정을 진행할 수 있지만 이는 컨텍스트가 닫힐 때 진행될 것이다. 반면에 'refresh' 콜백은 SmartLifecycle 빈의 또 다른 기능을 사용하게 한다. 컨텍스트가 갱신되었을 때(모든 객체가 인스턴스화되고 초기화된 다음에) 갱신 콜백이 호출될 것이고 이 때 기본 라이프사이클 프로세서가 각 SmartLifecycle 객체의 isAutoStartup() 메서드가 리턴하는 boolean 값을 확인할 것이다. 만약 이 값이 "true"이면 그 객체는 컨텍스트의 명시적인 호출이나 자신의 start() 메서드를 기다리는 대신에 바로 시작할 것이다. (컨텍스트 갱신과는 다르게 컨텍스트 시작은 표준 컨텍스트 구현체를 위해서 자동적으로 일어나지 않는다.) "의존하는" 관계뿐만 아니라 "단계" 값도 위에서 설명한 것과 같은 방법으로 시작순서를 결정할 것이다.

4.6.1.6 웹 어플리케이션이 아닌 경우에 스프링 IoC 컨테이너를 안정적으로(gracefully) 종료하기

Note
이 섹션은 웹 어플리케이션이 아닌 경우에만 적용된다. 스프링의 웹기반 ApplicationContext에는 관련된 웹 어플리이션이 종료될 때 스프링 IoC 컨테이너를 안정적으로(gracefully) 종료하는 코드가 이미 있다.

웹 어플리케이션이 아닌 환경에서 스프링 IoC 컨테이너를 사용한다면(예를 들어 리치클라이언트 데스크탑 환경이라든지) JVM에 종료 훅(shutdown hook)을 등록한다. 종료 훅을 등록함으로써 안정적인 종료를 보장하고 모든 리소스를 릴리즈하기 위해 싱글톤 빈의 적절한 파괴 메서드를 호출한다. 물론 여전히 이러한 파괴 콜백을 적절하게 설정하고 구현해야 한다.

AbstractApplicationContext 클래스에 선언된 registerShutdownHook() 메서드를 호출해서 종료 훅을 등록한다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Java

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {

  public static void main(final String[] args) throws Exception {
    AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(new String []{"beans.xml"});

    // 위의 컨텍스트에 종료 훅(shutdown hook)을 추가한다... 
    ctx.registerShutdownHook();

    // 어플리케이션은 여기서 실행한다...

    // main 메서드가 있고 훅(hook)은 어플리케이션을 종료보다 먼저 호출된다...
  }
}

4.6.2 ApplicationContextAware과 BeanNameAware
ApplicationContext이 org.springframework.context.ApplicationContextAware 인터페이스를 구현한 클래스를 생성할 때 클래스는 ApplicationContext에 대한 참조를 제공받는다.

1
2
3
4
5
Java

public interface ApplicationContextAware {
  void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

그러므로 빈이 빈을 생성한 ApplicationContext를 프로그래밍적으로 다룰 수 있다. 이는 ApplicationContext 인터페이스를 통하거나 추가적인 기능을 노출하는 ConfigurableApplicationContext같은 인터페이스의 알려진 서브클래스에 대한 참조를 캐스팅함으로써 이뤄진다. 프로그래밍적으로 다른 빈을 복구하는 것이 한가지 사용이 될 것이다. 종종 이 기능은 유용하지만 보통은 사용하지 말아야 한다. 이 기능을 사용하면 코드가 스프링에 의존하게 되고 빈의 프로퍼티로 협력객체를 제공하는 제어의 역전 스타일을 따르지 않기 때문이다. ApplicationContext의 다른 메서드들은 파일 리소스, 어플리케이션 이벤트의 퍼블리싱, MessageSource 접근을 제공한다. 이러한 추가적인 기능은 Section 4.14, “Additional Capabilities of the ApplicationContext”에서 설명한다.

스프링 2.5처럼 자동 연결(autowiring)은 ApplicationContext의 참조를 얻는 또다른 대안이다. "전통적인" constructor와 byType 자동연결 모드(Section 4.4.5, “협력객체의 자동연결(Autowiring)”에서 설명했다.)는 각각 생성자 아규먼트나 setter 메서드 파라미터에 대한 ApplicationContext 타입 의존성을 제공할 수 있다. 필드들과 여러 파라미터를 가진 메서드들을 자동연결을 포함해서 더 유연하게 사용하려면 새로운 어노테이션 기반의 자동연결 기능을 사용해라. 이 기능을 사용하면 필드나 생성자나 메서드에 @Autowired 어노테이션을 사용했을 때 기대하는 BeanFactory 타입인 필드나 생성자 아규먼트나 메서드 파라미터에 ApplicationFactory이 자동연결된다. 더 자세한 내용은 Section 4.9.2, “@Autowired”를 참고해라.

ApplicationContext가 org.springframework.beans.factory.BeanNameAware 인터페이스를 구현한 클래스를 생성할 때 이 클래스는 관련된 객체 정의에서 정의한 이름에 대한 참조를 제공받는다.


1
2
3
4
5
Java

public interface BeanNameAware {
  void setBeanName(string name) throws BeansException;
}

콜백은 보통의 빈 프로퍼티 그룹 후에 호출되지만 InitializingBean afterPropertiesSet나 커스텀 init-method같은 초기화 콜백보다는 먼저 호출된다.

4.6.3 그 밖의 Aware 인터페이스
앞에서 얘기한 ApplicationContextAware와 BeanNameAware 외에도 스프링은 빈이 필요로 하는 인프라적인 의존성을 빈이 컨테이너에 알릴 수 있도록하는 Aware 인터페이스들을 제공한다. 가장 중요한 Aware 인터페이스는 아래에 정리해 놨다. 일반적인 규칙에 따라 이름은 의존성의 타입을 잘 나타낸다.

Table 4.4. Aware interfaces

이름주입되는 의존성설명되어 있는 위치
ApplicationContextAware
ApplicationContext의 선언
Section 4.6.2, “ApplicationContextAware과 BeanNameAware"
ApplicationEventPublisherAware
제공된 ApplicationContext의 이벤트 퍼블리셔
Section 4.14, "Additional Capabilities of the ApplicationContext"
BeanClassLoaderAware
빈(bean) 클래스들을 로드하는 클래스 로더
Section 4.3.2, "빈의 인스턴스화"
BeanFactoryAware
BeanFactory의 선언
Section 4.6.2, "ApplicationContextAware과 BeanNameAware"
BeanNameAware
선언한 빈의 이름
Section 4.6.2, "ApplicationContextAware과 BeanNameAware"
BootstrapContextAware
컨테이너가 동작하는 리소스 아답터BootstrapContext. 보통 JCA 친화적인ApplicationContext에서만 사용할 수 있다.
Chapter 24, JCA CCI
LoadTimeWeaverAware
로딩시에 클래스 정의를 처리하려고 정의한 weaver
Section 8.8.4, "Load-time weaving with AspectJ in the Spring Framework"
MessageSourceAware
메시지(파라미터화와 국제화 지원을 포함해서)를 처리하려고 설정한 전략
Section 4.14, "Additional Capabilities of the ApplicationContext"
NotificationPublisherAware
스프링의 JMX 노티피케이션 퍼블리셔
Section 23.7, "Notifications"
PortletConfigAware
컨테이너가 동작하는 현재PortletConfig. 웹 친화적인 스프링의ApplicationContext에서만 유효하다.
Chapter 19, Portlet MVC Framework
PortletContextAware
컨테이너가 동작하는 현재PortletContext. 웹 친화적인 스프링의ApplicationContext에서만 유효하다.
Chapter 19, Portlet MVC Framework
ResourceLoaderAware
저레벨 리소스접근을 위해 설정한 로더
Chapter 5, Resources
ServletConfigAware
컨테이너가 동작하는 현재ServletConfig. 웹 친화적인 스프링의ApplicationContext에서만 유효하다.
Chapter 16, Web MVC framework
ServletContextAware
컨테이너가 동작하는 현재ServletContext. 웹 친화적인 스프링의ApplicationContext에서만 유효하다.
Chapter 16, Web MVC framework

이러한 인터페이스의 사용은 코드를 스프링 API의 의존하게 하고 제어의 역전 스타일을 따르지 않는다는 것을 다시 강조한다. 이처럼 컨테이너에 프로그래밍적으로 접근할 필요가 있는 인프라적인 빈에 사용하기를 권장한다.

4.7 빈 정의 상속
빈 정의는 초기화 메서드, 정적 팩토리 메서드명 등과 같은 컨테이너 특유의 정보, 생성자 아규먼트, 프로퍼티 값을 포함해서 많은 설정정보를 담고 있다. 자식 빈 정의는 부모 정의에서 설정 데이터를 상속받는다. 자식 빈은 값을 오버라이드할 수 있고 필요하다면 추가할 수도 있다. 부모 빈 정의와 자식 빈 정의를 사용하면 타이핑 양을 많이 줄일 수 있다. 사실상 이는 템플릿 형식이다.

ApplicationContext 인터페이스를 프로그래밍적으로 작업한다면 자식 빈 정의는 ChildBeanDefinition 클래스로 나타난다. 대부분의 사용자는 이러한 레벨로 작업하지 않고 대신에 ClassPathXmlApplicationContext같은 어떤 선언적인 빈정의로 설정한다. XML 기반의 설정 메타데이터를 사용한다면 parent 속성의 값으로 부모 빈을 지정해서 자식 빈 정의를 나타낸다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Xml

<bean id="inheritedTestBean" abstract="true"
    class="org.springframework.beans.TestBean">
  <property name="name" value="parent"/>
  <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
      class="org.springframework.beans.DerivedTestBean"
      parent="inheritedTestBean" init-method="initialize">

  <property name="name" value="override"/>
  <!-- age 프로퍼티  1 부모로부터 상속될 것이다. -->
</bean>

지정한 것이 아무것도 없다면 자식 빈 정의는 부모 정의에서 빈 클래스를 사용하고 자식 빈 정의는 이를 오버라이드할 수 있다. 오버라이드할 경우 자식 빈 클래스는 부모와 반드시 호환성이 있어야 한다. 즉 자식 빈 클래스는 부모의 프로퍼티 값을 받아야 한다.

자식 빈 정의는 부모로부터 생성자 아규먼트 값, 프로퍼티 값, 메서드 오버라이드를 상속받고 새로운 값을 추가할 수도 있다. 지정한 어떤 초기화 메서드나 파괴 메서드 static 팩토리 메서드 설정도 대응되는 부모의 설정을 오버라이드 할 것이다.

남은 설정은 항상 자식 정의에서 가져온다: 의존성, 자동연결 모드, 의존성 확인, 싱글톤, 범위, 지연 초기화.

앞의 예제는 abstract 송석을 사용해서 명시적으로 부모 빈 정의를 추상으로 표시했다. 부모 정의가 클래스를 지정하지 않았다면 다음과 같이 명시적으로 부모 빈 정의를 abstract로 표시해야한다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Xml

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
    parent="inheritedTestBeanWithoutClass" init-method="initialize">
  <property name="name" value="override"/>
  <!-- age는 부모  정의에서 1 값을 상속받을 것이다-->
</bean>

부모 빈은 미완성이기 때문에 그 자체로는 초기화 될 수 없고 abstract로 명시되어 있다. 이처럼 정의가 abstract이면 자식정의를 위해서 부모정의가 제공하는 빈 정의 템플릿으로서만 사용할 수 있다. 다른 빈의 ref 프로퍼티로 참조하거나 명시적으로 부모 빈의 id로 getBean()호출해서 abstract인 부모 빈 자체를 사용하려고 하면 오류가 발생한다. 유사하게 컨테이너 내부의 preInstantiateSingletons() 메서드는 추상으로 정의된 빈 정의를 무시한다.


Note
ApplicationContext는 기본적으로 모든 싱글톤을 미리 인스턴스화한다. 그래서 템플릿으로만 사용하려는 (부모)빈 정의가 있고 이 빈 정의가 클래스를 지정하고 있다면 모든 싱글톤이 미리 인스턴스화된다는 사실은 중요하다.(최소한 싱글톤 빈에 대해서는) 반드시 abstract 속성을 true로 설정했는지 확인해야 한다. 그렇지 않으면 어플리케이션 컨텍스트는 실제로 abstract 빈을 미리 인스턴스화하려고 시도할 것이다.

[JAVA]4장 IoC 컨테이너 #6..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.

4.5 빈(Bean) 범위
빈 정의를 생성할 때 빈 정의로 정의한 클래스의 실제 인스턴스를 생성하기 위해 레시피를 만든다. 클래스처럼 하나의 레시피에서 많은 객체 인스턴스를 생성할 수 있다는 것을 의미하기 때문에 빈 정의가 레시피라는 개념은 중요하다.

특정 빈 정의로 생성한 객체에 연결할 다양한 의존성과 설정값뿐만 아니라 생성된 객체의 범위도 제어할 수 있다. 이 접근방법은 자바 클래스 레벨에서 객체의 범위를 생성하는 대신에 설정을 통해서 생성한 객체들의 범위를 선택할 수 있으므로 강력하고 유연하다. 빈을 여러 가지 범위 중 하나로 정의할 수 있다. 스프링 프레임워크는 다섯가지 범위를 지원한다. 이 중 3가지는 웹기반의 ApplicationContext에서만 사용할 수 있다.

다음과 같은 범위들이 준비되어 있다. 커스텀 범위를 만들 수도 있다.

Table 4.3. 빈의 범위

범위설명
singleton
(기본값) 하나의 빈정의가 스프링 IoC 컨테이너마다 하나의 객체 인스턴스가 되는 범위
prototype
하나의 빈 정의가 다수의 객체 인스턴스가 되는 범위
request
하나의 빈 정의가 하나의 HTTP 요청의 라이프사이클이 되는 범위; 이는 각 HTTP 요청은 빈 정의로 부터 생성된 자신만의 빈 인스턴스를 갖는다. 웹 친화적인 스프링ApplicationContext의 컨텍스트에서만 유효하다.
session
하나의 빈 정의가 하나의 HTTP Session의 라이프사이클이 되는 범위. 웹 친화적인 스프링  ApplicationContext의 컨텍스트에서만 유효하다.
global session
하나의 빈 정의가 전역적인 HTTP Session의 라이프사이클이 되는 범위. 보통 포틀릿(portlet) 컨텍스트내에서 사용할 때만 유효하다. 웹 친화적인 스프링ApplicationContext의 컨텍스트에서만 유효하다.


스레드의 범위를 갖는 빈
스프링 3.0에서는 스레드 범위를 사용할 수 있지만 기본적으로는 등록되어 있지 않다. 더 자세한 내용은 SimpleThreadScope를 봐라. 스레드 범위나 다른 커스텀 범위를 등록하는 방법을 알고 싶다면 Section 4.5.5.2, “커스텀 범위의 사용”를 봐라.

4.5.1 싱글톤 범위
싱글톤 빈의 유일하고 공유된 인스턴스로 관리되고 해당 빈으로 매칭되는 id의 빈의 모든 요청은 스프링 컨테이너가 같은 빈 인스턴스를 돌려준다.

간단히 말해서 빈 정의를 싱글톤 범위로 정의하면 스프링 IoC 컨테이너는 해당 빈 정의로 정의된 객체의 정확히 하나의 인스턴스를 생성한다. 이 하나의 인스턴스는 싱글톤 빈 같은 캐시로 저장하고 해당 빈에 대한 이후의 모든 요청이나 참조에 캐시된 객체를 돌려준다.

사용자 삽입 이미지

스프링의 싱글톤 빈의 개념은 Gang of Four (GoF)의 패턴북에 나온 싱글톤 패턴과는 다르다. GoF 싱글톤은 특정 클래스에 대해서 ClassLoader 마다 유일한 인스턴스가 생성되는 것처럼 객체의 범위를 하드코딩한다. 스프링 싱글톤의 범위는 컨테이너 마다, 빈 마다 라는 것이 가장 좋은 설명이다. 이 말은 하나의 스프링 컨테이너에서 특정 클래스의 빈을 정의하면 스프링 컨테이너는 빈 정의의 클래스에 대한 유일한 인스턴스를 생성한다. 싱글톤 범위는 스프링의 기본범위이다. XML에서 싱글톤으로 빈을 정의하려면 다음과 같이 작성한다.




1
2
3
4
5
6
Xml

<bean id="accountService" class="com.foo.DefaultAccountService"/>

<!-- 장황하긴 하지만 다음처럼 해도 똑같다. (싱글톤 범위는 기본값이다) -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>

4.5.2 프로토타입 범위
싱글톤이 아닌 프로토타입 범위의 빈 배포는 특정한 빈에 대한 요청마다 새로운 빈(bean) 인스턴스를 생성한다. 이는 빈이 다른 빈에 주입되거나 컨테이너에 getBean() 메서드를 호출해서 빈을 요청하는 것을 의미한다. 규칙에 따라 상태를 가진 모든 빈에는 프로토타입 범위를 사용하고 상태가 없는 빈에는 싱글톤 범위를 사용해라.

다음 다이어그램은 스프링의 프로토타입 범위를 나타낸다. 전형적인 DAO는 주고받는 어떤 상태도 가지지 않기 때문에 데이터 접근 객체 (DAO)는 보통 프로토타입으로 설정하지 않는다.

사용자 삽입 이미지

다음은 XML에서 빈을 프로토타입으로 정의하는 예제이다.




1
2
3
4
Xml

<!-- spring-beans-2.0.dtd의 사용 -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>

다름 범위와는 대조적으로 스프링은 프로토타입 빈의 완전한 라이프사이클을 관리하지 않는다. 컨테이너는 프로토타입 객체를 인스턴스화하고 설정하고 다른 방법으로 모은 다음에 프로토타입 인스턴스의 어떤 기록도 없이 클라이언트에 전달한다. 그러므로 초기화 라이프사이클 콜백 메서드가 범위와 관계없이 모든 객체에서 호출되었더라고 설정된 소멸 라이프사이클 콜백은 호출되지 않는다. 클라이언트 코드는 반드시 프로토타입 범위를 가진 객체를 정리해야 하고 프로토타입 빈이 잡고 있는 비싼 리소스들을 해제시켜야 한다. 스프링 컨테이너가 프로토타입 범위를 가진 빈이 붙잡고 있는 리소스를 해제하도록 하려면 정리되어야 하는 빈의 참조를 가진 커스텀 bean post-processor를 사용해 봐라.

어떤 관점에서는 프로토타입 범위를 가진 빈에 관한 스프링 컨테이너의 역할이 자바의 new 오퍼레이터 대신이다. 그 관점에서 과거의 모든 라이프사이클 관리는 클라이언트에서 처리해야 합니다. (스프링 컨테이너의 빈 라이프사이클에 대한 자세한 내용은 Section 4.6.1, “라이프사이클 콜백”를 봐라.)

4.5.3 프로토타입 빈에 의존성이 있는 싱글톤 빈
프로토타입 빈에 의존성이 있는 싱글톤 범위의 빈을 사용하면 인스턴스화 할 때 의존성이 처리된다는 것을 알아야 한다. 그러므로 싱글톤 범위의 빈에 프로토타입 범위의 빈을 의존성 주입하면 새로운 프로토타입 빈을 인스턴스화 한 후 싱글톤 빈에 의존성 주입한다. 이 프로토타입 인스턴스는 싱글톤 범위의 빈에 항상 제공되는 유일한 인스턴스이다.

하지만 싱글톤 범위의 빈이 런타임시에 계속해서 프로토타입 범위를 가진 빈의 새로운 인스턴스를 얻어야 하는 경우를 생각해 보자. 의존성 주입은 스프링 컨테이너가 싱글톤 빈을 인스턴스화하고 빈의 의존성을 처리하고 주입할 때만 딱 한번 일어나므로 싱글톤 빈에 프로토타입 범위의 빈을 의존성 주입할 수 없다. 런타임에서 프로토타입 빈의 새로운 인스턴스가 하나이상 필요하다면 Section 4.4.6, “메서드 주입”를 참고해라.

4.5.4 리퀘스트, 세션, 글로벌 세션 범위
request, session, global session 범위는 웹기반 스프링 ApplicationContext 구현에서만 사용할 수 있다. 이러한 범위들을 ClassPathXmlApplicationContext같은 보통의 스프링 IoC 컨테이너와 함께 사용한다면 모르는 빈 범위라는 의미로 IllegalStateException이 발생한다.

4.5.4.1 초기 웹 설정
request, session, global session 레벨(웹 범위의 빈)에서 빈의 범위를 지원하려면 빈을 정의하기 전에 몇가지 마이너한 초기 설정을 해야한다. (이 초기 설정은 표준 범위인 싱글톤과 프로토타입 범위에서는 필요하지 않다.)

특정한 서블릿 환경에 기반해서 이 초기 설정을 어떻게 해야하는가..

스프링 웹 MVC 안에서(사실상은 요청안에서) 범위를 가진 빈에 접근하면 스프링 DispatcherServlet나 DispatcherPortlet이 처리하고 특별한 설정은 필요하지 않다. DispatcherServlet와 DispatcherPortlet는 이미 모든 연관된 상태를 드러낸다.

스프링의 DispatcherServlet 밖에서 처리되는 요청이 있는 서블릿 2.4이상의 웹 컨테이너를 사용한다면(예를 들어 JSF나 스트럿츠(Struts)를 사용하는 경우) 웹 어플리케이션의 web.xml 파일에 다음 javax.servlet.ServletRequestListener 선언을 추가해야 한다.




 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Xml

<web-app>
...
<listener>
  <listener-class>
      org.springframework.web.context.request.RequestContextListener
  </listener-class>
</listener>
...
</web-app>

더 오래된 웹 컨테이너 (Servlet 2.3)를 사용한다면 제공되는 javax.servlet.Filter 구형체를 사용해라. 서블릿 2.3 컨테이너에서 스프링의 DispatcherServlet 밖의 요청에서 웹 범위를 가진 빈에 접근하려면 웹 어플리케이션의 web.xml 파일에 다음의 XML 설정을 포함시켜야 한다.(필터는 웹 어플리케이션 환경에 기반에서 매핑되므로 웹 어플리케이션 환경을 적절하게 수정해야 한다.)


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Xml

<web-app>
..
<filter>
  <filter-name>requestContextFilter</filter-name>
  <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>requestContextFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>

DispatcherServlet, RequestContextListener, RequestContextFilter는 모두 완전히 같은 일을 한다. 즉, HTTP 요청 객체를 해당 요청을 서비스하는 Thread에 바인딩한다. 이는 요청이나 세션범위의 빈을 콜 체인(call chain)에서 이용가능하도록 한다.

4.5.4.2 요청 범위(Request scope)
다음 빈 정의를 보자.




1
2
3
Xml

<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>

스프링 컨테이너는 모든 HTTP 요청에 대해서 loginAction 빈 정의를 사용해서 LoginAction 빈의 새로운 인스턴스를 생성한다. loginAction빈은 HTTP 요청 레벨의 범위가 된다. 같은 loginAction 빈 정의로 생성된 다른 인스턴스들은 이러한 상태변화를 볼 수 없기 때문에 생성된 인스턴스의 내부 상태를 원하는 대로 수정할 수 있다. 인스턴스들은 각 요청에 개별적이다. 요청의 처리가 완료되었을 때 요청 범위의 빈은 버려진다.

4.5.4.3 세션 범위(Session scope)
다음 빈 정의를 보자.




1
2
3
Xml

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

스프링 컨테이너는 단일 HTTP Session의 생명주기에 대해서 userPreferences 빈 정의를 사용해서 UserPreferences 빈의 새로운 인스턴스를 생성한다. 다시 말하면 userPreferences 빈은 HTTP Session 레벨에서 유효한 범위가 된다. 요청 범위의 빈처럼 생성된 인스턴스의 내부 상태를 원하는 만큼 바꿀 수 있다. 같은 userPreferences 빈 정의에서 생성된 인스턴스를 사용하는 다른 HTTP Session 인스턴스는 개별적인 HTTP Session을 갖기 때문에 이러한 상태변화를 볼 수 없다. HTTP Session이 버려졌을 때 특정 HTTP Session의 범위인 빈도 역시 버려진다.

4.5.4.4 전역 세션 변위(Global session scope)
다음 빈 정의를 보자.




1
2
3
Xml

<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>

전역 세션 범위는 표준 HTTP Session 범위(위에서 설명했다)와 유사하고 포틀릿 기반의 웹 어플리케이션의 컨텍스트에서만 적용된다. 포틀릿 명세는 Session 세션의 개념을 단일 포틀릿 웹 어플리케이션의 모든 포틀릿 가운데 공유된다고 정의한다. 전역 세션 범위로 정의된 빈은 전역 포틀릿의 생명주기의 범위(또는 바인드)가 된다.

표준 서블릿 기반의 웹 어플리케이션을 작성하고 전역 세션 범위를 가지는 하나 이상의 빈을 정의하면 표준 HTTP Session 범위가 사용되고 에러가 발생하지 않는다.

4.5.4.5 의존성에서 범위를 가진 빈
스프링 IoC 컨테이너는 객체들(빈)의 인스턴스화 뿐만 아니라 협력 객체들(또는 의존성)들의 연결까지 관리한다. (예를 들어) HTTP 요청 범위의 빈은 다른 빈으로 주입하려고 하면 HTTP 요청 범위 빈의 위치에 AOP 프록시를 주입해야 한다. 이것은 같은 퍼블릭 인터페이스를 HTTP 요청 범위의 빈으로 노출하는 프록시 객체를 주입해야할 필요가 있다. 하지만 관계된 범위에서(예를 들면 HTTP 요청) 실제 타겟 객체를 획득하고 실제 객체에서 위임 메서드를 호출할 수 있다.



Note
싱글톤이나 프로토타입같은 범위를 가진 빈과의 결합을 위해서 <aop:scoped-proxy/>를 사용할 필요가 없다. 싱글톤 빈에 대한 범위를 가진 프록시를 생성하려고 시도하면 BeanCreationException이 발생한다.

다음 예제의 설정은 한 라인뿐이지만 내부에서 “어떻게” 동작하고 “왜” 그렇게 동작하는지 이해하는게 중요하다.




 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:aop="http://www.springframework.org/schema/aop"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

  <!-- HTTP Session범위의 빈은 프록시로 노출된다 -->
  <bean id="userPreferences" class="com.foo.UserPreferences" scope="session">

        <!-- 주변의 빈을 프록시하도록 컨테이너에게 지시한다 -->
        <aop:scoped-proxy/>
  </bean>

  <!-- 싱글톤 범위의 빈이 위의 빈에 대한 프록시에 주입된다 -->
  <bean id="userService" class="com.foo.SimpleUserService">

      <!-- 프롯시된 userPreferences 빈에 대한 참조 -->
      <property name="userPreferences" ref="userPreferences"/>

  </bean>
</beans>

이러한 프록시를 생성하려면 범위를 가진 빈 정의의 자식요소로 <aop:scoped-proxy/> 요소를 추가해야 한다. (클래스 기반의 프록싱을 선택하다면 클래스 패스에 CGLIB 라이브러리도 추가해야 한다. the section called “생성할 프록시의 타입 선택”와 Appendix C, XML Schema-based configuration를 참고해라.) request, session, globalSession, 커스텀 레벨의 범위를 가진 빈 정의는 왜 <aop:scoped-proxy/> 요소가 필요한가? 다음 싱글톤 빈 정의를 시도해보고 앞에서 언급한 범위에 대해서 무엇을 정의해야 하는가와 비교해 봐라. (다음 userPreferences 빈 정의로는 불완전하다.)


1
2
3
4
5
6
7
Xml

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

<bean id="userManager" class="com.foo.UserManager">
  <property name="userPreferences" ref="userPreferences"/>
</bean>

앞의 예제에서 싱글톤 빈 userManager는 HTTP Session 범위의 빈 userPreferences에 대한 참조와 함께 주입된다. 여기서 두드러진 점은 userManager 빈이 싱글톤 이라는 것이다. 이는 컨테이너 당 딱 한번만 인스턴스화 되고 그 의존성(이 경우에서는 유일한 userPreferences 빈)도 역시 한번만 주입된다. 이는 userManager 빈은 최초에 함께 주입된 완전히 같은 userPreferences 객체상에서만 수행된다는 것을 의미한다.

생명이 긴 범위의 빈에 생명이 짧은 범위의 빈을 주입할 때 이 동작은 원하는 동작이 아니다. 예를 들어 HTTP Session 범위의 협력 빈을 의존성으로 싱글톤 빈에 주입하는 경우이다. 더 정확히 얘기하자면 HTTP Session 의 생명주기인 단일 userManager 객체가 필요하고 앞에서 얘기한 HTTP Session을 가리키는 userPreferences 객체가 필요하다. 그러므로 컨테이너는 스코프 메카니즘(HTTP 요청, Session 등등)에서 실제 UserPreferences 객체를 가져올 수 있는 UserPreferences 클래스처럼 완전히 같은 퍼블릭 인터페이스를 노출하는 객체를 생성한다. (원칙적으로 객체는 UserPreferences 인스턴스 이다.) 컨테이너는 이 프록시 객체를 userManager에 주입하고 userManager는 이 UserPreferences 참조가 프록시라는 것을 모르고 있다. 이 예제에서 UserManager 인스턴스는 의존성 주입된 UserPreferences 객체의 메서드를 호출했을 때 사실상 프록시의 메서드를 호출한다. 그 다음 프록시는 (이 경우에는) HTTP Session에서 실제 UserPreferences 객체를 가져오고 메서드 호출을 획득한 실제 UserPreferences 객체에 위임한다.

그러므로 요청, 세션, 전역세션 범위의 빈을 협력객체에 주입하려면 다음의 제대로된 완전한 설정이 필요하다.




1
2
3
4
5
6
7
8
9
Xml

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
  <aop:scoped-proxy/>
</bean>

<bean id="userManager" class="com.foo.UserManager">
  <property name="userPreferences" ref="userPreferences"/>
</bean>

생성할 프록시의 타입 선택
기본적으로 스프링 컨테이너가 <aop:scoped-proxy/> 요소로 표시된 빈에 대한 프록시를 생성할 때 CGLIB 기반 클래스 프록시를 생성한다. 이 말은 어플리케이션 클래스 패스에 CGLIB 라이브러리가 필요하다는 의미이다.

Note: CGLIB 프록시는 퍼블릭 메서드 호출만 가로챈다! 이러한 프록시에서 퍼블릭이 아닌 메서드를 호출하지 마라. 퍼블릭이 아닌 메서드는 범위를 가진 타겟 객체로 위임되지 않을 것이다.

대신 이러한 범위의 빈에 대해서 <aop:scoped-proxy/> 요소의 proxy-target-class 속성의 값을 false로 지정함으로써 스프링 컨테이너가 표준 JDK 인터페이스 기반의 프록시를 생성하도록 설정할 수 있다. JDK 인터페이스 기반 프록시를 사용한다는 것은 이러한 프록시가 유효하도록 하기 위해 어플리케이션 클래스패스에 추가적인 라이브러리가 필요없다는 것을 의미한다. 하지만 이러한 범위를 가진 빈의 클래스가 반드시 최소한 하나의 인터페이스를 구현해야 한다는 것과 이러한 범위를 가진 빈으로 주입된 모든 협력객체들은 반드시 인터페이스 중 하나를 통해서 빈을 참조해야 한다는 것을 의미한다.




 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Xml

<!-- DefaultUserPreferences는 UserPreferences 인터페이스를 구현한다 -->
<bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session">
  <aop:scoped-proxy proxy-target-class="false"/>
</bean>

<bean id="userManager" class="com.foo.UserManager">
  <property name="userPreferences" ref="userPreferences"/>
</bean>

클래스 기반이나 인터페이스 기반의 프록시 선택에 대한 더 자세한 내용은 Section 8.6, “Proxying mechanisms”를 참조해라.

4.5.5 커스텀 범위
스프링 2.0처럼 빈 범위의 메카니즘은 확장할 수 있다. 자신만의 범위를 정의하고나 이미 존재하는 범위를 재정의 할 수도 있다. 하지만 기존의 범위를 재정의하는 것은 별로 권장하지 않으며 내장된 singleton과 prototype 범위는 오버라이드 할 수 없다.

4.5.5.1 커스텀 범위 생성
스프링 컨테이너와 커스텀 범위를 통합하려면 이번 섹션에서 설명할 org.springframework.beans.factory.config.Scope 인터페이스를 구현해야 한다. 어떻게 자신의 범위를 구현하는가에 대한 개념을 알고 싶다면 스프링 프레임워크가 제공하는 Scope 구현을 참고하고 구현해야 하는 메서드에 대해서 자세히 설명해 놓은 Scope Javadoc을 참고해라.

Scope 인터페이스는 범위에서 객체를 얻고, 범위에서 객체를 제거하고 소멸되도록 하는 4가지 함수가 있다.

다음 메서드는 의존하는 범위에서 객체를 리턴한다. 예를 들어 세션 범위 구현은 세션 범위의 빈을 리턴한다. (그리고 존재하지 않는다면 메서드는 차후 참조를 위해서 빈을 바인딩한 후 새로운 인스턴스를 리턴한다.)



Java
Object get(String name, ObjectFactory objectFactory)

다음 메서드는 의존하는 범위에서 객체를 제거한다. 예를 들어 세션 범위 구현은 의존하는 세션에서 세션 범위의 빈을 제거한다. 객체는 리턴되어야 하지만 지정한 이름의 빈이 없다면 null을 리턴할 수 있다.



Java
Object remove(String name)

다음 메서드는 범위가 소멸되거나 범위내에서 지정한 객체가 소멸될 때 실행되어야 하는 콜백을 등록한다. 소멸 콜백에 대한 더 자세한 내용은 Javadoc을 참조하거나 스프링 범위 구현체를 참조해라.



Java
void registerDestructionCallback(String name, Runnable destructionCallback)

다음 메서드는 의존하는 범위에 대한 conversation 식별자를 획득한다. 이 식별자는 각 범위마다 달르다. 세션 범위의 구형체에서 이 식별자는 세션 식별자가 될 수 있다.


Java
String getConversationId()

4.5.5.2 커스텀 범위의 사용

하나 이상의 커스텀 Scope 구현체를 작성하고 테스트한 후 스프링 컨테이너가 작성한 새로운 범위를 익식하도록 해야한다. 다음 메서드는 새로운 Scope를 스프링 컨테이너에 등록하는 핵심 메서드이다.



Java
void registerScope(String scopeName, Scope scope);

이 메서드는 ConfigurableBeanFactory 인터페이스에 선언되어 있다. ConfigurableBeanFactory 인터페이스는 BeanFactory 프로퍼티로 스프링과 함께 제공되는 대부분의 ApplicationContext 구현체에서 사용할 수 있다.

registerScope(..)의 첫번째 아규먼트는 범위와 연관되는 유일한 이름이다. singleton나 prototype이 스프링내에서 사용하는 이러한 이름의 예이다. registerScope(..) 메서드의 두번재 아규먼트는 등록하고 사용할 커스텀 Scope 구현체의 실제 인스턴스다.

자신만의 커스텀 Scope 구현체를 작성한다면 다음과 같이 등록한다.



Note
다음 예제는 스프링에 포함된 SimpleThreadScope를 사용하지만 기본적으로 등록되지는 않는다. 이 설명은 자신만의 커스텀 Scope 구현체에서도 똑같이 적용될 것이다.


Java
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);

그 다음 커스텀 Scope의 범위 규칙을 따르는 빈 정의를 생성한다.


Xml
<bean id="..." class="..." scope="thread">

커스텀 Scope 구현체를 사용할 때 범위를 프로그래밍적으로 등록하는데 제한은 없다. 또한 CustomScopeConfigurer 클래스를 사용해서 선언적으로 Scope를 등록할 수도 있다.




 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:aop="http://www.springframework.org/schema/aop"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

  <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
      <property name="scopes">
          <map>
              <entry key="thread">
                  <bean class="org.springframework.context.support.SimpleThreadScope"/>
              </entry>
          </map>
      </property>
  </bean>

  <bean id="bar" class="x.y.Bar" scope="thread">
      <property name="name" value="Rick"/>
      <aop:scoped-proxy/>
  </bean>

  <bean id="foo" class="x.y.Foo">
      <property name="bar" ref="bar"/>
  </bean>

</beans>



Note
FactoryBean구현체에 <aop:scoped-proxy/>가 있으면 이는 getObject()에서 리턴받은 객체가 아닌 스스로 범위를 가진 팩토리 빈이다.