[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...

레이블이 Reference Guide인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Reference Guide인 게시물을 표시합니다. 모든 게시물 표시

2017년 7월 11일 화요일

[Spring 레퍼런스] 27장 동적 언어 지원..


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

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

27. 동적 언어 지원

27.1 소개

스프링 2.0부터 스프링에서 동적 언어(JRuby 등)를 사용해서 클래스와 객체를 정의할 수 있도록 지원하고 있다. 그래서 지원하는 동적 언어로 클래스 다수를 작성해서 투명하게 스프링 컨테이너를 인스턴스화하고 객체를 의존성 주입하도록 구성할 수 있다.

현재 지원하는 동적 언어
  • JRuby 0.9 / 1.0
  • Groovy 1.0 / 1.5
  • BeanShell 2.0

바로 사용할 수 있는 동적 언어의 완전한 예제는 Section 27.4, “시나리오”에 나와 있다. Note: 스프링 2.5에서는 위에 명시한 버전만 지원한다. 특히 JRuby 1.1(상당히 많은 API 변경이 있었다)은 현재 지원하지 않는다.


왜 일부 언어만 지원하는가?
지원하는 언어는 a) 자바 엔터프라이즈 커뮤니티에서 많은 인기를 가진 언어이면서 b) 스프링 2.0 개발 기간 동안 다른 언어에 대한 요청도 없었고 c) 스프링 개발자가 가장 익숙한 언어이기 때문에 선택했다. 그래도 언어는 계속 추가할 예정이다. <자신이 좋아하는 동적 언어를 추가하고 싶다면> 언제든 스프링의 JIRA에 이슈를 올릴 수 있다.(또는 직접 구현하거나)


27.2 첫 예제

이번 장에서 동적 언어 지원을 자세히 설명하는데 자세히 살펴보기 전에 동적 언어로 정의한 빈의 예제를 가볍게 살펴보자. 처음 작성할 빈의 동적 언어는 Grooby이다.(이 예제는 스프링 테스트 슈트에서 가져왔으므로 다른 지원 언어의 예제를 보고 싶다면 소스코드를 참고해라.) 아래 예제에서 Grooby 빈으로 구현할 Messenger 인터페이스를 보자. 이 인터페이스는 일반적인 자바로 정의되어 있다. Messenger에 참조로 주입할 의존 객체는 Groovy 스크립트로 구현되었다는 것을 알지 못한다.

1
2
3
4
5
6
7
Java

package org.springframework.scripting;

public interface Messenger {
  String getMessage();
}

Messenger 인터페이스에 의존성을 가진 클래스 정의가 다음에 나와 있다.

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

package org.springframework.scripting;

public class DefaultBookingService implements BookingService {
  private Messenger messenger;

  public void setMessenger(Messenger messenger) {
    this.messenger = messenger;
  }

  public void processBooking() {
    // 주입된 Messenger 객체를 사용한다...
  }
}

다음은 Groovy로 작성한 Messenger 인터페이스의 구현체이다.

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

// from the file 'Messenger.groovy'
package org.springframework.scripting.groovy;

// 구현할 Messenger 인터페이스(Java로 작성된)를 임포트한다
import org.springframework.scripting.Messenger

// Groovy로 구현을 정의한다
class GroovyMessenger implements Messenger {
  String message
}

마지막으로 DefaultBookingService 클래스의 인스턴스에 Groovy로 정의된 Messenger 구현체를 주입할 때 영향을 주는 빈 정의가 다음에 나와 있다.


Note
동적 언어로 작성한(dynamic-language-backed) 빈을 정의하는 커스텀 동적 언어 태그를 사용하려면 스프링 XML 설정파일 상단에 XML 스키마를 선언해야 하고 IoC 컨테이너로 스프링 ApplicationContext 구현체를 사용하도록 해야 한다. 동적 언어로 작성한 빈을 일반적인 BeanFactory 구현체와 사용하는 것도 지원하지만, 이 동작을 하는 스프링 내부의 시스템을 관리해야 한다. 스키마에 기반을 둔 구성에 대한 자세한 내용은 Appendix C, XML Schema-based configuration를 참고해라.

주입된 Messenger 인스턴스가 Messenger 인스턴스이므로 bookingService 빈(DefaultBookingService)은 private messenger 멤버 변수를 평소처럼 사용할 수 있다. 딱히 특별할 것도 없이 그냥 일반적인 Java와 Groovy다. 이상적으로는 위의 XML 스니펫만으로 이해할 수 있어야 하지만 이해하지 못하더라도 크게 걱정할 필요는 없다. 왜 위와 같은 설정을 사용했는지에 대한 자세한 내용은 이어서 나올 것이다.


27.3 동적 언어로 작성된 빈 정의

이번 장에서 스프링이 관리하는 빈을 지원하는 동적언어로 정의하는 방법을 설명한다. 이번 장에서 동적 언어의 문법을 설명하지는 않는다. 예를 들어 애플리케이션에서 특정 클래스를 Groovy로 작성하려고 하면 이미 Groovy를 알고 있다고 전제한다. 동적 언어에 대해서 자세히 알고 싶다면 이번 장 마지막의 Section 27.6, “추가 자료”를 참고해라.


27.3.1 공통 개념

동적 언어에 기반을 둔 빈을 사용하는 과정은 다음과 같다.
  1. 동적 언어 소스코드의 테스트를 작성한다.(당연히)
  2. 그 다음 동적 언어 소스 코드 자체를 작성한다 :)
  3. XML 설정에 적절한 <lang:language/> 요소를 사용해서 동적 언어에 기반을 둔 빈을 정의한다. (물론 스프링 API를 사용해서 프로그래밍적으로 이러한 빈을 정의할 수도 있다. - 이번 장에서는 이러한 방식의 고급 설정을 다루지 않으므로 어떻게 정의하는지 알고 싶다면 소스 코드를 참고해야 하지만) 이는 반복되는 과정이다. 동적 언어 소스 파일마다 최소 하나의 빈 정의가 필요할 것이다.(물론 여러 빈 정의가 같은 동적 언어 소스 파일을 참조할 수 있지만)
처음 두 과정(동적 언어 소스파일을 테스트하고 작성)은 이 장의 범위를 넘어간다. 사용하는 동적 언어의 명세와 레퍼런스 문서를 참고해서 동적 언어 소스를 작성해라. 스프링의 동적 언어 지원은 동적 언어 소스파일의 내용을 이해한다는 (약간의) 가정을 하고 있으므로 이번 장을 계속 읽으려면 동적 언어의 문서를 먼저 보아야 할 것이다.

27.3.1.1 <lang:language/> 요소

마지막 단계는 동적 언어에 기반을 둬서 사용할 빈 정의를 정의하는 것이다.(일반적인 JavaBean 설정과 다르지 않다.) 하지만 컨테이너가 인스턴스화하고 구성할 클래스의 정규화된 클래스 명을 지정하는 대신 동적 언어에 기반을 둔 빈을 정의할 때는 <lang:language/> 요소를 사용한다.
지원하는 각 언어는 각각 <lang:language/> 요소를 가진다.
  • <lang:jruby/> (JRuby)
  • <lang:groovy/> (Groovy)
  • <lang:bsh/> (BeanShell)
설정에 사용할 수 있는 속성과 자식 요소는 빈을 정의한 언어에 따라 다르다. (언어에 특화된 부분은 아래에 나와있다.)


XML 스키마
이번 장의 모든 설정 예제는 스프링 2.0에서 새로 추가된 XML 스키마를 사용한다. XML 스키마의 사용하기 전에 예전 방식으로 스프링 XML 파일을 DTD 기반으로 유효성 검사하는 것이 가능하지만 이렇게 하면 요소의 편리함을 얻지 못할 것이다. XML 스키마 기반의 밸리데이션이 필요없는 예전 방식의 설정 예제는 스프링 테스트 슈트를 참고해라.(꽤 복잡하고 사용하는 스프링 구현이 모두 나와 있다.)


27.3.1.2 갱신 가능한(Refreshable) 빈

스프링의 동적 언어 지원에서 가장 강력한 장점(유일한 장점은 아니다.) 중 하나는 '갱신 가능한 빈' 기능이다.
갱신 가능한 빈은 동적 언어로 작성한 빈에 약간의 설정을 추가한 것이다. 동적 언어로 작성한 빈은 사용하는 소스파일 리소스의 변경사항을 감시할 수 있어서 소스파일이 변경되었을 때 재로드할 수 있다.(예를 들어 개발자가 파일시스템에서 파일을 수정하고 저장했을 때) 이를 통해 개발자는 애플리케이션에 다수의 동적 언어 소스파일을 포함해서 배포하고 스프링 컨테이너가 동적 언어 소스파일로 빈을 생성하도록 구성하고(사용하는 메커니즘은 이번 장에서 설명한다.) 나중에 요구사항 변경이 있거나 다른 외부요소가 영향을 주거나 동적 언어 소스파일을 변경했을 때 변경사항을 빈에 적용할 수 있다. 운영하는 애플리케이션을 종료할 필요가 없다.(웹 애플리케이션인 경우에는 재배포할 필요가 없다) 이렇게 수정된 동적 언어로 작성된 빈은 변경된 동적 언어 소스파일에서 새로운 상태와 로직을 가져와서 적용할 것이다.


