[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월 19일 화요일

[Tool]jmap으로 자바의 메모리맵 확인하기..

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

jmap은 자바 어플리케이션의 메모리 맵을 확인할 수 있는 도구입니다. JDK 설치시에 포함되어 있는 걸로 알고 있었는데 jmap 문서를 보면 차후의 JDK에서는 포함되지 않을 수 있고 윈도우에서는 별도의 설치과정을 거쳐야 한다고 나와 있습니다.(요즘 개발할때는 윈도우를 거의 안써서 잘 모르겠군요.) 저는 최근에 톰캣어플리케이션에서 jmap으로 메모리 사용량을 확인하는 용도로 사용했습니다.(Heap dump도 뜰 수 있는 것으로 알고 있습니다.)

먼저 확인할 자바어플리케이션의 프로세스 ID를 알아야 하므로 jps나 ps 명령어를 사용해서 프로세스 ID를 알아냅니다.

jmap -heap 프로세스ID

위 처럼 입력하면 해당 프로세스의 메모리맵을 통해서 Heap 메모리의 각 영역별 항당관 메모리 크기와 사용량 등을  다음과 같이 확인할 수 있습니다.

사용자 삽입 이미지

My Comment..
이 글은 진짜 그냥 흥미성이다.. 내가 관심을 갖거나 위에 언급한 메모리 맵을 봐야되겠다라고는 생각을 전혀 안했다.. 그냥 이런 것이 있구나 싶은 생각에서 가져왔다.. 햄은 하나부터 열까지 세세한 부분에도 관심이 많은 듯.. 


[Tool]Eclipse의 리소스 검색 대상에서 제외시키기..

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

Eclipse에서 Open Resource(Ctrl + Shift + R)은 코딩 중에 쉽게 여러 파일을 왔다갔다 하는 유용한 기능중 하나입니다. 큰 프로젝트 일수록 프로젝트내에 많은 리소스들이 생성되는데 많은 파일 중 이름이 겹치는 파일이 있거나 하는 경우 리소스를 찾을 때 후보군이 많이 나와서 귀찮게 됩니다. 저 같은 경우는 메이븐의 멀티모듈 프로젝트를 사용하는데 최상위 프로젝트를 내려받고 여기서 서브 프로젝트를 생성했기 때문에 결과적으로는 전체 프로젝트에서 같은 파일이 최상위 프로젝트와 실제 프로젝트 두 곳에 포함되었습니다. 이는 리소스를 찾을 때마다 2개씩 나와서 항상 불편했었는데 이 리소스 대상에서 제외시킬 수 있습니다.

Properties창의 Derived 속성 설정화면

프로젝트의 폴더나 패키지등에서 우측클릭을 하고 Properties 메뉴에 들어가면 Resource부분에 Derived라는 속성을 볼 수 있습니다. 이 속성을 체크하면 해당 리소스는 파생된 리소스로 인식해서 Open Resource의 대상에서 제외되게 됩니다.



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

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

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

4.13 LoadTimeWeaver 등록하기
스프링 2.5에서 도입된 context 네임스페이스는 load-time-weaver 요소를 제공한다.


1
2
3
4
5
Xml

<beans>
    <context:load-time-weaver/>
</beans>

XML기반 스프링 설정파일에 이 요소를 추가하면 ApplicationContext에 대한 스프링 LoadTimeWeaver가 활성화된다. 해당 ApplicationContext내의 어떤 빈도 LoadTimeWeaverAware를 구현할 수 있기 때문에 로드타임 위버(load-time weaver) 인스턴스에 대한 참조를 받는다. 이는 JPA 클래스 변환때문에 로드타임 위빙이 필요한 스프링의 JPA 지원을 함께 사용할 때 특히 유용하다. 더 자세한 내용은 LocalContainerEntityManagerFactoryBean Javadoc에 나와있다. AspectJ 로드타임 위빙에 대해서 더 알고 싶다면 Section 8.8.4, “Load-time weaving with AspectJ in the Spring Framework”를 봐라.

4.14 ApplicationContext의 추가적인 기능들
이 챕터의 도입부에서 얘기했듯이 org.springframework.beans.factory 패키지는 프로그래밍적인 방법을 포함해서 빈을 관리하고 조작하는 기본적인 기능을 제공한다. org.springframework.context 패키지는 추가적으로 더 어플리케이션 프레임워크 지향적인 스타일로 추가적인 기능을 제공하려고 다른 인터페이스를 확장하는 BeanFactory 인터페이스를 확장한 ApplicationContext를 추가한다. 완전히 선언적인 방법으로 ApplicationContext를 사용하는 많은 사람들은 프로그래밍으로는 생성조차 하지 않고 대신에 J2EE 웹 어플리케이션의 일반적인 시작과정의 일부로 ApplicationContext를 자동으로 인스턴스화 하려고 ContextLoader같은 지원 클래스에 의존한다.

좀 더 프레임워크 지향적인 스타일로 BeanFactory의 기능을 강화하기 위해 context 패키지는 다음 기능도 제공한다.

  • MessageSource 인터페이스로 i18n 스타일의 메세지 접근
  • ResourceLoader 인터페이스로 URL이나 파일같은 리소스 접근
  • ApplicationEventPublisher 인터페이스의 사용으로 ApplicationListener 인터페이스를 구현한 빈에 이벤트 발행(Event publication)
  • HierarchicalBeanFactory 인터페이스로 어플리케이션의 웹 계층같은 특정 계층에 각각 집중하는 여러 가지 (계층적인) 컨텍스트의 로딩

4.14.1 MessageSource를 사용한 국제화
ApplicationContext 인터페이스는 MessageSource라는 인터페이스를 확장하므로 국제화 (i18n) 기능을 제공한다. 스프링은 메시지를 계층적으로 처리할 수 있는 HierarchicalMessageSource 인터페이스도 제공한다. 이 인터페이스들은 스프링이 메세지를 처리한 결과에 기반한 기초를 제공한다. 이 인터페이스들에서 정의된 메서드들은 다음을 포함한다.

  • String getMessage(String code, Object[] args, String default, Locale loc): MessageSource에서 메시지를 획득하기 위해 사용하는 기본 메서드. 지정한 로케일(locale)에 대한 메시지를 찾지 못하면 기본 메시지를 사용한다. 전달한 아규먼트는 표준 라이브러리에서 제공하는 MessageFormat 기능을 사용해서 대체값이 된다.
  • String getMessage(String code, Object[] args, Locale loc): 본질적으로는 앞의 메서드와 같지만 한가지가 다르다: 기본 메시지를 지정할 수 없다. 메시지를 찾을 수 없다면 NoSuchMessageException를 던진다.
  • String getMessage(MessageSourceResolvable resolvable, Locale locale): 앞의 메서드에서 사용했던 모든 프로퍼티들은 이 메서드에서 사용할 수 있는 MessageSourceResolvable라는 클래스로도 감싸진다.
ApplicationContext가 로드되면 자동으로 컨텍스트에서 정의된 MessageSource 빈을 찾는다. 해당 빈을 찾으면 메시지 소스에 위임한 앞의 메서드들을 모두 호출한다. 메세지 소스가 없다면 ApplicationContext는 부모가 같은 이름의 빈을 가지고 있는지 찾아본다. 부모가 같은 이름의 빈을 가지고 있으면 그 빈을 MessageSource로 사용한다. ApplicationContext가 어떤 메시지 소스도 찾을 수 없다면 위에서 정의한 메서스에 대한 호출을 받을 수 있도록 비어있는 DelegatingMessageSource를 인스턴스화 한다.

스프링은 2개의 MessageSource 구현체를 제공하는데 ResourceBundleMessageSource와 StaticMessageSource이다. 둘다 중첩된 메세지를 처리하기 위해 HierarchicalMessageSource를 구현한다. StaticMessageSource는 드물게 사용하지만 프로그래밍적인 방법으로 소스에 메시지를 추가하는 방법을 제공한다. ResourceBundleMessageSource는 다음 예제에 나와있다.


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

<beans>
<bean id="messageSource"
     class="org.springframework.context.support.ResourceBundleMessageSource">
  <property name="basenames">
   <list>
     <value>format</value>
     <value>exceptions</value>
     <value>windows</value>
   </list>
  </property>
</bean>
</beans>

예제에서 클래스패스에 format, exceptions, windows라는 3개의 리소스 번들이 정의되어 있다고 가정한다. 메시지를 처리하는 어떤 요청이라도 리소스번들을 통해 메시지를 처리하는 JDK 표준방법으로 다룰 것이다. 예제의 의도를 위해 위의 리소스 번들 파일 중 2개의 내용이 다음과 같다고 가정한다.

1
2
3
4
5
6
7
8
9
C-like

# in format.properties
message=Alligators rock!

C-like

# in exceptions.properties
argument.required=The '{0}' argument is required.

MessageSource 기능을 실행하는 프로그램은 다음 예제에 나와있다. 모든 ApplicationContext 구현체는 MessageSource의 구현체이기고 하므로 MessageSource 인터페이스로 캐스팅 될 수 있다는 것을 기억해라.

1
2
3
4
5
6
7
Java

public static void main(String[] args) {
  MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
  String message = resources.getMessage("message", null, "Default", null);
  System.out.println(message);
}

위 프로그램은 다음과 같은 아웃풋이 될 것이다.

1
2
3
C-like

Alligators rock!

그래서 요약하자면 MessageSource는 클래스패스 루트경로에 있는 beans.xml 파일에 정의되어 있다. messageSource 빈 정의는 basenames 프로퍼티에서 다수의 리소스 번들을 참조한다. basenames 프로퍼티의 리스트로 전달한 3개의 파일은 클래스패스 루트경로에 있는 파일들이고 각각 format.properties, exceptions.properties, windows.properties라는 이름이 된다.

다음 예제는 메시지 검색을 위해 아규먼트를 전달하는 것을 보여준다. 이러한 아규먼트들을 문자열로 변환해서 검색 메시지의 플레이스홀더에 삽입할 것이다.


 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
Xml

<beans>
  <!--  MessageSource는  어플리케이션에서 익숙하다 -->
  <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
     <property name="basename" value="test-messages"/>
  </bean>

  <!-- 위의 MessageSource를  POJO에 주입한다 -->
  <bean id="example" class="com.foo.Example">
     <property name="messages" ref="messageSource"/>
  </bean>
</beans>

Java

public class Example {

  private MessageSource messages;

  public void setMessages(MessageSource messages) {
      this.messages = messages;
  }

  public void execute() {
      String message = this.messages.getMessage("argument.required",
          new Object [] {"userDao"}, "Required", null);
      System.out.println(message);
  }
}

execute()를 호출의 결과는 다음과 같은 아웃풋이 될 것이다.

1
2
3
C-like

The userDao argument is required.

국제화 (i18n)와 관련해서 스프링의 여러가지 MessageResource 구현체는 표준 JDK ResourceBundle과 같은 로케일(locale) 처리방법과 장애복구 규칙을 따른다. 간단히 말해서 앞에서 정의한 messageSource 예제에서 영국 (en-GB) 로케일로 메시지를 처리하려면 format_en_GB.properties, exceptions_en_GB.properties, windows_en_GB.properties파일을 각각 생성해야 한다.

보통 로케일 처리는 어플리케이션 환경에 의해 관리된다. 이 예제에서 (영국) 메시지가 처리되어야 하는 로케일은 수정으로 지정한다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
C-like

# exceptions_en_GB.properties 파일
argument.required=Ebagum lad, the '{0}' argument is required, I say, required.

Java

public static void main(final String[] args) {
  MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
  String message = resources.getMessage("argument.required",
      new Object [] {"userDao"}, "Required", Locale.UK);
  System.out.println(message);
}

위의 프로그램을 실행하면 다음과 같은 아웃풋이 될 것이다.

1
2
3
C-like

Ebagum lad, the 'userDao' argument is required, I say, required.

정의된 MessageSource에 대한 참조를 얻으려고 MessageSourceAware 인터페이스를 사용할 수도 있다. MessageSourceAware 인터페이스를 구현한 ApplicationContext에서 정의된 어떤 빈이라도 빈이 생성되고 설정될 때 어플리케이션 컨텍스트의 MessageSource와 함께 주입된다.


Note
ResourceBundleMessageSource에 대한 대안으로 스프링은 ReloadableResourceBundleMessageSource 클래스를 제공한다. 이 클래스는 같은 번들 파일 형식을 지원하지만 ResourceBundleMessageSource 구현체에 기반한 표준 JDK보다 더 유연하다. 특히 이 클래스는 어떤 스프링 리소스 위치(단지 클래스 패스가 아니라)에서도 파일을 읽을 수 있고 번들 프로퍼티파일의 핫 리로딩을 지원한다.(동시에 중간에서 이들을 캐싱한다.) 더 자세한 내용은 ReloadableResourceBundleMessageSource 자바독을 확인해 봐라.

4.14.2 표준 이벤트와 커스텀 이벤트
ApplicationContext의 이벤트 핸들링은 ApplicationEvent 클래스와 ApplicationListener 인터페이스를 통해서 제공한다. ApplicationListener인터페이스를 구현한 빈이 컨텍스트에 배포되면 ApplicationContext에 퍼블리싱되는 것을 ApplicationEvent가 얻을 때마다 빈에 통지한다. 본질적으로 이는 표준 Observer 디자인 패턴이다. 스프링은 다음과 같은 표준 이벤트를 제공한다.

Table 4.7. 내장 이벤트

이벤트설명
ContextRefreshedEventApplicationContext가 초기화되거나 갱신될 때 퍼블리싱된다. 예를 들어 ConfigurableApplicationContext 인터페이스의 refresh() 메서드를 사용하는 경우다. 여기서 "초기화"는 모든 빈이 로드되고 후처리자(post-processor) 빈을 탐지해서 활성화하고 싱글톤이 미리 인스턴스화되고 ApplicationContext 객체를 사용할 준비가 되었음을 의미한다. 컨텍스트가 닫히지 않는 한 여러 번 갱신될 수 있고 선택된 ApplicationContext는 사실 "핫" 리프레시등을 지원한다. 예를 들면 XmlWebApplicationContext 핫 리프레시를 지원하지만 GenericApplicationContext는 지원하지 않는다.
ContextStartedEventConfigurableApplicationContext 인터페이스의 start() 메서드를 사용해서 ApplicationContext이 시작될 때 퍼블리싱된다. 여기서 "시작한다"는 의미는 모든 Lifecycle 빈이 명시적으로 시작 신호를 받는다는 말이다. 이 신호는 보통 명시적인 멈춤 후에 빈을 재시작하는 데 사용하지만 자동 시작이 설정되지 않은 컴포넌트들을 시작하는 데도 사용할 수 있다. 초기화 때 시작되지 않은 컴포넌트들이 그 예이다.
ContextStoppedEventConfigurableApplicationContext 인터페이스의 stop() 메서드를 사용해서 ApplicationContext이 멈췄을 때 퍼블리싱된다. 여기서 "멈춘다"는 의미는 모든 Lifecycle 빈이 명시적인 멈춤 신호를 받는다는 뜻이다. 멈춰진 컨텍스트는 start() 호출로 재시작할 수 있다.
ContextClosedEventConfigurableApplicationContext의 close() 메서드를 사용해서 ApplicationContext이 닫혔을 때 퍼블리싱된다. 여기서 "닫힌다"는 의미는 모든 싱글톤 빈이 파괴되었다는 의미이다. 닫혀진 컨텍스트는 생명의 끝에 도달한다. 이 컨텍스트는 갱신하거나 재시작할 수 없다.
RequestHandledEventHTTP 요청이 서비스되는 모든 빈에 전하는 웹에 특정화된(web-specific) 이벤트. 이 이벤트는 요청이 완료된 후에 퍼블리싱된다. 이 이벤트는 스프링의 DispatcherServlet을 사용하는 웹 어플리케이션에만 적용할 수 있다.





자신만의 커스텀 이벤트를 생성해서 퍼블리싱할 수도 있다. 이 예제는 스프링의 ApplicationEvent 기반 클래스를 확장하는 간단한 클래스를 보여준다.

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

public class BlackListEvent extends ApplicationEvent {
  private final String address;
  private final String test;

  public BlackListEvent(Object source, String address, String test) {
      super(source);
      this.address = address;
      this.test = test;
  }
  // 접근자와 그 외 메서드들...
}

커스텀 ApplicationEvent를 퍼블리싱하려면 ApplicationEventPublisher의 publishEvent() 메서드를 호출해라. 보통 이는 ApplicationEventPublisherAware를 구현한 클래스를 생성하고 스프링 빈으로 등록함으로써 완료된다. 다음 예제는 이러한 클래스를 보여준다.

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

public class EmailService implements ApplicationEventPublisherAware {

  private List<String> blackList;
  private ApplicationEventPublisher publisher;

  public void setBlackList(List<String> blackList) {
      this.blackList = blackList;
  }

  public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
      this.publisher = publisher;
  }

  public void sendEmail(String address, String text) {
      if (blackList.contains(address)) {
          BlackListEvent event = new BlackListEvent(this, address, text);
          publisher.publishEvent(event);
          return;
      }
      // 이메일 보내기...
  }
}

