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

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>


[UFC] UFC 마감 뉴스..


출처 : 스포티비뉴스

 

 

 

 

 

 

 



2017년 5월 10일 수요일

[NEWS] 기술 뉴스 #11 : 14-06-15..


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

웹개발 관련

  • HTTP/1.1 just got a major update. : IFTF가 15년 전에 작성된 HTTP/1.1의 명세인 RFC 2616를 개선해서 새로 발표했다. 명세를 더 분명하고 이해하기 쉽게 바뀌었고 RFC7230, RFC7231, RFC7232 등 세부 다양한 명세로 나뉘었다.(영어)
  • Medium 프론트 기술 분석 : Single Page Application으로 되어 있는 Medium의 프론트엔드가 어떤 식으로 구성되어 있고 각 요소를 어떻게 불러와 사용하는지 분석한 글(한글)
  • XRebel 1.0 – the interactive Java profiler : ZeroTrunAround에서 새로운 인터렉티브 자바 프로파일러를 공개했다. XRebel jar를 톰캣실행시에 연결만 하면 웹 브라우저 내에서 실시간으로 웹 애플리케이션을 프로파일링할 수 있다. XRebel은 유료제품이고 14일간은 무료로 사용해볼 수 있다.(영어)

그 밖의 프로그래밍 관련

IT 업계 뉴스

볼만한 링크


[DEV] Sitespeed.io를 사용한 웹사이트 성능 테스트..


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

오랫동안 웹페이지 속도를 측정하는 방법에 대해서 고민했다. 크롬 개발자 도구 등으로 성능측정을 할 수 있고 YSlowPage Speed로 개선점을 파악할 수 있지만 그다지 만족스럽지는 않았다.(당장 그쪽 업무를 하는 건 아니다 보니 그렇게 열심히 찾지 않은 탓도 있다.) 웹 프런트 앤드도 점점 복잡해져서 성능 개선을 할 수 있는 포인트가 다양해 졌으므로 부분별 용량을 줄이거나 속도를 측정해 볼 수 있지만 관심 있는 부분은 전체 페이지 로딩 속도였다. 이런저런 개선을 하고 나서 실제로 전체 페이지 속도가 얼마나 개선되었는 지가 궁금했다. 이 속도가 사용자가 체감할 속도였으니까...

그러다가 HTML5에 Navigation Timing이라는 API가 존재한다는 것을 알게 되었고 이 API가 내가 바라던 용도를 위한 API라는 것을 알게 되었다. 그래서 공부하다가 잘 정리된 HTML5Rocks의 글은 번역했다. 이 글에 Navigation Timing API를 어떻게 사용하는지 설명이 잘 나와 있고 글 마지막에 있는 북마클릿을 사용하면 페이지의 로딩 속도를 정확하게 측정할 수 있다.

sitespeed.io

Navigation Timing을 사용하더라도 간단한 테스트가 아닌 성능을 측정하려면 여러 번 테스트를 실행하고 평균값을 구해야 어느 정도 정확한 값을 알 수 있지 한두 번 테스트만으로는 신뢰할만한 데이터를 얻을 수 없을 것이다. 성능테스트를 제대로 하려면 최적화 작업을 하고 테스트를 실행하는 과정을 계속 반복해야 하므로 많은 테스트를 실행하고 결과 값을 수집하는 작업이 간단하게 자동화할 수 있다면 더 좋을 것이다. 이러다 찾아낸 도구가 sitespeed.io다. 현재까지는 내가 원하는 모든 요구사항을 가장 잘 갖추고 있는 도구다.


설치방법

sitespeed.io는 OS X의 패키지관리도구인 Homebrew로 설치할 수 있다. 다음 명령어를 통해서 brew tap으로 sitesppedio를 등록하고 설치를 진행하면 된다.


Bash
$ brew tap sitespeedio/sitespeedio
$ brew tap tobli/browsertime
$ brew install sitespeed.io