Note
이 기능은 기본적으로는 비활성화되어 있다.

갱신 가능한 빈을 사용하기가 얼마나 쉬운지 예제를 살펴보자. 갱신 가능한 빈 기능을 활성화하려면 빈 정의에서 <lang:language/> 요소에 딱 하나의 속성을 추가하면 된다. 이번 장 앞부분의 예제를 다시 사용해서 갱신 가능한 빈을 사용하는 스프링 XML 구성이 다음에 나와 있다.

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

<beans>
  
  <lang:groovy id="messenger"
      refresh-check-delay="5000" <!-- 5초마다 갱신을 확인하도록 설정한다 -->
      script-source="classpath:Messenger.groovy">
    <lang:property name="message" value="I Can Do The Frug" />
  </lang:groovy>

  <bean id="bookingService" class="x.y.DefaultBookingService">
    <property name="messenger" ref="messenger" />
  </bean>
</beans>

개발자가 해야 하는 것은 이것이 전부다. 'messenger' 빈 정의의 'refresh-check-delay' 속성은 사용하는 동적 언어 소스파일의 변경사항으로 빈을 갱신할 밀리 초를 정의한다. 'refresh-check-delay' 속성에 음수를 할당하면 갱신 동작을 끌 수 있다. 기본적으로 갱신 기능은 비활성화되어 있다는 점을 유념해라. 갱신기능이 필요 없다면 속성을 정의하지 않으면 된다. 다음 애플리케이션을 실행하면 갱신 가능한(refreshable) 빈 기능을 사용할 수 있다. 다음 코드에서 '실행을 멈추도록 하는' 불필요한 부분은 크게 신경 쓰지 마라. 다음 코드에서 개발자가 동적 언어 소스파일을 열어서 수정하는 동안 프로그램의 실행이 멈춰서 프로그램이 다시 실행될 때 동적 언어를 사용한 빈이 갱신되도록 System.in.read() 호출이 이곳에만 존재한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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("beans.xml");
    Messenger messenger = (Messenger) ctx.getBean("messenger");
    System.out.println(messenger.getMessage());
    // 소스파일을 열어서 수정하는 동안 실행을 멈춘다...
    System.in.read();
    System.out.println(messenger.getMessage());
  }
}

이 예제의 목적에 따라 Messenger 구현체의 getMessage() 메서드에 대한 모든 호출은 메시지를 따옴표로 감싸야 한다고 가정해 보자. 프로그램의 실행이 멈춰있을 때 Messenger.groovy 소스파일을 아래와 같이 수정했다.

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

package org.springframework.scripting

class GroovyMessenger implements Messenger {
  private String message = "Bingo"

  public String getMessage() {
    // 메시지를 따옴표로 감싸도록 구현체를 변경한다
    return "'" + this.message + "'"
  }

  public void setMessage(String message) {
    this.message = message
  }
}

프로그램 실행했을 때 입력을 멈추기 전에 출력은 I Can Do The Frug가 될 것이다. 소스파일을 변경하고 저장한 후에는 프로그램이 다시 실행되고 동적 언어로 작성한 Messenger 구현체에서 getMessage() 메서드를 호출하면 'I Can Do The Frug'가 될 것이다. (따옴표가 추가되었다.) 'refresh-check-delay' 값의 범위내에서 변경이 되더라도 스크립트의 변경이 리프레시를 발생시키는 것이 아니라는 점을 이해하는 것이 중요하다. 동적 언어로 작성한 빈의 메서드를 호출할 때까지 스크립트의 변경이 실제 '적용되지' 않는다는 점을 이해하는 것도 마찬가지로 중요하다. 동적 언어로 작성한 빈에서 메서드 호출이 일어났을 때만 사용하는 스크립트 소스가 변경되었는지 확인한다. 스크립트 갱신과 관련한 모든 예외는 호출코드로 전파하는 fatal 예외가 된다. 앞에서 설명한 갱신 가능한 빈의 동작은 <lang:inline-script/> 요소 표기로 정의한 동적언어 소스 파일에는 적용되지 않는다. (Section 27.3.1.3, “인라인 동적 언어 소스 파일” 참고) 또한 변경이 일어난 소스파일을 실제로 탐지할 수 있는 경우에만 빈에 적용된다. 예를 들면 파일시스템에서 동적언어 소스파일의 최종 수정일을 확인하는 코드를 사용하는 등이다.


27.3.1.3 인라인 동적 언어 소스 파일

동적 언어 지원은 스프링 빈 정의에 직접 삽입한 동적 언어 소스파일 기능도 제공한다. 더 자세히 얘기하면 <lang:inline-script/> 요소로 스프링 설정파일에서 바로 동적 언어 소스를 정의할 수 있다. 예제를 보면 인라인 스크립트 기능을 확실히 알 수 있을 것이다.

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

<lang:groovy id="messenger">
  <lang:inline-script>
package org.springframework.scripting.groovy;

import org.springframework.scripting.Messenger

class GroovyMessenger implements Messenger {

    String message
}
  </lang:inline-script>
  <lang:property name="message" value="I Can Do The Frug" />
</lang:groovy>

스프링 설정파일에 동적 언어 소스를 정의하는 좋은 방법인가에 대한 부분은 제쳐놓고 <lang:inline-script/> 요소는 일부 시나리오에서 유용할 수 있다. 예를 들어 스프링 MVC Controller에 스프링 Validator 구현체를 빠르게 추가하고자 할 때 인라인 소스가 유용한 경우이다. (예시는 Section 27.4.2, “스크립트로 작성한 밸리데이터(Validator)”를 참고해라.) inline: 문법으로 스프링 XML 설정파일에서 JRuby로 작성한 빈 소스를 바로 정의하는 예제가 다음에 나와 있다. ('<' 문자를 표시하려고 <를 사용했다. 이러면 <![CDATA[]]>로 인라인 소스를 감싸는 것이 더 좋다.)

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

<lang:jruby id="messenger" script-interfaces="org.springframework.scripting.Messenger">
  <lang:inline-script>
require 'java'

include_class 'org.springframework.scripting.Messenger'

class RubyMessenger &lt; Messenger
  def setMessage(message)
    @@message = message
  end

  def getMessage
    @@message
  end
end
  </lang:inline-script>
  <lang:property name="message" value="Hello World!" />
</lang:jruby>

27.3.1.4 동적 언어로 작성한 빈의 컨텍스트의 생성자 주입 이해하기

스프링 동적 언어 지원과 관련해서 알아야 할 아주 중요한 내용이 있다. 즉, (현재는) 동적 언어로 작성한 빈에 생성자 인자를 제공할 수 없다.(그래서 동적 언어 빈에는 생성자 주입을 사용할 수 없다.) 생성자와 프로퍼티에 관련한 이 특수한 처리를 100% 이해하기 위해 동작하지 않는 다음 코드와 설정을 보자.

 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

// from the file 'Messenger.groovy'
package org.springframework.scripting.groovy;

import org.springframework.scripting.Messenger

class GroovyMessenger implements Messenger {
  GroovyMessenger() {}

  // 이 생성자는 생성자 주입을 사용할 수 없다
  GroovyMessenger(String message) {
  this.message = message;
  }

  String message

  String anotherMessage
}

Xml

<lang:groovy id="badMessenger"
  script-source="classpath:Messenger.groovy">

  <!-- 이다음의 생성자 인자는 GroovyMessenger에 주입되지 *않는다* -->
  <!-- 사실 이는 스키마를 따르도록 하지도 않는다 -->
  <constructor-arg value="이는 동작하지 *않을 것이다*" />

  <!-- only 프로퍼티 값은 동적 언어로 작성한 객체에 주입된다 -->
  <lang:property name="anotherMessage" value="Passed straight through to the dynamic-language-backed object" />
</lang>

개발자들은 대부분 setter 주입을 선호하므로 실제로 이 제약사항이 처음 느낌만큼 크게 문제 되지는 않는다.(이에 대한 논의는 나중을 위해 남겨두자.)


27.3.2 JRuby 빈