설정타임시에 스프링 컨테이너는 ApplicationEventPublisherAware를 구현한 EmailService를 탐지할 것이고 자동적으로 setApplicationEventPublisher()를 호출할 것이다. 실제로는 전달한 파라미터는 스프링 컨테이너 자체가 될 것이다. ApplicationEventPublisher 인터페이스를 통해서 어플리케이션 컨텍스트와 간단히 상호작용한다.

커스텀 ApplicationEvent를 받으려면 ApplicationListener를 구현한 클래스를 생성하고 스프링 빈으로 등록해라. 다음 에제는 이러한 클래스를 보여준다.


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

public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

  private String notificationAddress;

  public void setNotificationAddress(String notificationAddress) {
      this.notificationAddress = notificationAddress;
  }

  public void onApplicationEvent(BlackListEvent event) {
        // notificationAddress를 통해서 적절한 같은 그룹에 알린다...
  }
}

ApplicationListener는 일반적으로 커스텀 이벤트의 타입인 BlackListEvent으로 파라미터화된다. 이는 onApplicationEvent() 메서드가 타입세이프할 수 있고 다운캐스팅이 필요한 경우를 피한다는 의미이다. 필요한 만큼의 많은 이벤트 리스터를 등록할 수 있지만 기본적으로 이벤트 리스너는 동기적으로 이벤트를 받는다. 이는 모든 리스너가 이벤트 처리를 완료할 때까지 publishEvent()가 블락된다는 의미이다. 이 동기방식과 싱글쓰레드 접근의 한가지 이점은 리스너가 이벤트를 받을 때 트랜잭션 컨텍스트를 사용할 수 있으면 퍼블리셔의 트랜젝션 컨텍스트내에서 수행된다는 것이다. 이벤트 발행에 대한 또다른 전략이 필요하다면 스프링의 ApplicationEventMulticaster 인터페이스에 대한 JavaDoc을 참고해라.