설치가 완료되면 다음과 같이 sitespeed.io 명령어를 터미널에서 사용할 수 있다.(-V옵션 다음에 true를 붙인 이유는 버그로 -V옵션 뒤에 어떤 값을 주지 않으면 동작을 하지 않기 때문이다. 아무 문자나 입력하면 된다. 이 버그는 v2.6에서 수정될 예정이다.)


Bash
$ sitespeed.io -V true
2.5.7


사용방법

자세한 옵션은 sitespeed.io -h로 확인할 수 있는데 -u로 테스트할 URL을 지정할 수 있고 -c로 사용할 브라우저를 지정한다. 그래서 내 블로그를 크롬으로 속도를 측정하려면 sitespeed.io -u http://blog.outsider.ne.kr -c chrome와 같이 입력할 수 있다. 간단한 테스트는 직접 해봐도 되므로 sitespeed.io를 쓰는 이유는 테스트를 여러 번 실행하기 위함인데 이 경우는 -z로 총 횟수 테스트를 할 수 있다. sitespeed.io는 기본적으로 페이지에서 링크를 1단계까지만 테스트하지만 같은 호스트 내의 링크는 모두 추적해서 테스트를 실행한다. 그래서 지정한 페이지 하나만 테스트하려면 -j 옵션을 사용해야 한다.(그렇지 않으면 이 블로그에서 태그나 페이징 같은 URL을 모두 추적해서 테스트하므로 시간이 꽤 걸린다.) -n으로 이름을 줄 수도 있는데 이는 나중에 결과 페이지의 제목으로 나타난다.


Bash
$ sitespeed.io -u http://blog.outsider.ne.kr -c chrome -n blog -z 10 -j 1
Will crawl from start point http://blog.outsider.ne.kr with User-Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.71 Safari/537.36 and viewport 1280x800 with crawl depth 1 using ruleset sitespeed.io-desktop ... this can take a while
Using sitespeed.io version 2.5.7
Using PhantomJS version 1.9.7
Using Java version 1.8.0
Using BrowserTime version 0.6
From IP unknown
Will analyze 1 pages
Analyzing http://blog.outsider.ne.kr
Collecting Navigation Timing metrics (chrome): http://blog.outsider.ne.kr
Create all (1) pages per URL
Create all the result pages
Create result.xml
Create the summary.xml
Create the summary.details.html
Create the pages.html
Create the summary index.html
Create the assets.html
Create the rules.html
Finished analyzing blog.outsider.ne.kr

위 명령어는 블로그를 10번 테스트를 한 화면이다. 분석이 끝나고 테스트가 실행되면 크롬 브라우저가 실행되면서 속도를 측정하게 된다. 여기서는 크롬을 지정했으므로 크롬이 실행되고 횟수를 10번으로 지정했으므로 크롬이 10번 실행됐다 종료됐다고 한다. 테스트가 완료되면 자동으로 데이터를 볼 수 있는 HTML을 만들어 준다. 테스트 결과는 sitespeed-result/blog.outsider.ne.kr/2014-06-04-03-43-22와 같은 폴더에 생기고 그 아래 HTML 파일과 XML이 자동으로 위치한다. HOST 별로 폴더를 나눠주고 테스트 시간마다 폴더를 만들어 주므로 다양한 테스트를 진행하고 시간별로 구분해서 볼 수 있다.


테스트 결과

테스트 결과의 index.html를 열어보면 다음과 같은 화면을 볼 수 있다.


사용자 삽입 이미지

프론트 엔드에서 속도와 관련해서 관심 가질만한 대부분 항목이 분류별로 표시가 되고 나름대로 기준에 따라 양호한지 문제가 있는지를 라벨 색으로 표시해 줘서 개선해야 할 부분을 한눈에 볼 수 있다.


사용자 삽입 이미지

값 위에 마우스를 올리면 해당 값이 어떤 의미인지 툴팁으로 나오고 색을 바꾸려면 어느 어느 정도의 값을 얻어야 하는지도 표시된다. serverResponseTime, backEndTime, pageDownloadTime, frontEndTime, domContentLoadedTime처럼 앞에서 언급했던 Navigation Timing 관련 값도 표시가 돼서 병목구간별로 나누어서 속도를 개선 여부를 테스트하고 확인해 볼 수 있다.