JRuby 홈페이지에서...
“ JRuby는 Ruby 프로그래밍 언어를 100% 순수히 자바로만 구현한 구현체이다. ”
선택권을 제공하는 스프링의 철학에 따라 스프링의 동적 언어도 JRuby 언어로 정의한 빈을 지원한다. JRuby 언어는 아주 직관적인 Ruby 언어에 기반을 두고 있어서 인라인 정규표현식, 블락(클로저)과 특정 도메인 문제를 훨씬 쉽게 개발할 수 있는 다른 기능들을 모두 지원한다.
스프링의 JRuby 동적 언어 지원 구현체는 무슨 일이 이뤄지는지에 관심을 둔다. 요소의 'script-interfaces' 속성값에 지정한 모든 인터페이스를 구현하는 JDK 동적 프락시를 스프링이 생성한다. (그래서 'script-interfaces' 속성값에 최소 하나의 인터페이스를 반드시 해야 한다. 이를 통해 JRuby기반의 빈을 사용할 때 프로그램이 연결한다.) JRuby로 작성한 빈을 사용한 완전한 예제를 보자. 이번 장 앞부분에서 정의한 Messenger 인터페이스의 JRuby 구현체가 다음에 나와 있다.(보기 쉽게 아래 다시 작성했다.)


JRuby 라이브러리 의존성
스프링의 JRuby 스크립트 지원을 사용하려면 애플리케이션 클래스패스에 다음 라이브러리가 필요하다.(명시된 버전은 JRuby 스크립트 지원 개발 시에 스프링 팀이 사용한 버전일 뿐이다. 해당 라이브러리의 다른 버전도 아마 잘 동작할 것이다.)

  • jruby.jar
  • cglib-nodep-2.1_3.jar


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.springframework.scripting;

public interface Messenger {
  String getMessage();
}

require 'java'

class RubyMessenger
  include org.springframework.scripting.Messenger

  def setMessage(message)
    @@message = message
  end

  def getMessage
    @@message
  end
end

#  마지막 라인은 핵심은 아니다(하지만 아래를 참고해라)
RubyMessenger.new


다음은 RubyMessenger JRuby 빈의 인스턴스를 정의하는 스프링 XML이다.

1
2
3
4
5
6
7
8
Xml

<lang:jruby id="messageService"
    script-interfaces="org.springframework.scripting.Messenger"
    script-source="classpath:RubyMessenger.rb">

  <lang:property name="message" value="Hello World!" />
</lang:jruby>

JRuby 소스의 마지막 라인을 봐라 ('RubyMessenger.new') 스프링의 동적 언어 지원에서 JRuby를 사용하는 경우 JRuby 소스의 실행 결과인 동적 언어로 작성한 빈으로 사용하려는 JRuby 클래스를 인스턴스화하고 새로운 인스턴스를 반환하는 것이 좋다. 이는 다음과 같이 소스파일의 마지막 라인으로 JRuby 클래스의 새로운 인스턴스를 인스턴스화해서 할 수 있다.


require 'java'

include_class 'org.springframework.scripting.Messenger'

# 클래스 정의는 앞과 같다...

# RubyMessenger 클래스의 새로운 인스턴스를 인스턴스화하고 반환한다.
RubyMessenger.new

이 부분을 빠뜨린다고 큰 문제가 생기는 것은 아니지만, 스프링이 인스턴스화할 클래스를 찾으려고 JRuby 클래스의 타입 표현으로 세밀하게(리플렉션하게) 조사하게 된다. 크게 보면 이는 너무 빨라서 느끼지 못할 정도이지만 위 JRuby 스크립트의 마지막 한 라인을 둠으로써 쉽게 피할 수 있는 부분이다. 해당 라인을 적지 않거나 스프링이 스크립트에서 인스턴스화할 JRuby 클래스를 찾을 수 없다면 JRuby 인터프리터가 소스를 실행하자마자 불투명한 ScriptCompilationException가 바로 던져질 것이다. 이를 식별하는 핵심 문구로 예외를 발생시킨 이유를 즉시 찾을 수 있다. (그래서 스프링 컨테이너가 동적 언어로 작성한 빈을 생성할 때 예외를 던지고 해당 스택트레이스에 다음 문구가 있다면 이슈를 발견하고 쉽게 수정할 수 있을 것이다.) org.springframework.scripting.ScriptCompilationException: Compilation of JRuby script returned '' 이를 수정하려면 JRruby 동적 언어로 작성한 빈으로 노출할 클래스의 새로운 인스턴스를 인스턴스화 하면 된다(위에서 이미 봤다.) JRuby 스크립트에서 원하는 만큼 많은 클래스와 객체를 정의할 수도 있다. 중요한 것은 전체적으로 하나의 객체(스프링이 설정할)를 반드시 반환해야 한다는 것이다. JRuby로 작성한 빈을 사용하는 예시들을 보려면 Section 27.4, “시나리오”를 참고해라.


27.3.3 Groovy 빈

Groovy 홈페이지에서...
"Groovy는 Java 2 플랫폼에서 동작하는 애자일한 동적 언어로 Python, Ruby, Smalltalk같은 언어에서 사람들이 무척 좋아하던 많은 기능을 자바 개발자들이 자바 문법으로 사용하도록 한다."
이번 장을 처음부터 읽었다면 Groovy 동적 언어로 작성한 빈의 예제를 이미 보았을 것이다. 여기서는 다른 예제를 보자.(스프링 테스트 슈트의 예제를 다시 사용한다.)


Groovy 라이브러리 의존성
스프링의 Groovy 스크립트 지원을 사용하려면 애플리케이션 클래스패스에 다음 라이브러리가 있어야 한다.

  • groovy-1.5.5.jar
  • asm-2.2.2.jar
  • antlr-2.7.6.jar


1
2
3
4
5
6
7
Java

package org.springframework.scripting;

public interface Calculator {
  int add(int x, int y);
}

Groovy에서 Calculator 인터페이스의 구현체가 다음에 나와 있다.

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

// 'calculator.groovy' 파일
package org.springframework.scripting.groovy

class GroovyCalculator implements Calculator {
  int add(int x, int y) {
    x + y
  }
}

Xml

<-- 'beans.xml' 파일 -->
<beans>
  <lang:groovy id="calculator" script-source="classpath:calculator.groovy"/>
</beans>

마지막으로 위의 설정을 실행해볼 작은 애플리케이션이다.

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

package org.springframework.scripting;

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

public class Main {
  public static void Main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    Calculator calc = (Calculator) ctx.getBean("calculator");
    System.out.println(calc.add(2, 8));
  }
}

위 프로그램을 실행하면 (놀랍지는 않지만) 10이 나온다. (예제가 흥미로운가? 이 예제의 의도는 개념을 설명하려는 것이다. 더 복잡한 예제의 동적 언어 쇼케이스 프로젝트를 참고하고 이 장 뒷부분에서 Section 27.4, “시나리오”를 살펴본다.)
Groovy 소스파일마다 하나 이상의 클래스를 정의하지 않는 것이 중요하다. 이는 Groovy에서 완전히 유효하지만 (분명히)좋지 않은 방법이다. 일관성을 위해서 소스파일마다 하나의 (public) 클래스를 작성하는 표준 자바 관례를 따라야 한다.(저자의 의견)

27.3.3.1 콜백으로 Groovy 객체 커스터마이징하기

GroovyObjectCustomizer 인터페이스는 Groovy로 작성한 빈을 생성하는 과정에 로틱을 추가할 수 있는 훅(hook)을 제공하는 콜백이다. 예를 들어 이 인터페이스의 구현체로 필요한 초기화 메서드를 호출하거나 프로퍼티의 기본값을 설정하거나 커스텀 MetaClass를 지정할 수 있다.

1
2
3
4
5
Java

public interface GroovyObjectCustomizer {
  void customize(GroovyObject goo);
}

스프링 프레임워크는 Groovy로 작성한 빈의 인스턴스를 인스턴스화한 뒤 빈이 정의되었다면 지정한 GroovyObjectCustomizer에 생성한 GroovyObject를 전달할 것이다. 제공받은 GroovyObject 참조로 무엇이든 할 수 있다. 대부분의 사람들이 이 콜백으로 커스텀 MetaClass를 설정할 것이고 이에 대한 예제가 아래 나와 있다.

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

public final class SimpleMethodTracingCustomizer implements GroovyObjectCustomizer {
  public void customize(GroovyObject goo) {
    DelegatingMetaClass metaClass = new DelegatingMetaClass(goo.getMetaClass()) {

     public Object invokeMethod(Object object, String methodName, Object[] arguments) {
      System.out.println("Invoking '" + methodName + "'.");
      return super.invokeMethod(object, methodName, arguments);
     }
    };
    metaClass.initialize();
    goo.setMetaClass(metaClass);
  }
}

Groovy의 메타프로그래밍에 대한 논의는 스프링 레퍼런스 매뉴얼의 범위를 벗어난다. Groovy 레퍼런스 메뉴얼의 관련 부분을 참고하거나 온라인에서 검색해 봐라. 메타 프로그래밍에 대한 많은 글을 볼 수 있을 것이다. 사실 스프링 2.0 네임스페이스 지원을 사용하고 있다면 GroovyObjectCustomizer를 사용하기는 쉽다.

1
2
3
4
5
6
7
8
Xml