다음 예제는 위의 클래스 각각을 등록하고 설정하는 빈 정의를 보여준다.


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

<bean id="emailService" class="example.EmailService">
  <property name="blackList">
      <list>
          <value>known.spammer@example.org</value>
          <value>known.hacker@example.org</value>
          <value>john.doe@example.org</value>
      </list>
  </property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
  <property name="notificationAddress" value="blacklist@example.org"/>
</bean>

emailService빈의 sendEmail()를 호출할 때 블랙리스트에 있는 이메일이 있다면 BlackListEvent 타입의 커스텀 이벤트가 퍼블리시된다. blackListNotifier 빈이 ApplicationListener으로 등록되므로 BlackListEvent를 받아서 적당한 같은 그룹에게 알릴 수 있다.


Note
스프링의 이벤트 메카니즘은 같은 어플리케이션 컨텍스트내의 스프링 빈간의 간단한 통신을 위해 디자인되었다. 하지만 더 복잡한 엔터프리이즈 통합이 필요하면 따로 관리되는 Spring Integration 프로젝트가 잘 알려진 스프링 프로그래밍 모델에 기반해서 경량이고 패턴지향(pattern-oriented)이고 이벤트기반의 아키텍쳐를 구성하는데 대한 완전한 지원을 제공한다.

4.14.3 저수준 리소스에 편리하게 접근하기
어플리케이션 컨텍스트의 가장 바람직한 사용과 이해를 위해서는 Chapter 5, Resources 챕터에서 설명했듯이 사용자들은 보통 스프링의 Resource 추상화에 스스로 익숙해져야 한다.

어플리케이션 컨텍스트는 Resource를 로드하는데 사용할 수 있는 ResourceLoader이다. Resource는 본질적으로 JDK java.net.URL 클래스의 더 많은 기능이 있는 리치(rich) 버전이다. 사실 Resource 구현체는 적절한 위치에서 java.net.URL의 인스턴스를 감싼다. Resource는 투명한 방법으로 클래스패스, 파일시스템 위치, 표준 URL로 나타낼 수 있는 위치 등 거의 대부분의 위치에서 저수준 리소스들을 획득할 수 있다. 리소스 위치 문자열이 어떤 특별한 접두사가 없는 간단한 경로라면 이라한 리소스의 위치는 실제 어플리케이션 타입을 지정하고 사용한다.

어플리케이션 컨텍스트에 배포한 빈이 특별한 콜백 인터페이스인 ResourceLoaderAware를 구현하도록 설정할 수 있다. 이렇게 함으로써 ResourceLoader처럼 어플리케이션 컨텍스트 자체에 전달된 것을 초기화 시에 자동적으로 다시 호출하게 한다. 정적 리소스에 접근하도록 Resource 타입의 프로퍼티를 노출할 수도 있다. 정적 리소스들은 다른 프로퍼티처럼 주입될 것이다. 이러한 Resource 프로퍼티들을 간단한 경로 문자열로 지정할 수도 있고 빈이 배포될 때 해당 문자열을 실제 Resource 객체로 변환하도록 하기 위해 컨텍스트가 자동으로 등록하는 특별한 JavaBean PropertyEditor에 의존할 수도 있다.

ApplicationContext 생성자에 전달한 위치 경로(location path)는 실제로 리소스 문자열이며 간단한 형식으로 적절하게 특정정한 컨텍스트 구현체처럼 다뤄진다. ClassPathXmlApplicationContext는 간단한 위치 경로를 클래스패스 위치처럼 다룬다. 실제 컨텍스트 타입에 상관없이 클래스패스나 URL에서 정의를 강제로 로드하도록 특별한 접두사를 가진 위치 경로(리소스 문자열)를 사용할 수도 있다.

4.14.4 웹 어플리케이션에 펀리한 ApplicationContext 인스턴스화
ContextLoader등을 사용해서 선언적으로 ApplicationContext 인스턴스를 생성할 수 있다. 물론 ApplicationContext 구현체중 하나를 사용해서 프로그래밍적으로 생성할 수도 있다.

ContextLoader 메카니즘은 ContextLoaderListener와 ContextLoaderServlet 두 가지에서 왔다. 이 둘은 같은 기능이지만 서블릿 2.3 컨테이너에서 신뢰할 수 없는 리스너 버전이 다르다. 서블릿 2.4 명세에서 서블릿 컨텍스트 리스너는 웹 어플리케이션의 서블릿 컨텍스트가 생성되자 마자 즉시 실행되어야 하고 첫번째 요청을 서비스할 수 있어야 한다.(그리고 서블릿 컨텍스트가 셧다운되어야 한다는 부분도 있다.) 이러한 서블릿 컨텍스트 리스너는 스프링 ApplicationContext를 초기화하기에 이상인 곳이다. 모든 것이 같다면 아마도 ContextLoaderListener이 더 나을 것이다. 호환성에 대한 더 자세한 정보는 ContextLoaderServlet 자바독을 봐라.

다음과 같이 ContextLoaderListener를 사용해서 ApplicationContext를 등록할 수 있다.


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

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 또는 위의 리스너 대신 ContextLoaderServlet를 사용한다
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
-->

리스너는 contextConfigLocation 파라미터를 검사한다. 파라미터가 존재하지 않으면 리스너는 기본값인 /WEB-INF/applicationContext.xml를 사용한다. 파라미터가 존재하면 리스너는 미리 정의된 구분자 (콤마, 세미콜론, 공백)로 문자열을 분리하고 어플리케이션 컨텍스트를 검색해야하는 위치로 이 값을 사용한다. Ant 스타일 경로 패턴도 잘 지원한다. 예제들은 모두 "Context.xml"로 끝나는 파일명을 가지고 "WEB-INF" 디렉토리에 있는 /WEB-INF/*Context.xml와 "WEB-INF"의 하위디렉토리에 있는 이러한 모든 파일인 /WEB-INF/**/*Context.xml 이다.