사용자 삽입 이미지

Detailed summary 메뉴에서는 위에서 본 각 값의 정확한 수치를 볼 수 있다. 여러 번 테스트하는 경우 속도 차이가 약간 발생할 수 있으므로 최고/최저값과 상하위값에 대한 내용도 확인할 수 있다.


사용자 삽입 이미지

Pages 메뉴에서는 페이지에 대한 요약정보를 볼 수 있다. 여기서는 하나의 페이지만 테스트했으므로 정보도 하나만 나타난다.


사용자 삽입 이미지

Assets 메뉴에서는 정적파일의 용량이나 캐시에 대한 정보를 볼 수 있다.

에필로그

sitespeed.io에서는 다양한 옵션을 제공하므로 여러 브라우저에서의 결과를 테스트해 본다거나 테스트한 페이지의 스크린샷을 저장한다거나 하는 옵션도 지정할 수 있다. 커맨드라인으로 제공하므로 자동화가 가능하고 횟수를 증가시켜서 다양한 테스트를 실행할 수 있기 때문에 웹사이트의 성능 튜닝을 할 때 테스트 도구로 사용하기에 좋아 보인다. 물론 Page Speed같은 도구가 있기는 하지만 Sitespeed를 사용하면 결과를 자동으로 저장해서 나중에 비교할 수 있고 쉽게 다수의 테스트를 실행할 수 있어서 만족스럽다.

2017년 5월 4일 목요일

[NEWS] 기술 뉴스 #10 : 14-06-01..


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

웹개발 관련

  • Specification : 웹 표준에 관련해서 토론할 수 있는 사이트가 새로 열렸다. 공개적으로 API에 대한 토론을 주고받을 수 있다.(영문)
  • Assassin's Creed Pirates Web Demo : IE 팀에서 WebGL 기반의 3D 엔진 Babylon.JS로 만든 Assassin’s Creed Pirates 게임의 웹 데모를 공개했다. 자연스러운 3D 화면이 인상적이고 글 가운데 있는 Babylon.JS의 데모도 흥미롭다.(영문)
  • Parallax Done Right : 유행처럼 많이 사용하는 Parallax 디자인을 적용한 사이트를 구현할 때 어떻게 구현하고 어떤 부분을 조심해야 하는지 잘 정리된 글(영문)
  • Object.observe()를 통한 데이터 바인딩 혁명 : Object.observe()가 크롬 36 베타에 추가되었다. Object.observe()는 객체의 변경사항을 추적하기 위해서 dirty-checking 같은 방법 대신 훨씬 성능 좋게 사용할 수 있는 ES7의 API이다.(한글)

그 밖의 프로그래밍 관련

IT 업계 뉴스

볼만한 링크

  • Atlassian Design Guidelines : 아틀라시안의 디자인 가이드라인의 새 버전이 나왔다.(전에는 있는지도 몰랐지만) 칼라셋부터 모든 버튼과 UI에 대해서 설명이 되어 있어서 UI나 디자인에 관심 있으면 참고할 요소가 많아 보인다.(영문)
  • First Pull Request : Github에서 처음 보낸 Pull Request를 찾아주는 사이트. Github를 사용한 지 오래되었다면 예전에 이런 풀리퀘를 보냈구나 하는 재미가 있다.
  • Don't learn to code. Learn to think. : Code.org를 비롯해서 최근에 얘기 나오는 어렸을 때부터 코드를 작성하는 법을 가르쳐야 한다는 움직임에 대해서 코드를 작성하는 법이 아니라 생각하는 방법을 배워야 한다는 관점에 글로 코드 자체는 도구일 뿐이고 왜 생각하는 법을 배우는 것이 중요한지를 잘 설명하고 있다.(영문)
  • fira font : Firefox OS의 폰트인 fira font를 무료로 배포하고 있다.(영문)