<!-- 다른 빈과 마찬가지로 GroovyObjectCustomizer를 정의한다 -->
<bean id="tracingCustomizer" class="example.SimpleMethodTracingCustomizer" />
  <!-- ... 그리고 'customizer-ref' 속성으로 원하는 Groovy 빈에 연결한다 -->
  <lang:groovy id="calculator"
    script-source="classpath:org/springframework/scripting/groovy/Calculator.groovy"
    customizer-ref="tracingCustomizer" />

스프링 2.0 네임스페이스 지원을 사용하고 있지 않더라도 GroovyObjectCustomizer 기능을 사용할 수 있다.

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

<bean id="calculator" class="org.springframework.scripting.groovy.GroovyScriptFactory">
  <constructor-arg value="classpath:org/springframework/scripting/groovy/Calculator.groovy"/>
  <!-- GroovyObjectCustomizer를 정의한다 (내부 빈으로) -->
  <constructor-arg>
    <bean id="tracingCustomizer" class="example.SimpleMethodTracingCustomizer" />
  </constructor-arg>
</bean>

<bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/>

27.3.4 BeanShell 빈

BeanShell 홈페이지에서...
"BeanShell은 자바로 작성된 동적 언어 기능을 가진 자바소스 인터프리터로 작고 무료고 내장할 수 있다. BeanShell은 동적으로 표준 자바 문법을 실행하고 Perl이나 JavaScript처럼 느슨한 타입, 커맨드, 메서드 클로저같은 일반적으로 편리한 스크립트 기능으로 자바 문법을 확장한다."
Groovy와는 달리 BeanShell로 작성한 빈 정의를 사용하려면 (약간의) 추가적인 설정이 필요하다. 스프링의 BeanShell 동적 언어 지원 구현체에는 흥미로운 동작이 일어난다. 요소의 'script-interfaces' 속성값으로 지정한 인터페이스를 구현하는 JDK 동적 프락시를 스프링이 생성한다.(그래서 'script-interfaces' 속성에 최소한 하나 이상의 인터페이스를 반드시 제공해야 하고 BeanShell로 작성한 빈을 사용할 때 인터페이스를 (적절히) 프로그래밍해야 한다.) 즉 BeanShell로 작성한 객체의 모든 메서드 호출은 JDK 동적 프락시 호출 메커니즘을 사용한다. 이번 장 초반에서 정의한 Messenger 인터페이스(편의상 아래 다시 적어놨다.)를 구현한 BeanShell로 작성한 빈을 사용하는 전체 예제를 보자.


BeanShell 라이브러리 의존성
스프링의 BeanShell 스크립트 지원을 사용하려면 애플리케이션 클래스 패스에 다음 라이브러리가 있어야 한다.

  • bsh-2.0b4.jar
  • cglib-nodep-2.1_3.jar


1
2
3
4
5
6
7
Java

package org.springframework.scripting;

public interface Messenger {
  String getMessage();
}

다음은 Messenger 인터페이스의 BeanShell '구현체'이다. (이 용어는 여기서는 약간 막연하다)

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

String message;

String getMessage() {
  return message;
}

void setMessage(String aMessage) {
  message = aMessage;
}

다음은 위 '클래스'의 '인스턴스'를 정의한 스프링 XML이다.(여기서도 아주 막연한 용어이다.)

1
2
3
4
5
6
7
Xml

<lang:bsh id="messageService" script-source="classpath:BshMessenger.bsh"
  script-interfaces="org.springframework.scripting.Messenger">

  <lang:property name="message" value="Hello World!" />
</lang:bsh>

BeanShell로 작성한 빈을 사용하는 다른 시나리오를 보려면 Section 27.4, “시나리오”를 참고해라.


27.4 시나리오

스크립트 언어로 스프링이 관리하는 빈을 작성하는 것이 유용한 시나리오는 물론 많고 다양하다. 이번 부분에서는 스프링에서 동적 언어 지원으로 가능한 두 가지 유즈케이스를 설명한다.

27.4.1 스크립트로 작성한 스프링 MVC 컨트롤러

동적 언어로 작성한 빈을 사용해서 이득이 되는 클래스 중 하나가 스프링 MVC 컨트롤러다. 순수한 스프링 MVC 애플리케이션에서 웹 애플리케이션의 탐색 가능한 흐름은 아주 큰 범위이고 스프링 MVC 컨트롤러에서 은닉화된 코드로 결정된다. 웹 애플리케이션의 탐색 가능한 흐름과 다른 표현 계층의 로직은 이슈를 처리하거나 사업적 요구사항의 변경에 따라 갱신되어야 한다. 하나 이상의 동적 언어 소스 파일을 수정하고 실행 중인 애플리케이션의 상태에 바로 적용되는 변경사항을 보면 필요한 변경사항을 적용하기가 훨씬 쉬울 것이다.
스프링 같은 프로젝트가 지지하는 경량 아키텍처 모델을 생각해 보자. 보통은 정말 얇은 표현 계층을 가지면서 애플리케이션의 많은 비즈니스 로직을 도메인 계층과 서비스 계층 클래스에 담으려고 할 것이다. 동적 언어로 작성한 빈으로 스프링 MVC 컨트롤러를 작성하면 텍스트 파일을 수정하고 저장하기만 하면 표현 계층의 로직을 변경할 수 있을 것이다. 이러한 동적 언어 소스파일의 변경사항은 해당 파일로 작성한 빈에 자동으로 반영될 것이다. (설정에 따라 다를 수 있다.)


Note
동적 언어로 작성한 빈의 변경사항을 자동으로 '선택'해서 적용하려면 '갱신 가능한 빈'기능을 활성화해야 한다. 이 기능에 대한 전체 내용은 Section 27.3.1.2, “갱신 가능한(Refreshable) 빈”를 참고해라.

Groovy 동적 언어로 구현한 org.springframework.web.servlet.mvc.Controller의 예제가 아래 나와 있다.

 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
Java

// from the file '/WEB-INF/groovy/FortuneController.groovy'
package org.springframework.showcase.fortune.web

import org.springframework.showcase.fortune.service.FortuneService
import org.springframework.showcase.fortune.domain.Fortune
import org.springframework.web.servlet.ModelAndView
import org.springframework.web.servlet.mvc.Controller

import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

class FortuneController implements Controller {

  @Property FortuneService fortuneService

  ModelAndView handleRequest(
      HttpServletRequest request, HttpServletResponse httpServletResponse) {

    return new ModelAndView("tell", "fortune", this.fortuneService.tellFortune())
  }
}

Xml

<lang:groovy id="fortune"
     refresh-check-delay="3000"
     script-source="/WEB-INF/groovy/FortuneController.groovy">
  <lang:property name="fortuneService" ref="fortuneService"/>
</lang:groovy>


27.4.2 스크립트로 작성한 밸리데이터(Validator)

동적 언어로 작성한 빈의 유연성을 통해 스프링 애플리케이션 개발에서 이점을 취할 수 있는 또 하나의 부분이 유효성 검사(validation)다. 자바와 비교해서 타입이 느슨한 동적 언어(인라인 정규표현식도 지원한다)를 사용해서 복잡한 유효성 검사 로직을 표현하기가 더 쉬울 것이다. 다시 말하지만 동적 언어로 작성한 빈으로 밸리데이터를 개발하면 텍스트파일을 수정하고 저장하는 것만으로 유효성 검사 로직을 변경할 수 있다. 모든 변경사항이 (설정에 따라 다르지만) 자동으로 운영 중인 애플리케이션에 반영될 것이고 애플리케이션을 재시작할 필요가 없다.


Note
동적 언어로 작성한 빈의 변경사항을 자동으로 '적용'하려면 갱신 가능한 빈 기능을 활성화해야 할 것이다. 이 기능에 대한 자세한 내용은 Section 27.3.1.2, “갱신 가능한(Refreshable) 빈”를 참고해라.


Groovy 동적 언어로 구현한 스프링 org.springframework.validation.Validator의 예제가 다음에 나와 있다. (Validator 인터페이스에 대한 내용은 Section 6.2, “스프링의 Validator 인터페이스를 사용하는 유효성검사”를 참고해라.)

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

import org.springframework.validation.Validator
import org.springframework.validation.Errors
import org.springframework.beans.TestBean

class TestBeanValidator implements Validator {
  boolean supports(Class clazz) {
    return TestBean.class.isAssignableFrom(clazz)
  }

  void validate(Object bean, Errors errors) {
    if(bean.name?.trim()?.size() > 0) {
      return
    }
    errors.reject("whitespace", "Cannot be composed wholly of whitespace.")
  }
}


27.5 그 밖의 내용

이 마지막 장에서는 동적 언어 지원에 대한 잡다한 내용을 다룬다.

27.5.1 AOP - 스크립트로 작성한 빈의 어드바이스