ContextLoaderListener 대신에 ContextLoaderServlet를 사용할 수 있다. 서블릿은 마치 리스너가 하는 것처럼 contextConfigLocation 파라미터를 사용한다.

4.14.5 J2EE RAR 파일처럼 스프링 ApplicationContext 배포하기
스프링 2.5부터는 컨텍스트와 필요한 모든 빈 클래스와 라이브러리 JAR 파일들을 J2EE RAR 배포유닛으로 은닉화해서 RAR 파일로 스프링 ApplicationContext를 배포하는 것이 가능하다. 이는 단독으로 어클리케이션 컨텍스트를 실행한 것과 동일하며 단지 J2EE 환경에 호스팅해서 J2EE 서버 시설들에 접근할 수 있게 된 것이다. RAR 배포는 헤드없이 WAR 파일을 배포하는 시나리오보다 더 자연스러운 대안이다. 헤드없는 WAR 파일이라는 것은 J2EE 환경에서 스프링 어플리케이션컨텍스트를 시작하는데만 사용하는 HTTP 진입점이 없는 WAR 파일을 말한다.

RAR 배포는 HTTP 진입점이 필요없는 어플리케이션 컨텍스트의 이상형이라기 보다는 메시지 앤드포인트와 스케쥴된 잡들로만 이루어져있다. 이러한 컨텍스트의 빈들은 JTA 트랜잭션 매니저와 JNDI에 바인딩된 JDBC DataSource와 JMS ConnectionFactory 인스턴스같은 어플리케이션 서버 리소스들을 사용할 수 있고 플랫폼의 JMX서버와 함께 등록할 수도 있다. - 모든 것은 스프링의 표준 트랜잭션 관리와 JNDI, JMS 지원 부분을 통해서 이루어진다. 어플리케이션 컴포넌트는 스프링의 TaskExecutor 추상화로 어플리케이션 서버의 JCA WorkManager와 상호작용도 할 수 있다.

RAR 배포와 관련한 설정에 대한 자세한 내용은 SpringContextResourceAdapter 클래스의 JavaDoc을 확인해라.

J2EE RAR파일로 스프링 ApplicationContext을 간단히 배포하려면: 모든 어플리케이션 클래스들을 표준 JAR 파일과는 다른 확장자인 RAR 파일로 패키징한다. RAR 아카이브의 루트에 필요한 라이브러리의 JAR를 모두 추가한다. "META-INF/ra.xml" 배포 디스크립터(SpringContextResourceAdapter의 JavaDoc에 나온 것처럼)와 대응되는 스프링 XML 빈 정의 파일(보통 "META-INF/applicationContext.xml")을 추가하고 어플리케이션 서버의 배포 디렉토리에 최종 RAR 파일을 둔다.



Note
이러한 RAR 배포 유닛들은 보통 독립적이다. 컴포넌트를 외부로 노출하지 않으며 심지어 같은 어플리케이션의 다른 모듈에도 노출하지 않는다. RAR 기반 ApplicationContext와의 상호작용은 보통 다른 모듈과 공유하는 JMS 목적지(destination)를 통해서 발생한다. 예를 들어 RAR 기반 ApplicationContext는 잡(job)을 스케쥴링하거나 파일 시스템의 새로운 파일에 반응하는 등의 일도 할 수 있다. 외부의 동기적인 접근을 허용하려면 예를 들어 같은 머신의 다른 어플리케이션 모듈이 사용할 RMI 엔드포인트를 익스포트할 수 있다.

4.15 BeanFactory
BeanFactory는 스프링 IoC 기능의 근간을 이루는 원리를 제공하지만 다른 서드파티 프레임워크와의 통합에서만 직접적으로 사용되고 대부분의 스프링 유저들에게는 이제 전적으로 역사적인 것이다. BeanFactory와 BeanFactoryAware, InitializingBean, DisposableBean같은 관련 인터페이스들은 스프링과 통합된 많은 수의 서드파티 프레임워크들과의 하위호환성 때문에 여전히 스프링에 존재한다. 종종 JDK 1.4 호환성을 유지하거나 JSR-250에 대한 의존성을 없애기 위해 @PostConstruct나 @PreDestroy같은 더 최신 기능을 사용할 수 없는 서드파티 컴포넌트들이 있다.

이 섹션에서는 BeanFactory와 ApplicationContext의 차이점에 대한 추가적인 배경을 설명하고 전형적인 싱글톤 검색으로 직접 IoC 컨테이너에 어떻게 접근하는 지를 설명한다.

4.15.1 BeanFactory냐? ApplicationContext냐?
ApplicationContext을 사용하지 않을 좋은 이유가 있는 것이 아니라면 ApplicationContext을 사용해라.

ApplicationContext가 BeanFactory의 모든 기능을 포함하고 있기 때문에 메모리 소비가 중대한 문제가 될 수 있거나 조금 많은 킬로바이트가 차이를 만들 수 있는 Applet같은 몇가지 상황을 제외하고는 보통 BeanFactory보다 ApplicationContext를 더 권장한다. 하지만 대부분의 전형적인 엔터프라이즈 어플리케이션과 시스템에서는 사용하려는 것이 바로 ApplicationContext이다. 스프링 2.0이나 그 이상의 버전에서는 BeanPostProcessor 확장점을 많이 사용하게 된다.(프록싱하려는 등등) 평이한 BeanFactory만 사용한다면 트랜잭션이나 AOP같은 꽤 많은 지원을 사용할 수 없을 것이다. 최소한 개발자가 추가적인 어떤 작업을 해야한다. 이 상황은 설정에는 실제로 잘못된 곳이 없기 때문에 혼란스러울 수 있다.

다음 표는 BeanFactory와 ApplicationContext 인터페이스와 구현체가 제공하는 기능 목록이다.

Table 4.8. 기능 매트릭스

기능BeanFactoryApplicationContext
빈 인스턴스화/연결YesYes
자동 BeanPostProcessor 등록NoYes
자동 BeanFactoryPostProcessor  등록NoYes
편리한 MessageSource 접근 (i18n에 대한)NoYes
ApplicationEvent 발행NoYes

BeanFactory 구현체와 함께 빈 후처리자(bean post-processor)를 명시적으로 등록하려면 다음과 같은 코드를 반드시 작성해야 한다.

1
2
3
4
5
6
7
8
9
Java

ConfigurableBeanFactory factory = new XmlBeanFactory(...);

// 필요한 BeanPostProcessor 인스턴스를 등록한다
MyBeanPostProcessor postProcessor = new MyBeanPostProcessor();
factory.addBeanPostProcessor(postProcessor);

// 팩토리를 사용해서 시작한다

BeanFactory를 사용할 때 BeanFactoryPostProcessor를 명시적으로 등록하려면 다음과 같은 코드를 반드시 작성해야 한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));

// Properties 파일에서 몇몇 프로퍼티 값들을 가져온다
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// 이제 실제로 교체한다
cfg.postProcessBeanFactory(factory);

아주 대다수의 스프링을 사용하는 어플리케이션에서 다양한 ApplicationContext 구현체들은 위의 평이한 BeanFactory 구현체를 선호하기 때문에 두 경우의 명시적인 등록과정은 편리하지 않다. 특히 BeanFactoryPostProcessors와 BeanPostProcessors를 사용할 때 더욱 그렇다. 이러한 메카니즘은 프로퍼티 플레이스홀더 교체와 AOP같은 중요한 기능을 구현한다.

4.15.2 딱 붙은 코드(Glue code)와 나쁜 싱글톤
스프링 IoC 컨테이너가 서비스하는 코드와 생성될 때 컨테이너가 의존성을 제공하는 코드, 컨테이너가 인지하지 못하는 코드는 의존성 주입(DI) 스타일로 대부분의 어플리케이션을 작성하는 것이 가장 좋다. 하지만 때로는 다른 코드와 함께 묶을 필요가 있어서 코드의 딱 붙은 작은 레이어에는 때로 스프링 IoC 컨테이너에 접근하는 싱글톤(또는 quasi-singleton) 스타일이 필요하다. 예를 들어 서드파티 코드는 스프링 IoC 컨테이너에서 새로운 객체들을 얻는 능력없이 직접 새로운 객체들을 생성하려고 할 수도 있다.(Class.forName() 스타일) 서드파티 코드가 생성한 객체가 작은 스텁이나 프록시이면서 실제 객체를 얻는 것을 위임하려고 스프링 IoC 컨테이너에 접근하는 싱글톤 스타일을 사용한다면 제어의 역전은 여전히 대부분의 코드를 획득한다. 그러므로 대부분의 코드는 여전히 컨테이너를 알지 못하거나 어떻게 접근해야 하는지 모르고 모든 이득을 보장한 채로 다른 코드와 커플링이 없다. EJB는 평이한 자바 구현 객체에 위임하고, 스프링 IoC 컨테이서 획득하려고 이 스텁/프록시 접근을 사용할 것이다. 스프링 IoC 컨테이너 자체는 이상적으로 싱글톤이 되지 않기 때문에 각 빈이 자신의 싱글톤이 아닌 스프링 IoC 컨테이너를 사용하는 빈에서는 메모리 사용이나 초기화 시간 면에서 비현실적이 될 수 있다. (하이버네이트 SessionFactory같은 스프링 IoC 컨테이너의 빈을 사용할 때)