스크립트로 작성한 빈을 어드바이스하도록 스프링 AOP 프레임워크를 사용할 수 있다. 사실 스프링 AOP 프레임워크는 어드바이스하는 빈이 스크립트로 작성한 빈이라는 것을 알지 못하므로 사용할 모든 AOP 사용 사례와 기능은 스크립트로 작성한 빈과도 잘 동작할 것이다. 스크립트로 작성한 빈을 어드바이스할 때 알아두어야 할 (소소한)내용이 딱 하나 있는데 클래스 기반의 프락시는 사용할 수 없고 인터페이스 기반의 프록시를 반드시 사용해야 한다는 것이다. 물론 스크립트로 작성한 빈을 어드바이스만 할 수 있는 것은 아니고 지원되는 동적 언어로 관점 자체를 작성하고 다른 스프링 빈을 어드바이스할 때 스크립트로 작성한 빈도 사용할 수 있다. 이는 동적 언어 지원에서 정말로 진보된 사용방법이다.


27.5.2 범위 제어

아주 명확하게 보이지는 않더라도 스크립트로 작성한 빈도 다른 빈처럼 범위를 줄 수 있다. 여러 <lang:language/> 요소의 scope 속성으로 다른 일반적인 빈처럼 해당 스크립트성 빈의 범위를 제어할 수 있다. (기본 범위는 다른 '일반적인' 빈과 마찬가지로 singleton이다.) Grooby 빈을 정의할 때 scope 속성을 사용해서 prototype 범위로 정의한 예제가 다음에 나와 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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:Messenger.groovy" scope="prototype">
    <lang:property name="message" value="I Can Do The RoboCop" />
  </lang:groovy>

  <bean id="bookingService" class="x.y.DefaultBookingService">
    <property name="messenger" ref="messenger" />
  </bean>
</beans>

스프링 프레임워크의 범위에 대한 내용은 Chapter 4, IoC 컨테이너의 Section 4.5, “빈(Bean) 범위”를 참고해라.


27.6 추가 자료

이번 장에서 설명한 여러 동적 언어에 대한 추가 자료를 아래 링크에서 볼 수 있다.
스프링 커뮤니티에서 적극적인 일부 회원들이 이번 장에서 다룬 것 외의 추가적인 동적언어를 다수 추가했다. 스프링 배포판에서 지원하는 언어 목록에 언어를 추가하는 공헌을 할 수 있지만 Spring Modules project에서 선호하는 스크립트를 찾을 수 있기를 바란다.

2017년 5월 12일 금요일

[Spring 레퍼런스] 26장 태스크(Task) 실행과 스케줄링..


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

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

26. 태스크(Task) 실행과 스케줄링

26.1 소개

스프링 프레임워크는 TaskExecutor 인터페이스와 TaskScheduler 인터페이스로 태스크의 비동기 시행과 스케줄링에 대한 추상화를 각각 제공한다. 스프링은 이 인터페이스를 사용해서 애플리케이션 서버 환경내에서 CommonJ로 위임하거나 스레드 풀을 지원하는 구현체도 제공하고 있다. 그래서 공통 인터페이스를 사용하는 이러한 구현체들을 사용해서 Java SE5, Java SE 6, Java EE 환경 간의 차이점을 추상화해버린다.
스프링은 JDK 1.3부터 추가된 Timer와 Quartz 스케줄러(http://www.opensymphony.com/quartz/)를 사용한 스케줄링을 지원하는 통합 클래스도 제공한다. 이 두가지 스케줄러 모두 FactoryBean에 Timer나 Trigger에 대한 참조를 각각 전달해서 구성한다. 또한, 이미 존재하는 대상 객체의 메서드를 호출할 수 있는(일반적인 MethodInvokingFactoryBean 작업과 유사하다.) Quartz 스케줄러와 Timer에 대한 편의 클래스도 사용할 수 있다.

26.2 스프링 TaskExecutor 추상화

스프링 2.0에는 executor를 다루는 새로운 추상화가 도입되었다. executor는 스레드 풀의 개념에 대해 Java 5에서 도입된 개념이다. 사용하는 구현체가 실제로 풀(pool)이라는 보장이 없기 때문에 "executor"라는 이름을 사용한다. executor는 싱글 스레드가 될 수도 있고 동기화가 될 수도 있다. 스프링의 추상화는 Java SE 1.4와 Java SE 5, Java EE 환경 사이의 자세한 구현부를 숨겨준다.
스프링의 TaskExecutor 인터페이스는 java.util.concurrent.Executor 인터페이스와 같다. 사실 이 인터페이스가 존재하는 이유는 스레드 풀을 사용할 때 Java 5에 대한 요구사항을 추상화하기 위해서다. TaskExecutor 인터페이스는 스레드 풀의 의미(semantic)와 구성에 기반을 둬서 실행하려고 태스크(task)를 받는 execute(Runnable task) 메서드를 가진다.
TaskExecutor는 원래 스레드 풀에 대한 추상화가 필요한 곳에 다른 스프링 컴포넌트를 제공하기 위해 만들어진 것이다. ApplicationEventMulticaster나 JSM의 AbstractMessageListenerContainer나 Quartz 통합 같은 컴포넌트들은 모두 스레드 풀에 TaskExecutor 추상화를 사용한다. 하지만 빈(bean)에서 스레드 풀의 동작이 필요하다면 자신의 요구사항에 맞게 이 추상화를 사용할 수 있다.

26.2.1 TaskExecutor의 종류

스프링 배포본에 포함된 다수의 TaskExecutor 구현체들이 있다. 아마 직접 구현할 일은 거의 없을 것이다.
  • SimpleAsyncTaskExecutor

    이 구현에는 어떤 스레드도 재사용하지 않고 호출마다 새로운 스레드를 시작한다. 하지만 이 구현체는 동시접속 제한(concurrency limit)을 지원해서 제한 수가 넘어서면 빈 공간이 생길 때까지 모든 요청을 막을 것이다. 실제 풀링(pooling)을 원한다면 뒷부분을 더 봐야 한다.
  • SyncTaskExecutor

    이 구현체는 호출을 비동기적으로 실행하지 않고 대신, 각 호출이 호출 스레드에 추가된다. 간단한 테스트 케이스처럼 멀티스레드가 필요하지 않은 상황에서 주로 사용한다.
  • ConcurrentTaskExecutor

    이 구현체는 Java 5 java.util.concurrent.Executor의 래퍼다. 또 다른 대안은 빈 프로퍼티로 Executor 설정 파라미터를 노출하는 ThreadPoolTaskExecutor다. ConcurrentTaskExecutor를 사용해야 하는 경우는 드물지만 ThreadPoolTaskExecutor가 원하는 만큼 안정적이지 않다면 ConcurrentTaskExecutor를 대신 사용할 수 있다.
  • SimpleThreadPoolTaskExecutor

    이 구현체는 실제로 Quartz SimpleThreadPool의 하위클래스로 스프링의 생명주기 콜백을 받는다. 이는 Quartz와 Quartz가 아닌 컴포넌트 간에 공유해야 하는 스레드 풀이 있는 경우에 보통 사용한다.
  • ThreadPoolTaskExecutor

    이 구현체는 자바 5 환경에서만 사용할 수 있지만, 자바 5 환경에서는 가장 일반적으로 사용한다. 이 구현체는 java.util.concurrent.ThreadPoolExecutor를 구성하는 빈 프로퍼티를 노출하고 이를 TaskExecutor로 감싼다. ScheduledThreadPoolExecutor같은 고급 기능이 필요하다면 ConcurrentTaskExecutor를 대신 사용하기를 권장한다.
  • TimerTaskExecutor

    이 구현체는 지원 구현체로 단일 TimerTask를 사용한다. SyncTaskExecutor와 다른 부분은 같은 스레드에서 동기로 동작하더라도 메서드 호출을 다른 스레드에서 한다는 점이다.
  • WorkManagerTaskExecutor

    이 구현체는 CommonJ WorkManager를 지원 구현체로 사용하고 스프링 컨텍스트에서 CommonJ WorkManager 참조를 구성하는 핵심 클래스다. SimpleThreadPoolTaskExecutor와 유사하게 이 클래스는 WorkManager 인터페이스를 구현했으므로 WorkManager처럼 직접 사용할 수 있다.

이 구현체는 java.util.concurrent 패키지의 어떤 백포트나 다른 버전과 함께 사용할 수 없다. Doug Lea의 구현체와 Dawid Kurzyniec의 구현체 모두 다른 패키지 구조를 사용해서 제대로 동작하지 않을 것이다.


CommonJ는 BEA와 IBM이 함께 개발한 명세다. 이 명세는 Java EE 표준은 아니지만, BEA와 IBM의 애플리케이션 서버 구현체의 표준이다.


26.2.2 TaskExecutor의 사용

스프링의 TaskExecutor 구현체를 간단한 JavaBean으로 사용한다. 다음 예제에서는 메시지를 비동기로 출력하려고 ThreadPoolTaskExecutor를 사용하는 빈을 정의했다.

 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
Java

import org.springframework.core.task.TaskExecutor;

public class TaskExecutorExample {
  private class MessagePrinterTask implements Runnable {

    private String message;

    public MessagePrinterTask(String message) {
      this.message = message;
    }

    public void run() {
      System.out.println(message);
    }
  }

  private TaskExecutor taskExecutor;

  public TaskExecutorExample(TaskExecutor taskExecutor) {
    this.taskExecutor = taskExecutor;
  }

  public void printMessages() {
    for(int i = 0; i < 25; i++) {
      taskExecutor.execute(new MessagePrinterTask("Message" + i));
    }
  }
}

여기서 보듯이 풀(pool)에서 스레드를 가져와서 직접 실행하는 대신 큐에 Runnable를 추가하고 TaskExecutor가 테스트를 언제 실행할지 결정하기 위해 내부의 규칙을 사용한다.
TaskExecutor가 사용할 규칙을 설정하려고 빈(bean) 프로퍼티를 노출했다.

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

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
  <property name="corePoolSize" value="5" />
  <property name="maxPoolSize" value="10" />
  <property name="queueCapacity" value="25" />
</bean>

<bean id="taskExecutorExample" class="TaskExecutorExample">
  <constructor-arg ref="taskExecutor" />
</bean>


26.3 스프링의 TaskScheduler 추상화

TaskExecutor 추상화에 추가로 스프링 3.0에서는 미래의 어떤 시점에 실행할 태스크를 스케줄링하는 다양한 메서드를 가진 TaskScheduler를 도입되었다.

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

public interface TaskScheduler {
  ScheduledFuture schedule(Runnable task, Trigger trigger);

  ScheduledFuture schedule(Runnable task, Date startTime);

  ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);

  ScheduledFuture scheduleAtFixedRate(Runnable task, long period);

  ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);

  ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}