EJB 2.1 환경에서나 WAR 파일을 넘어서 WebApplicationContexts에 부모로 단일 ApplicationContext를 공유하고 싶을 때 서비스 로케이터 스타일로 어플리케이션 컨텍스트 검색하는 것은 때로 공유된 스프링이 관리하는 컨포넌트에 접근하기 위한 유일한 선택사항이다. 이 경우에 이 스프링 소스 팀블로그 글에서 설명한 ContextSingletonBeanFactoryLocator 로케이터 유틸리티 클래스를 사용해야 한다.

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

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

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

4.11 JSR 330 표준 어노테이션 사용
스프링 3.0부터는 JSR-330 표준 어노테이션 (의존성 주입)을 지원한다. 이러한 어노테이션들은 스프링 어노테이션과 같은 방법으로 스캔한다. 클래스패스에 적절한 jar를 두기만 하면 된다.



Note
메이븐을 사용한다면 표준 메이븐 저장소(http://repo1.maven.org/maven2/javax/inject/javax.inject/1/)에서 javax.inject artifact를 사용할 수 있다. pom.xml 파일에 다음 의존성을 추가할 수 있다.

1
2
3
4
5
6
7
Xml

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>


4.11.1 @Inject와 @Named를 사용한 의존성 주입
@Autowired 대신 @javax.inject.Inject 를 다음처럼 사용할 수 있다.


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

import javax.inject.Inject;

public class SimpleMovieLister {

  private MovieFinder movieFinder;

  @Inject
  public void setMovieFinder(MovieFinder movieFinder) {
      this.movieFinder = movieFinder;
  }
  // ...
}

@Autowired를 사용하듯이 클래스레벨, 필드레벨, 메서드레벨, 생성자 아규먼트레벨에서 @Inject를 사용하는 것이 가능하다. 주입되어야 하는 의존성에 검증된 이름을 사용하기를 좋아한다면 다음과 같이 @Named 어노테이션을 사용할 수 있다.

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

import javax.inject.Inject;
import javax.inject.Named;

public class SimpleMovieLister {

  private MovieFinder movieFinder;

  @Inject
  public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
      this.movieFinder = movieFinder;
  }
  // ...
}

4.11.2 @Named: @Component 어노테이션과 동일한 표준
@Component 대신에 @javax.inject.Named를 다음과 같이 사용할 수 있다.

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

import javax.inject.Inject;
import javax.inject.Named;

@Named("movieListener")
public class SimpleMovieLister {

  private MovieFinder movieFinder;

  @Inject
  public void setMovieFinder(MovieFinder movieFinder) {
     this.movieFinder = movieFinder;
  }
  // ...
}

컴포넌트에 이름을 지정하지 않고 @Component를 사용하는 것은 아주 일반적이다. 동일한 방법으로 @Named를 사용할 수 있다.

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

import javax.inject.Inject;
import javax.inject.Named;
  
@Named
public class SimpleMovieLister {

  private MovieFinder movieFinder;

  @Inject
  public void setMovieFinder(MovieFinder movieFinder) {
      this.movieFinder = movieFinder;
  }
  // ...
}

@Named를 사용하면 스프링 어노테이션을 사용할 때와 완전히 같은 방법으로 컴포넌트 스캔을 사용할 수 있다.

1
2
3
4
5
Xml

<beans>
    <context:component-scan base-package="org.example"/>
</beans>

4.11.3 표준 접근의 한계
표준 어노테이션들을 사용해서 작업할 때 다음 표에 나온 것처럼 몇가지 중요한 기능들을 사용할 수 있다는 것을 꼭 알아야 한다.

Table 4.6. 스프링 어노테이션 vs. 표준 어노테이션

Springjavax.inject.*javax.inject의 제약 / 설명
@Autowired@Inject@Inject에는 'required'속성이 없다
@Component@Named-
@Scope("singleton")@SingletonJSR-330의 기본 범위는 스프링의 prototype과 비슷하다. 하지만 스프링의 일반적인 기본값과 일관성을 유지하기 위해 스프링 컨테이너에서 선언된 JSR-330 빈은 기본적으로 singleton이다. The JSR-330 default scope is like Spring's prototype. singleton 대신 다은 범위를 사용하려면 스프링의 @Scope 어노테이션을 사용해야 한다.

javax.inject도 @Scope 어노테이션을 제공한다. 그렇기는 하지만 이 어노테이션은 자신만의 어노테이션을 생성할 때만 사용하도록 만들어졌다.
@Qualifier@Named-
@Value-동일한 것이 없다
@Required-동일한 것이 없다
@Lazy-동일한 것이 없다


















4.12 자바기반의 컨테이너 설정
4.12.1 기본 개념: @Configuration와 @Bean
스프링의 새로운 자바설정 지원의 핵심부분은 @Configuration 어노테이션이 붙은 클래스다. 이러한 클래스들은 스프링 IoC 컨테이너가 관리하는 객체의 인스턴스화, 설정, 초기화 로직을 정의하는 @Bean 어노테이션이 붙은 메서드들을 주로 이루어져 있다.

클래스에 @Configuration 어노테이션을 붙히는 것은 스프링 IoC 컨테이너가 해당 클래스를 빈 정의의 소스로 사용한다는 것을 나타낸다. 가장 간단한 @Configuration 클래스는 다음과 같을 것이다.


1
2
3
4
5
6
7
8
9
Java

@Configuration
public class AppConfig {
  @Bean
  public MyService myService() {
      return new MyServiceImpl();
  }
}

스프링 <beans/> XML에 익숙하다면 앞의 AppConfig 클래스는 다음과 같을 것이다.

1
2
3
4
5
Xml

<beans>
  <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

여기서 보듯이 @Bean 어노테이션은 <bean/> 요소와 같은 역할을 한다. @Bean 어노테이션은 이번 섹션 후반에 더 깊게 살펴볼 것이다. 하지만 우선 자바기반의 설정을 사용해서 스프링 컨테이서를 생성하는 여러가지 방법을 살펴보자.

4.12.2 AnnotationConfigApplicationContext를 사용하는 스프링 컨테이너 예제 살펴보기
이 섹션에서는 스프링 3.0의 새 기능인 AnnotationConfigApplicationContext를 설명한다. 이 다재다능한 ApplicationContext 구현체는 인풋으로 @Configuration 클래스뿐만 아니라 평범한 @Component 클래스와 JSR-330 메타데이터로 어노테이션이 붙은 클래스들도 받아들일 수 있다.

인풋으로 @Configuration클래스를 받았을 때 @Configuration 클래스 자체가 빈 정의로 등록되고 해당 클래스내의 선언된 모든 @Bean 메서드들도 빈 정의로 등록된다.

@Component와 JSR-330 클래스들이 제공되었을 때 이 클래스들은 빈 정의로 등록되고 해당 클래스내에서 필요한 곳에 @Autowired나 @Inject 같은 DI 메타데이터가 사용되었다고 가정한다.

4.12.2.1 간단한 구성
ClassPathXmlApplicationContext를 인스턴스화 할 때 인풋으로 스프링 XML 파일을 사용하는 방법과 거의 동일하게 AnnotationConfigApplicationContext를 인스턴스화 할 때 @Configuration 클래스들을 인풋으로 사용할 것이다. 이를 통해 전혀 XML을 사용하지 않고 스프링 컨테이너를 사용할 수 있다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

public static void main(String[] args) {
  AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
  ctx.register(AppConfig.class, OtherConfig.class);
  ctx.register(AdditionalConfig.class);
  ctx.refresh();
  MyService myService = ctx.getBean(MyService.class);
  myService.doStuff();
}