Runnable과 Date만 받는 'schedule' 메서드가 가장 간단한 메서드다. 이 메서드는 지정한 시간 후에 태스크를 한번 실행할 것이다. 다른 모든 메서드는 반복해서 실행할 태스크를 스케줄링할 수 있다. fixed-rate 메서드와 fixed-delay 메서드는 간단하고 주기적인 실행을 위한 메서드이지만 훨씬 더 유연한 Trigger를 받을 수 있다.


26.3.1 Trigger 인터페이스

Trigger 인터페이스는 기본적으로 JSR-236(스프링 3.0에서는 아직 공식적으로 구현되지 않았다.)의 영향을 받았다. Trigger 인터페이스의 기본적인 개념은 과거의 실행 결과나 임의의 상황에 기반을 둬서 실행시간을 결정한다는 것이다. 이전 실행의 결과를 고려하는 경우 해당 정보는 TriggerContext내에 있다. Trigger 인터페이스 자체는 아주 간단하다.

1
2
3
4
5
Java

public interface Trigger {
  Date nextExecutionTime(TriggerContext triggerContext);
}

여기서 TriggerContext가 가장 중요한 부분이다. TriggerContext는 모든 관련 데이터를 은닉화하고 나중에 필요할 때 확장할 수 있게 열려있다. TriggerContext는 인터페이스다. (SimpleTriggerContext 구현체를 기본값으로 사용한다.) Trigger 구현체에서 사용할 수 있는 메서드가 다음에 나와 있다.

1
2
3
4
5
6
7
8
9
Java

public interface TriggerContext {
  Date lastScheduledExecutionTime();

  Date lastActualExecutionTime();

  Date lastCompletionTime();
}


26.3.2 Trigger 구현체

스프링은 Trigger 인터페이스의 두 구현체를 제공한다. CronTrigger가 가장 흥미로운데 이는 cron 표현식으로 태스크를 스케줄링할 수 있다. 예를 들어 다음 태스크는 주중 "업무시간"인 9~5시 사이의 매시간 15분에 실행된다.

1
2
3
Java

scheduler.schedule(task, new CronTrigger("* 15 9-17 * * MON-FRI"));

또 다른 구현체인 PeriodicTrigger는 고정된 기간을 받고 선택적으로 초기 지연값과 지정한 기간을 고정된 비율로 해석할지 고정된 지연시간으로 해석할지 나타내는 불리언값을 받는다. TaskScheduler가 고정된 비율이나 고정된 지연시간으로 태스크를 스케쥴일하는 메서드를 이미 정의하고 있으므로 가능하면 TaskScheduler의 메서드를 직접 사용할 것이다. Trigger 추상화에 의존하는 컴포넌트내에서 PeriodicTrigger 구현체의 값을 사용할 수 있다. 예를 들어 정기적인 트리거, 크론 기반의 트리거, 임의의 트리거 구현을 상호교환해서 편리하게 사용할 수 있다. 이러한 컴포넌트는 의존성 주입을 할 수 있으므로 Triggers 등을 외부에서 설정할 수도 있다.


26.3.3 TaskScheduler 구현체

스프링의 TaskExecutor 추상화처럼 TaskScheduler는 스케줄링 작업에 의존하는 코드가 특정 스케줄러 구현체에 의존할 필요가 없다는 것이 주요한 장점이다. TaskScheduler의 유연성은 애플리케이션이 직접 스레드를 생성하지 않아야 하는 애플리케이션 서버 환경에서 동작하는 경우와 특히 관련이 있다. 이러한 경우를 위해서 스프링은 CommonJ TimerManager 인스턴스(보통 JNDI 검색으로 설정하는)에 위임하는 TimerManagerTaskScheduler를 제공한다.
아니면 더 간단한 방법으로 외부 스레드 관리가 필요치 않은 경우에 ThreadPoolTaskScheduler를 사용할 수 있다. 내부적으로 ThreadPoolTaskScheduler는 ScheduledExecutorService 인스턴스에 위임한다. 사실 ThreadPoolTaskScheduler는 스프링의 TaskExecutor 인터페이스를 구현했으므로 스케줄된 대로 가능한한 빨리 비동기로 실행하는데(혹은 반복된 실행) 단일 인스턴스를 사용할 수 있다.

26.4 태스크(Task) 네임스페이스

스프링 3.0부터 TaskExecutor와 TaskScheduler 인스턴스를 구성하는 XML 네임스페이스가 존재하고 트리거로 예약된 태스크를 구성하는 간편한 방법도 제공한다.

26.4.1 'scheduler' 엘리먼트

아래의 엘리먼트는 지정한 크기의 스레드풀을 가진 ThreadPoolTaskScheduler 인스턴스를 생성한다.

1
2
3
Xml

<task:scheduler id="scheduler" pool-size="10"/>

'id' 속성으로 제공한 값은 풀(pool)에서 스레드 이름의 접두사로 사용한다. 'scheduler' 엘리먼트는 비교적 직관적이다. 'pool-size' 속성을 지정하지 않았다면 기본 스레드풀은 하나의 스레드만 가질 것이다. scheduler에 다른 설정 옵션은 없다.


26.4.2 'executor' 엘리먼트

다음은 ThreadPoolTaskExecutor 인스턴스를 생성한다.

1
2
3
Xml

<task:executor id="executor" pool-size="10"/>

앞에서 본 scheduler와 마찬가지로 'id' 속성은 풀(pool)에서 스레드 이름의 접두사로 사용한다. 풀의 크기에 관심이 있다면 'executor' 엘리먼트는 'scheduler' 엘리먼트보다 많은 설정 옵션을 지원한다. ThreadPoolTaskExecutor의 스레드풀 자체를 더 상세하게 설정할 수 있다. 그냥 하나의 사이즈를 지정하는 것이 아니라 executor의 스레드풀은 core와 max 크기에 다른 값을 줄 수 있다. 하나의 값을 지정한다면 executor는 고정된 크기의 스레드 풀을 가진다.(core와 max 크기가 같은 경우) 하지만 'executor' 엘리먼트의 'pool-size' 속성도 "min-max" 형식의 범위도 받을 수 있다.

1
2
3
4
5
Xml

<task:executor id="executorWithPoolSizeRange"
     pool-size="5-25"
     queue-capacity="100"/>

이 설정에서는 'queue-capacity' 값도 지정했다. 스레드풀의 설정은 익스큐터 큐의 크기와도 관련된다. 풀의 크기와 큐의 크기 간의 관계에 대한 자세한 내용은 ThreadPoolExecutor 문서를 참고해라. 핵심 개념은 태스크를 제출했을 때 executor는 액티브 스레드의 수가 코어의 개수보다 적다면 놀고 있는 스레드를 먼저 사용한다는 것이다. 활성화된 스레드 수가 코어의 개수만큼이고 큐의 용량이 꽉 차지 않았다면 태스크를 큐에 추가할 것이다. 큐의 용량이 꽉 찼다면 익스큐터는 코어 개수 이상의 새로운 스레드를 생성할 것이다. 최대 사이즈에 이르렀다면 익스큐터는 해당 태스크를 거절한다.
풀의 모든 스레드가 바쁜 상태일 때 해당 큐에 태스크가 추가된다면 OutOfMemoryErrors가 발생할 수 있으므로 보통 큐가 언바인드되는 것은 원하는 설정이 아닐 것이다. 게다가 큐가 언바인드되면 최대 사이즈는 아무런 영향을 주지 않는다. 익스큐터는 코어 개수 이상의 새로운 스레드를 생성하기 전에 항상 큐를 먼저 시도하므로 코어 개수보다 많이 스레드 풀을 늘리려면 큐가 반드시 정해진 용량을 가져야 한다.(이것이 고정된 크기의 풀이 언바인드된 큐를 사용하는 경우에만 합리적인 이유이다.)
이제 풀의 크기 설정을 제공하는 경우에 고려해야 한 또 다른 요소인 keep-alive 설정을 볼 것이다. 우선 앞에서 언급했던 태스크를 거절한 경우를 생각해 보자. 보통 태스크가 거절되면 스레드풀 익스큐터는 TaskRejectedException를 던질 것이지만 거절 정책을 설정할 수 있다. 기본 거절 정책인 AbortPolicy 구현체를 사용하는 경우에 TaskRejectedException 예외를 던진다. 과부하상태에서 일부 태스크는 건너뛸 수 있는 애플리케이션에서는 DiscardPolicy나 DiscardOldestPolicy를 대신 설정할 수 있다. 과부하상태에서 제출된 태스크를 제어(throttle)해야 하는 애플리케이션에서는 CallerRunsPolicy도 좋은 선택이다. 이 정책은 예외를 던지거나 태스크를 버리지 않고 스레드가 테스트 자체를 실행하는 submit 메서드를 호출하도록 강제할 것이다. 이 정책의 개념은 이러한 호출자는 태스크를 실행하는 동안에는 바빠서 다른 태스크를 즉시 제출하지 못하리라는 것이다. 그러므로 스레드 풀과 큐의 제한을 유지하면서 부하를 제거하는 간단한 방법을 제공한다. 보통 이를 통해서 익스큐터가 "밀려있는" 태스크를 처리할 수 있으므로 큐나 풀 용량에 여유가 생긴다. 'executor' 요소의 'rejection-policy' 속성에 이러한 옵션 중 어느 것이라도 사용할 수 있다.

1
2
3
4
5
6
Xml

<task:executor id="executorWithCallerRunsPolicy"
    pool-size="5-25"
    queue-capacity="100"
    rejection-policy="CALLER_RUNS"/>


26.4.3 'scheduled-tasks' 요소

스프링 task 네임스페이스에서 가장 강력한 기능은 스프링 애플리케이션 컨텍스트에서 스케줄링 되는 태스크를 설정하는 기능이다. 이 기능도 JMS 네임스페이스에서 메시지주도 POJO를 설정하는 것과 같은 다른 스프링의 "메서드-인보커"와 비슷한 접근을 한다. 기본적으로 "ref" 속성은 스프링이 관리하는 어떤 객체라도 가리킬 수 있고 "method" 속성은 해당 객체에서 호출될 메서드명을 지정한다. 다음은 간단한 예제이다.

1
2
3
4
5
6
7
Xml

<task:scheduled-tasks scheduler="myScheduler">
  <task:scheduled ref="someObject" method="someMethod" fixed-delay="5000"/>
</task:scheduled-tasks>

<task:scheduler id="myScheduler" pool-size="10"/>

예제에서 보듯이 외부 요소가 schedule를 참조하고 각 task는 트리거 메타데이터의 설정을 가지고 있다. 앞의 예제에서 메타데이터는 고정된 지연시간을 가진 주기적인 트리거를 정의하고 있다. 이는 "고정된 비율(fixed-rate)"로 설정하거나 "cron"속성을 사용해서 더 세밀하게 제어할 수도 있다. 다음은 이러한 옵션을 사용한 예제이다.

1
2
3
4
5
6
7
8
Xml

<task:scheduled-tasks scheduler="myScheduler">
  <task:scheduled ref="someObject" method="someMethod" fixed-rate="5000"/>
  <task:scheduled ref="anotherObject" method="anotherMethod" cron="*/5 * * * * MON-FRI"/>
</task:scheduled-tasks>

<task:scheduler id="myScheduler" pool-size="10"/>


26.5 스케줄링과 비동기 실행에 대한 어노테이션 지원

스프링 3.0은 task 스케줄링과 비동기 메서드 실행에 대한 어노테이션도 지원한다.

26.5.1 @Scheduled 어노테이션

트리거 메타데이터와 함께 @Scheduled 어노테이션을 메서드에 추가할 수 있다. 예를 들어 다음 예제는 고정된 시간이 5초마다 호출되고 이 시간은 이전 호출이 완료된 시점부터 계산될 것이다.

1
2
3
4
5
6
Java

@Scheduled(fixedDelay=5000)
public void doSomething() {
  // 주기적으로 실행될 것이다
}

고정된 비율로 실행하기를 원한다면 어노테이션에서 지정한 프로퍼티명만 바꿔주면 된다. 다음 예제는 각 호출의 연속적인 시작 시각의 간격으로 계산된 5초마다 실행될 것이다.

1
2
3
4
5
6
Java

@Scheduled(fixedRate=5000)
public void doSomething() {
  // 주기적으로 실행될 것이다
}

간단한 주기 스케줄링으로 충분하지 않다면 cron 표현식을 사용할 수 있다. 다음은 평일에만 실행될 것이다.

1
2
3
4
5
6
Java

@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
  // 평일에만 실행될 것이다
}

스케쥴되는 메서드는 반환 타입이 반드시 void이고 아무런 인자도 받지 말아야 한다. 애플리케이션 컨텍스트의 다른 객체와 메서드가 상호작용해야 한다면 의존성 주입으로 제공하는 것이 일반적이다.


Note
각 인스턴스에 대한 콜백을 스케줄링하려는 것이 아니라면 런타임시에 같은 @Scheduled 어노테이션 클래스의 여러 인스턴스를 초기화하지 않도록 해야 한다. 이와 관련해서 @Scheduled 어노테이션이 붙고 컨테이너에 일반적인 스프링 빈으로 등록된 bean 클래스에 @Configurable를 사용하지 말아야 한다. 그렇지 않으면 두 번 초기화되는 상황이 발생해서(한번은 컨테이너에서 발생하고 한번은 @Configurable 관점(aspect)에서 발생한다) 각 @Scheduled 메서드는 두 번 호출되게 된다.


26.5.2 @Async 어노테이션

@Async 어노테이션은 메서드에 사용해서 메서드를 비동기로 호출할 수 있다. 즉, 콜러(caller)는 호출하자마자 바로 결과를 받고 실제 메서드 실행은 스프링 TaskExecutor에 제출된 task에서 일어날 것이다. 가장 간단한 경우는 void를 반환하는 메서드에 @Async 어노테이션을 적용하는 것이다.

1
2
3
4
5
6
Java

@Async
void doSomething() {
  // 이 메서드는 비동기로 실행될 것이다
}

@Scheduled 어노테이션이 붙은 메서드와는 달리 @Async 어노테이션이 붙은 메서드는 컨테이너가 관리하는 스케쥴된 task가 아니라 런타임시에 콜러가 "일반적인" 방법으로 호출하기 때문에 인자를 받을 수 있다. 예를 들어 다음 코드는 유효한 @Async 어노테이션을 사용한 애플리케이션이다.

1
2
3
4
5
6
Java

@Async
void doSomething(String s) {
  // 이 메서드는 비동기로 실행될 것이다
}

값을 반환하는 메서드로 비동기로 호출할 수 있지만 이러한 메서드는 반환 값이 Future 타입이어야 한다. 이는 비동기 실행의 이점을 가지므로 콜러(caller)는 Future에서 get()를 호출하기 전에 다른 작업을 할 수 있다.

1
2
3
4
5
6
Java

@Async
Future<String> returnSomething(int i) {
  // 이 메서드는 비동기로 실행될 것이다
}

@Async는 @PostConstruct처럼 생명주기 콜백과 동시에 사용할 수 없다. 현재 스프링 빈을 비동기로 초기화하려면 타겟의 @Async 어노테이션이 붙은 메서드를 호출하는 스프링 빈을 초기화하는 작업을 분리해서 사용해야 한다.

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

public class SampleBeanImpl implements SampleBean {
  @Async
  void doSomething() {  }
}

public class SampleBeanInititalizer {
  private final SampleBean bean;

  public SampleBeanInitializer(SampleBean bean) {
    this.bean = bean;
  }

  @PostConstruct
  public void initialize() {
    bean.doSomething();
  }
}


26.5.3 요소

@Scheduled과 @Async 어노테이션을 둘 다 활성화하려면 설정에서 task 네임스페이스에 'annotation-driven' 요소를 추가해라.

1
2
3
4
5
6
7
Xml

<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>

<task:executor id="myExecutor" pool-size="5"/>

<task:scheduler id="myScheduler" pool-size="10"/>