4.12.2.3 scan(String...)으로 컴포넌트 스캔 가능하게 하기
경험있는 스프링 사용자들은 다음과 같이 일반적으로 사용되는 스프링의 context: 네임스페이스로 XML을 선언하는데 익숙할 것이다.

1
2
3
4
5
Xml

<beans>
  <context:component-scan base-package="com.acme"/>
</beans>

위의 예제에서 com.acme 팩키지는 스캔되고 @Component 어노테이션이 붙은 클래스들을 찾고 이러한 클래스를 컨테이너내 스프링 빈 정의로 등록할 것이다. AnnotationConfigApplicationContext에는 같은 컴포넌트 스캔 기능을 하는 scan(String...) 메서드가 있다.

1
2
3
4
5
6
7
8
Java

public static void main(String[] args) {
  AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
  ctx.scan("com.acme");
  ctx.refresh();
  MyService myService = ctx.getBean(MyService.class);
}




Note
@Configuration 클래스들은 @Component로 메타 어노테이션이 붙은 클래스라는 것을 기억해라. 그래서 이 클래스들은 컴포넌트 스캔의 후보들이 된다. 위의 예제에서 com.acme 팩키지 (또는 그 하위의 어떤 팩키지)내에 AppConfig가 선언었다는 것을 가정하고 이는 scan()을 호출하는 동안 선택될 것이고 클래스의 모든 @Bean 메서드들을 refresh() 할 때 컨테이너내 빈 정의로 처리되고 등록될 것이다.

4.12.2.4 AnnotationConfigWebApplicationContext를 사용한 웹 어플리케이션 지원
AnnotationConfigApplicationContext의 WebApplicationContext 변형은 AnnotationConfigWebApplicationContext로 사용할 수 있다. 이 구현체는 스프링 ContextLoaderListener 서블릿 리스너, 스프링 MVC DispatcherServlet 등을 설정할 때 사용할 수 있다. 다음은 전형적인 스프링 MVC 웹 어플리케이션을 설정하는 web.xml의 예제이다. contextClass context-param과 init-param의 사용방법을 보여준다.


 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
44
45
46
47
48
49
50
51
Xml

<web-app>
  <!-- 기본 XmlWebApplicationContext 대신 AnnotationConfigWebApplicationContext를 
       사용하는 ContextLoaderListener를 설정한다 -->
  <context-param>
      <param-name>contextClass</param-name>
      <param-value>
          org.springframework.web.context.support.AnnotationConfigWebApplicationContext
      </param-value>
  </context-param>

  <!-- 설정 위치는 반드시 콤마나 공백을 구분자로 사용하는 하나 이상의 정규화된 @Configuration 
       클래스들로 구성되어야 한다. 정규화된 팩키지는 컴포넌트 스캔으로 
       지정될 수도 있다. -->
  <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>com.acme.AppConfig</param-value>
  </context-param>

  <!-- 평소처럼 ContextLoaderListener를 사용해서 루트 어플리케이션 시작하기 -->
  <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- 평소처럼 스프링 MVC DispatcherServlet 선언 -->
  <servlet>
      <servlet-name>dispatcher</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <!-- 기본 XmlWebApplicationContext 대신 AnnotationConfigWebApplicationContext를 
           사용한 DispatcherServlet 설정 -->
      <init-param>
          <param-name>contextClass</param-name>
          <param-value>
              org.springframework.web.context.support.AnnotationConfigWebApplicationContext
          </param-value>
      </init-param>
      <!-- 다시한번, 설정 위치는 반드시 콤마나 공백을 구분자로 사용하는 하나 이상의 정규화된 
           @Configuration 클래스들로 구성되어야 한다. -->
      <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>com.acme.web.MvcConfig</param-value>
      </init-param>
  </servlet>

  <!-- /app/*에 대한 모든 요청을 디스패쳐 서블릿에 매핑한다 -->
  <servlet-mapping>
      <servlet-name>dispatcher</servlet-name>
      <url-pattern>/app/*</url-pattern>
  </servlet-mapping>
</web-app>

4.12.3 자바 기반으로 설정 구성하기
4.12.3.1 @Import 어노테이션의 사용
스프링 XML 파일에서 설정 모듈화에 <import/>요소를 사용하기는 하지만 @Import 어노테이션은 다른 설정 클래스에서 @Bean 설정을 로딩한다.


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

@Configuration
public class ConfigA {
  public @Bean A a() { return new A(); }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {
  public @Bean B b() { return new B(); }
}

이제 컨텍스트를 인스턴스화 할 때 ConfigA.class와 ConfigB.class를 둘 다 지정해야하는 대신 ConfigB만 명시적으로 제공하면 된다.

1
2
3
4
5
6
7
8
9
Java

public static void main(String[] args) {
  ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

  // now both beans A and B will be available...
  A a = ctx.getBean(A.class);
  B b = ctx.getBean(B.class);
}

이 접근은 개발자가 설정을 구성하는 동안 잠재적으로 많은 수의 @Configuration 클래스들을 기억해야 하는 대신 하나의 클래스만 다루면 되므로 컨테이너 인스턴스화를 단순화한다.

임포트한 @Bean 정의에서 의존성 주입하기
위의 예제는 동작하기는 하지만 너무 간단하다. 대부분의 실무에서는 빈에 다른 설정 클래스들에 대한 의존성이 있을 것이다. XML을 사용할 때 컴파일러가 관여하지 않고 그냥 ref="someBean"만 선언한 뒤 스프링이 컨테이너를 인스턴스화 하면제 제대로 동작하기를 믿으면 되기 때문에 의존성 자체는 이슈가 아니었다. 물론 @Configuration를 사용할 때 자바 컴파일러는 다른 빈에 대한 참조는 유효한 자바문법이어야 한다는 제약을 설정 모델에 둔다.

다행히도 이 문제의 해결책은 간단하다. @Configuration 클래스들은 결국 컨테이너내의 다른 빈일 뿐이라는 것을 기억해라. 이는 @Configuration 클래스들이 다른 빈처럼 @Autowired 주입 메타데이터의 이점을 취할 수 있다는 것을 의미한다!

다른 빈들에 선언된 빈에 따라 각각 다수의 @Configuration 클래스들이 있는 더 현실적인 시나리오를 생각해 보자.


 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
Java

@Configuration
public class ServiceConfig {
  private @Autowired AccountRepository accountRepository;

  public @Bean TransferService transferService() {
      return new TransferServiceImpl(accountRepository);
  }
}

@Configuration
public class RepositoryConfig {
  private @Autowired DataSource dataSource;

  public @Bean AccountRepository accountRepository() {
      return new JdbcAccountRepository(dataSource);
  }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
  public @Bean DataSource dataSource() { /* return new DataSource */ }
}

public static void main(String[] args) {
  ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
  // 모든 것들은 설정 클래스들 사이에서 연결된다...
  TransferService transferService = ctx.getBean(TransferService.class);
  transferService.transfer(100.00, "A123", "C456");
}

네비게이션을 편하게 하기 위해 임포트한 빈들을 정규화하기
위의 시나리오에서 @Autowired는 잘 동작하고 원하는 모듈화를 제공하지만 정확히 자동연결된 빈정의가 어디인지 결정하는 것은 여전히 약간 모호하다. 예를 들어 한 개발자가 ServiceConfig를 보듯이 정확히 어디에 @Autowired AccountRepository 빈이 선언되었는지 어떻게 알 수 있는가? 이는 코드에서 명백하지 않지만 괜찮을 것이다. SpringSource Tool Suite가 모든 것들이 어떻게 연결되는지 보여주는 그래프를 그리는 도구를 제공한다. 이 그래프가 당신이 바라는 전부일 것이다. 게다가 자바 IDE는 쉽게 모든 선언과 AccountRepository의 사용을 찾을 수 있고 해당 타입을 리턴하는 @Bean 메서드의 위치를 빠르게 보여줄 것이다.

이 애매모호함을 받아들일 수 없고 한 @Configuration 클래스에서 다른 클래스까지 IDE에서 직접 네비게이션하기를 원하는 경우에는 설정 클래스들 자체를 자동연결하는 것을 고려해 봐라.


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

@Configuration
public class ServiceConfig {
  private @Autowired RepositoryConfig repositoryConfig;

  public @Bean TransferService transferService() {
      // 설정 클래스 '전체에서' @Bean 메서드를 탐색한다!
      return new TransferServiceImpl(repositoryConfig.accountRepository());
  }
}

위의 상황에서는 어디 AccountRepository가 정의되었는지가 아주 명백하다. 하지만 이제 ServiceConfig가 RepositoryConfig에 강하게 연결되어 있다. 이는 트레이드 오프(tradeoff)다. 인터페이스 기반이나 추상 클래스 기반의 @Configuration 클래스들을 사용해서 이 강한 커플링을 약간 완화할 수 있다. 다음을 보자.

 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
Java

@Configuration
public class ServiceConfig {
  private @Autowired RepositoryConfig repositoryConfig;

  public @Bean TransferService transferService() {
      return new TransferServiceImpl(repositoryConfig.accountRepository());
  }
}

@Configuration
public interface RepositoryConfig {
  @Bean AccountRepository accountRepository();
}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
  public @Bean AccountRepository accountRepository() {
      return new JdbcAccountRepository(...);
  }
}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // 구체적인(concrete) 설정을 임포트한다!
public class SystemTestConfig {
  public @Bean DataSource dataSource() { /* DataSource를 리턴한다 */ }
}

public static void main(String[] args) {
  ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
  TransferService transferService = ctx.getBean(TransferService.class);
  transferService.transfer(100.00, "A123", "C456");
}

이제 ServiceConfig는 관련된 구체적인(concrete) DefaultRepositoryConfig에 약하게 연결되어 있고 내장 IDE 도구는 여전히 유용하다. 개발자는 쉽게 RepositoryConfig 구현체들의 타입 계층을 얻을 수 있을 것이다. 이 방법으로 @Configuration 클래스들과 그 의존성들을 네비게이션 하는 것은 인터페이스 기반의 코드를 네비게이션하는 일반적인 과정과 다르지 않아졌다.

4.12.3.2 자바와 XML 설정을 조합하기
스프링의 @Configuration 클래스 지원은 스프링의 XML을 100% 완전히 대체하지 못한다. 스프링 XML 네임스페이스같은 몇몇 기능들은 여전히 컨테이너를 설정하는 이상적인 방법이다. XML이 편리하거나 필수적인 상황에서 선택권이 있다. ClassPathXmlApplicationContext등을 사용한 "XML 중심적인" 방법으로 컨테이너를 인스턴스화하거나 AnnotationConfigApplicationContext와 필요한 XML을 임포트하는 @ImportResource 어노테이션을 사용하는 "자바 중심적인"방법 중에서 선택할 수 있다.

@Configuration 클래스의 XML 중심적인 사용
애드훅으로 @Configuration 클래스와 함께 XML로 스프링 컨테이너를 시작하는 것을 선호할 수도 있다. 예를 들어 스프링 XML을 사용하는 많은 양의 코드가 이미 있는 경우 필요한만큼의 원리에 따라 @Configuration 클래스를 생성하고 존재하는 XML파일에서 이 클래스들을 포함하는 것을 쉬울 것이다. 아래에서 "XML 중심적인" 상황에 위와 같은 경우에서 @Configuration 클래스들을 사용하는 옵션들을 볼 것이다.

평범한 스프링 <bean/> 요소처럼 @Configuration 클래스 선언하기
@Configuration는 결국은 그냥 컨테이너의 빈 정의라는 것을 기억해라. 이 예제에서 AppConfig라는 @Configuration 클래스를 생성하고 이 클래스를 <bean/>정의로 system-test-config.xml안에 포함시켰다. <context:annotation-config/>를 활성화 했기 때문에 컨테이너는 @Configuration 어노테이션을 인식하고 AppConfig에 선언된 @Bean 메서드를 적절히 처리할 것이다.


 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
44
45
46
Java

@Configuration
public class AppConfig {
  private @Autowired DataSource dataSource;

  public @Bean AccountRepository accountRepository() {
      return new JdbcAccountRepository(dataSource);
  }

  public @Bean TransferService transferService() {
      return new TransferService(accountRepository());
  }
}

Xml

system-test-config.xml
<beans>
  <!-- @Autowired와 @Configuration같은 어노테이션 처리가 가능하게 하기 -->
  <context:annotation-config/>
  <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

  <bean class="com.acme.AppConfig"/>

  <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="url" value="${jdbc.url}"/>
      <property name="username" value="${jdbc.username}"/>
      <property name="password" value="${jdbc.password}"/>
  </bean>
</beans>

C-like

jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=

Java

public static void main(String[] args) {
  ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
  TransferService transferService = ctx.getBean(TransferService.class);
  // ...
}



Note
위의 system-test-config.xml에서 AppConfig<bean/>에 id 요소를 선언하지 않았다. 이렇게 하는 것은 허용되지 않으며 이를 참조하는 빈이 없으므로 필요하지 않다. 그리고 이름으로 컨테이너에서 명시적으로 가져올 가능성도 없다. DataSource 빈과 마찬가지로 이는 타입으로만 자동연결되므로 명시적인 빈 id가 엄격하게 필요하지 않다.

@Configuration를 선택하는 <context:component-scan/>의 사용
@Configuration에 @Component 메타 어노테이션이 붙었으므로 @Configuration 어노테이션이 붙은 클래스들은 자동적으로 컴포넌트 스캔의 후보가 된다. 위와 같은 시나리오에서 컴포넌트 스캔의 이점을 얻기 위해 system-test-config.xml를 재정의 할 수 있다. 이 경우에 <context:component-scan/>가 같은 기능을 모두 사용가능하게 하므로 명시적으로 <context:annotation-config/>를 선언할 필요가 없다.


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

system-test-config.xml
<beans>
  <!--  정의로 AppConfig를 선택하고 등록한다 -->
  <context:component-scan base-package="com.acme"/>
  <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

  <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="url" value="${jdbc.url}"/>
      <property name="username" value="${jdbc.username}"/>
      <property name="password" value="${jdbc.password}"/>
  </bean>
</beans>

@ImportResource로 @Configuration 클래스 중심적인 XML의 사용
@Configuration클래스가 컨테이너를 설정하는 주요 메카니즘인 어플리케이션에서 여전히 최소한 약간의 XML은 사용할 필요가 있을 것이다. 이러한 시나리오에서 간단히 @ImportResource를 사용하고 필요한만큼의 XML을 정의한다. 이렇게 해서 최소한의 XML만으로 컨테이너를 설정하는 "자바 중심적인" 접근을 할 수 있다.

 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
Java

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
  private @Value("${jdbc.url}") String url;
  private @Value("${jdbc.username}") String username;
  private @Value("${jdbc.password}") String password;

  public @Bean DataSource dataSource() {
      return new DriverManagerDataSource(url, username, password);
  }
}

Xml

properties-config.xml
<beans>
  <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>

C-like

jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=

Java

public static void main(String[] args) {
  ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
  TransferService transferService = ctx.getBean(TransferService.class);
  // ...
}

4.12.4 @Bean 어노테이션의 사용
@Bean는 메서드 레벨의 어노테이션이고 XML <bean/> 요소와 바로 대응되는 요소이다. 이 어노테이션은 init-method, destroy-method, autowiring, name처럼 <bean/>가 제공하는 속성들의 일부를 지원한다.

@Configuration 어노테이션이나 @Component 어노테이션이 붙은 클래스에서 @Bean 어노테이션을 사용할 수 있다.

4.12.4.1 빈 선언하기
빈을 선언하려면 메서드에 @Bean 어노테이션을 붙히면 된다. 메서드가 리턴하는 값으로 지정한 타임의 ApplicationContext내에서 빈 정의를 등록하기 위해 이 메서드를 사용한다. 기본적으로 빈 이름은 메서드의 이름과 같을 것이다. 다음은 @Bean 메서드를 선언하는 간단한 예제이다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

@Configuration
public class AppConfig {

  @Bean
  public TransferService transferService() {
      return new TransferServiceImpl();
  }
}

앞의 설정은 다음 스프링 XML과 완전히 똑같다.

1
2
3
4
5
Xml

<beans>
  <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>  

두 선언은 모두 ApplicationContext에서 사용할 수 있는 transferService라는 빈을 만들고 TransferServiceImpl 타입의 인스턴스에 바인딩한다.

1
2
3
C-like

transferService -> com.acme.TransferServiceImpl