executor 참조는 @Async 어노테이션이 붙은 메서드에 대응하는 태스크를 처리하기 위해 제공하고 scheduler 참조는 @Scheduled 어노테이션이 붙은 메서드를 관리하기 위해서 제공한다.


26.6 OpenSymphony Quartz 스케쥴러의 사용

Quartz는 모든 종류의 작업 스케줄링을 인식하는데 Trigger, Job, JobDetail 객체를 사용한다. Quartz의 기본 개념에 대해서는 http://www.opensymphony.com/quartz를 참고해라. 스프링은 스프링에 기반한 애플리케이션에서 Quartz를 간단히 사용할 수 있도록 클래스 다수를 제공한다.

26.6.1 JobDetailBean의 사용

JobDetail 객체에는 작업을 실행하는 데 필요한 모든 정보가 담겨있다. 스프링 프레임워크는 합리적인 기본값으로 실제 자바 빈의 JobDetail를 만드는 JobDetailBean를 제공한다. 예제를 살펴보자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Xml

<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailBean">
  <property name="jobClass" value="example.ExampleJob" />
  <property name="jobDataAsMap">
    <map>
      <entry key="timeout" value="5" />
    </map>
  </property>
</bean>

job detail bean에는 작업(ExampleJob)에 필요한 모든 정보가 담겨있다. timeout은 job data map에서 지정한다. job data map은 JobExecutionContext로(실싱히세 전달되는) 사용할 수 있지만 JobDetailBean도 job data map의 프로퍼티에서 실제 job의 프로퍼티로 매핑한다. 그러므로 이 예제에서 ExampleJob에 timeout라는 프로퍼티가 있다면 JobDetailBean는 자동으로 이를 적용할 것이다.

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

package example;

public class ExampleJob extends QuartzJobBean {
  private int timeout;

  /**
   * Setter는 ExampleJob이 JobDetailBean의 값(5)으로
   * 인스턴스화된 후에 호출된다.
   */
  public void setTimeout(int timeout) {
    this.timeout = timeout;
  }

  protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
    // 실제 작업을 한다
  }
}

job detail bean의 부가적인 모든 설정도 당연히 사용할 수 있다. Note: name와 group 프로퍼티를 사용해서 작업의 이름과 그룹을 변경할 수 있다. 기본적으로 작업의 이름은 작업의 bean detail 빈의 이름과 일치한다.(위의 예제에서는 exampleJob이다.)


26.6.2 MethodInvokingJobDetailFactoryBean의 사용

특정 객체의 메서드를 그냥 호출해야 할 때가 있는데 MethodInvokingJobDetailFactoryBean를 사용해서 할 수 있다.

1
2
3
4
5
6
Xml

<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
  <property name="targetObject" ref="exampleBusinessObject" />
  <property name="targetMethod" value="doIt" />
</bean>

위 예제는 exampleBusinessObject에서 doIt 메서드가 호출될 것이다.(다음 예제 참고)

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

public class ExampleBusinessObject {
  // properties와 collaborators

  public void doIt() {
    // 여기서 실제 작업을 한다
  }
}

Xml

<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>

MethodInvokingJobDetailFactoryBean를 사용하면 실행하기만 할 메서드를 위한 한 줄짜리 작업을 생성할 필요 없이 실제 비즈니스 객체를 생성하고 detail 객체와 연결하기만 하면 된다.
기본적으로 Quartz Job은 상태를 가지지 않고 작업끼리 서로 간섭할 가능성이 있다. 같은 JobDetail에 대한 두 가지 트리거를 지정했다면 첫 번째 작업이 끝나기 전에 두 번째 작업이 시작할 가능성이 있다. JobDetail 클래스가 Stateful 인터페이스를 구현하면 이런 일은 일어나지 않고 두 번째 작업은 첫 번째 작업이 끝나기 전에는 시작되지 않는다. MethodInvokingJobDetailFactoryBean로 동시성이 없는 작업을 만들려면 concurrent 플래그를 false로 설정해라.

1
2
3
4
5
6
7
Xml

<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
  <property name="targetObject" ref="exampleBusinessObject" />
  <property name="targetMethod" value="doIt" />
  <property name="concurrent" value="false" />
</bean>



Note
기본적으로 작업은 동시성을 가진 채 실행될 것이다.


26.6.3 트리커와 SchedulerFactoryBean를 사용하는 작업의 연결

job details와 job을 생성했고 특정 객체의 메서드를 호출할 수 있는 편리한 빈도 살펴봤다. 물론 아직 작업 자체를 스케줄링해야 한다. 이는 트리거와 SchedulerFactoryBean을 사용해서 스케줄링한다. Quartz에서는 다수의 트리거를 사용할 수 있고 스프링은 간편한 기본값을 가진 두 트리거 하위클래스 CronTriggerBean와 SimpleTriggerBean를 제공한다.
트리거를 스케줄링해야 하는데 스프링은 설정할 프로퍼티로 트리거를 노출하는 SchedulerFactoryBean를 제공한다. SchedulerFactoryBean은 이러한 트리거로 실제 작업을 스케줄링한다. 다음의 두 예제를 보자.

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

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
  
  <property name="jobDetail" ref="jobDetail" />
  
  <property name="startDelay" value="10000" />
  
  <property name="repeatInterval" value="50000" />
</bean>

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
  <property name="jobDetail" ref="exampleJob" />
  
  <property name="cronExpression" value="0 0 6 * * ?" />
</bean>

이제 두 가지 트리거를 설정했다. 한 트리거는 10초 후에 시작해서 50초마다 실생하고 다른 트리거는 매일 오전 6시에 실행한다. 이 예제가 동작하려면 SchedulerFactoryBean를 구성해야 한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Xml

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
  <property name="triggers">
    <list>
      <ref bean="cronTrigger" />
      <ref bean="simpleTrigger" />
    </list>
  </property>
</bean>

이 외에도 job details가 사용하는 calendars나 Quartz를 커스터마이징할 수 있는 프로퍼티 등 SchedulerFactoryBean에서 설정할 프로퍼티가 많이 있다. 자세한 내용은 SchedulerFactoryBean Javadoc를 참고해라.


26.7 JDK Timer 지원 사용하기

스프링에서 작업을 스케줄링하는 또 다른 방법은 JDK Timer 객체를 사용하는 것이다. 커스텀 트리거를 생성하거나 메서드를 호출하는 타이머를 사용할 수 있다. TimerFactoryBean를 사용해서 타이머를 연결한다.

26.7.1 커스텀 타이머 생성

TimerTask를 사용해서 Quartz 작업과 유사한 커스텀 타이머 태스크를 생성할 수 있다.

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

public class CheckEmailAddresses extends TimerTask {
  private List emailAddresses;

  public void setEmailAddresses(List emailAddresses) {
    this.emailAddresses = emailAddresses;
  }

  public void run() {
    // 모든 이메일 주소를 반복하면서 저장한다
  }
}

간단히 연결할 수 있다.

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

<bean id="checkEmail" class="examples.CheckEmailAddress">
  <property name="emailAddresses">
    <list>
      <value>test@springframework.org</value>
      <value>foo@bar.com</value>
      <value>john@doe.net</value>
    </list>
  </property>
</bean>

<bean id="scheduledTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">
  
  <property name="delay" value="10000" />
  
  <property name="period" value="50000" />
  <property name="timerTask" ref="checkEmail" />
</bean>

period 프로퍼티를 0으로(혹은 음수로) 바꾸면 태스크를 딱 한 번만 실행하도록 할 수 있다.


26.7.2 MethodInvokingTimerTaskFactoryBean의 사용

Quartz 지원처럼 Timer지원에도 메서드를 주기적으로 호출할 수 있는 컴포넌트가 있다.

1
2
3
4
5
6
Xml

<bean id="doIt" class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean">
  <property name="targetObject" ref="exampleBusinessObject" />
  <property name="targetMethod" value="doIt" />
</bean>

위의 예제에서는 exampleBusinessObject의 doIt메서드가 호출된다.(다음 예제 참고)

1
2
3
4
5
6
7
8
9
Java

public class BusinessObject {
  // properties와 collaborators

  public void doIt() {
    // 여기서 실제 작업을 한다
  }
}

ScheduledTimerTask예제에서 timerTask의 참조를 doIt 빈으로 바꾸면 정해진 스케줄로 doIt 메서드를 실행할 것이다.


26.7.3 요약: TimerFactoryBean을 사용해서 태스크 설정하기

TimerFactoryBean는 Quartz SchedulerFactoryBean와 비슷하게 실제 스케줄링을 구성할 수 있다. TimerFactoryBean는 Timer를 구성하고 참조한 태스크를 스케줄링할 수 있다. 데몬 스레드를 사용할지를 지정할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Xml

<bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean">
  <property name="scheduledTimerTasks">
    <list>
      <!-- 앞의 예제 참고 -->
      <ref bean="scheduledTask" />
    </list>
  </property>
</bean>