4.12.4.2 의존성 주입
@Bean 들이 다른 빈에 대한 의존성이 있을 때 의존성을 나타내는 것은 하나의 빈 메서드가 다른 메서드를 호출하는 것만큼 간단하다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Java

@Configuration
public class AppConfig {

  @Bean
  public Foo foo() {
      return new Foo(bar());
  }

  @Bean
  public Bar bar() {
      return new Bar();
  }
}
위의 예제에서 foo은 생성자 주입으로 bar에 대한 참조를 받는다.

4.12.4.3 라이프사이클 콜백 받기
@Configuration 어노테이션이 붙은 클래스에서 선언한 빈들은 정규 라이프사이클 콜백을 지원한다. @Bean 어노테이션으로 정의된 어떤 클래스들도 JSR-250의 @PostConstruct와 @PreDestroy 어노테이션을 사용할 수 있다. JSR-250의 자세한 내용은 JSR-250 annotations를 봐라.

정규 스프링 라이프사이클 콜백을 완전히 지원한다. 빈이 InitializingBean, DisposableBean, Lifecycle를 구현하면 컨테이너가 각각의 메서드를 호출한다.

BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware 등과 같은 *Aware 인터페이스 표준 세트도 완전히 지원한다.

스프링 XML의 bean 요소에서 init-method와 destroy-method 속성처럼 @Bean 어노테이션은 임의의 초기화와 파괴 콜백 메서드 지정하는 것을 지원한다.


 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 Foo {
  public void init() {
      // 초기화 로직
  }
}

public class Bar {
  public void cleanup() {
      // 파괴 로직
  }
}

@Configuration
public class AppConfig {
  @Bean(initMethod = "init")
  public Foo foo() {
      return new Foo();
  }
  @Bean(destroyMethod = "cleanup")
  public Bar bar() {
      return new Bar();
  }
}

물론 위의 Foo의 경우는 생성과정동안 init() 메서드를 유효하게 직접 호출하는 것과 같다.

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

@Configuration
public class AppConfig {
  @Bean
  public Foo foo() {
     Foo foo = new Foo();
     foo.init();
     return foo;
  }

  // ...
}




Tip
자바로 직접 작업할 때 객체로 할 수 있는 모든 것을 할 수 있고 항상 컨테이너 라이프사이클에 의존한 필요가 없다!

4.12.4.4 빈 범위 지정하기
@Scope 어노테이션의 사용
@Bean 어노테이션으로 정의한 빈이 특정 범위를 갖도록 지정할 수 있다. Bean Scopes 섹션에서 지정한 표준 범위들은 모두 사용할 수 있다.

기본 범위는 singleton이지만 @Scope 어노테이션으로 이 기본 범위를 오버라이드할 수 있다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

@Configuration
public class MyConfiguration {
  @Bean
  @Scope("prototype")
  public Encryptor encryptor() {
      // ...
  }
}

@Scope와 범위를 가진 프록시
스프링은 범위를 가진 프록시를 통해서 범위를 가진 의존성과 동작하도록 하는 편리한 방법을 제공한다. XML 설정을 사용할 때 이러한 프록시를 생성하는 가장 쉬운 방법은 <aop:scoped-proxy/> 요소이다. 자바로 @Scope 어노테이션이 붙은 빈을 설정할 때 proxyMode 속성으로 동일한 기능을 제공한다. 기본값은 프록시가 없는 것이지만 (ScopedProxyMode.NO) ScopedProxyMode.TARGET_CLASS나 ScopedProxyMode.INTERFACES를 지정할 수 있다.

XML 레퍼런스 문서의 범위를 가진 프록시 예제를 (앞의 링크를 봐라) 자바를 사용하는 @Bean으로 포팅하면 다음과 같을 것이다.


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

// HTTP 세션 범위를 가진 빈을 프록시로 노출한다
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserPreferences userPreferences() {
 return new UserPreferences();
}

@Bean
public Service userService() {
 UserService service = new SimpleUserService();
 // 프록시 userPreferences 빈에 대한 참조
 service.setUserPreferences(userPreferences());
 return service;
}

검색 메서드 주입
앞에서 얘기했듯이 검색 메서드 주입은 드물게 사용하는 고급 기능이다. 이 기능은 싱글톤 범위를 가진 빈이 프로토타입 범위를 가진 빈에 의존성이 있는 경우에 유용하다. 자바로 이러한 타입의 설정을 사용한다는 것은 이 패턴을 구현한다는 자연스러운 의미가 된다.

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

public abstract class CommandManager {
  public Object process(Object commandState) {
      // 적절한 Command 인터페이스의 새로운 인스턴스를 획득한다
      Command command = createCommand();

      // (완전히 새로운) Command 인스턴스에 상태를 설정한다
      command.setState(commandState);
      return command.execute();
  }

  // 괜찮다... 하지만 이 메서드의 구현은 어디에 있는가?
  protected abstract Command createCommand();
}

자바로 설정을 사용할 때 새로운 (프로토타입) 커맨드 객체를 검색하는 방법같은 추상 createCommand() 메서드를 오버라이드한 CommandManager의 서브클래스를 생성할 수 있다.

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

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
  AsyncCommand command = new AsyncCommand();
  // 필요한 대로 여기서 의존성을 주입한다
  return command;
}

@Bean
public CommandManager commandManager() {
  // 새로운 프로토타입 Command 객체를 리턴하도록 command()를 오버라이드한 CommandManager의 
  // 새로운 익명 구현체를 리턴한다
  return new CommandManager() {
      protected Command createCommand() {
          return asyncCommand();
      }
  }
}

4.12.4.5 빈 이름 커스터마이징하기
기본적으로 설정 클래스들은 @Bean 메서드의 이름을 생성된 빈의 이름으로 사용한다. 하지만 이 기능은 name 속성으로 오버라이드 할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

@Configuration
public class AppConfig {

  @Bean(name = "myFoo")
  public Foo foo() {
      return new Foo();
  }
}

4.12.4.6 빈 별칭짓기
Section 4.3.1, “빈 이름짓기”에서 얘기했듯이 때로는 빈 별칭짓기로 알려진 단일 빈에 여러 가지 이름을 주어야 한다. @Bean 어노테이션의 name 속성은 이 용도를 위해서 문자열 배열을 받아들인다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

@Configuration
public class AppConfig {

  @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
  public DataSource dataSource() {
      // 인스턴스화하고 설정하고 DataSource 빈을 리턴한다...
  }
}

4.12.5 자바기반의 설정의 내부동작에 대한 추가적인 내용
다음 예제는 @Bean 어노테이션이 분은 메서드가 두번 호출되는 것을 보여준다.

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

@Configuration
public class AppConfig {

  @Bean
  public ClientService clientService1() {
    ClientServiceImpl clientService = new ClientServiceImpl();
    clientService.setClientDao(clientDao());
    return clientService;
  }
  @Bean
  public ClientService clientService2() {
    ClientServiceImpl clientService = new ClientServiceImpl();
    clientService.setClientDao(clientDao());
    return clientService;
  }

  @Bean
  public ClientDao clientDao() {
    return new ClientDaoImpl();
  }
}

clientDao()는 clientService1()에서 한번 clientService2()에서 한번 호출된다. 이 메서드가 ClientDaoImpl의 새로운 인스턴스를 생성하고 리턴하기 때문에 2개의 인스턴스를 (각 서비스마다 하나씩) 기대하는 것이 정상적이다. 이는 명확하게 문제의 소지가 있다. 스프링에서 인스턴스화 된 빈들은 기본적으로 singleton 범위를 가진다. 여기가 마법이 일어나는 곳이다. 모든 @Configuration 클래스는 시작할 때(startup-time) CGLIB과 함께 서브클래스가 된다. 서브클래스에서 자식 메서드는 부모 메서드를 호출하고 새로운 인스턴스를 생성하기 전에 캐싱된 (범위를 가진) 빈이 컨터이너에 있는지 먼저 확인한다.


Note
이 동작은 빈의 범위에 따라 다를 수 있다. 여기서는 싱글톤에 대해서 얘기한 것이다.


Note
JavaConfig가 동작하도록 하려면 의존성 리스트에 CGLIB jar를 반드시 포함시켜야 한다는 것을 주의해라.


Note
CGLIB은 시작할 때 동적으로 기능을 추가하기 때문에 몇 가지 제약사항이 있다.

  • Configuration 클래스는 final이 될 수 없다.
  • Configuration 클래스에는 아규먼트가 없는 생성자가 있어야 한다.