PropertyPlaceholderConfigurer를 @Bean으로 정의해서는 안되는 이유

2009.12.24

PropertyPlaceholderConfigurer는 프로퍼티 파일의 속성을 가지고 ${database.name}과 같은 프로퍼티 값을 바꿔주는 매우 유용한 기능을 가지고 있어서 자주 사용된다. 스프링의 기초정도만 배웠어도 DataSource를 직접 스프링에서 정의하는 할 때 DB연결정보는 프로퍼티 파일로 빼고 ${database.url}과 같은 식으로 쓰라는 것은 한번쯤 들어봤을 것이다.

context 네임스페이스를 쓰면 더욱 정의하기 편하다. 다음과 같이 적어주면 내부적으로 PropertyPlaceholderConfigurer가 만들어지고 모든 ${}로 정의된 빈의 프로퍼티 값을 프로퍼티 파일의 바꿔치기 해준다.

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

 

프로퍼티 값은 애노테이션을 쓴다면 @Value에 의해서 지정할 수 있다.

그렇다면 다음과 같은 코드도 만들어 볼 수 있지 않을까?

public class BeanSP {

    @Value("#{systemProperties['os.name']}")

    String name;

    @Value("${database.username}")

    String username;

    @Bean

    public PropertyPlaceholderConfigurer databaseProperty() {

        PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();

        ppc.setLocation(new ClassPathResource("database.properties", getClass()));

        return ppc;

    }

}

name은 SpEL을 사용해서 다이나믹하게 시스템 프로퍼티 값을 가져온 것이다. SpEL이 알아서 동작하니 신경쓸 건 없고.

두번째 username은 PropertyPlaceholderConfigurer에 의해서 변경되는 전형적인 ${}을 가지고 있으니 적절한 PropertyPlaceholderConfigurer와 프로퍼티 파일만 만들어주면 DB접속이름으로 변경될 것이라고 기대한다.

보통 PropertyPlaceholderConfigurer은 XML에 정의하지만 XML없는 컨텍스트를 사용하기 위해서 @Bean을 붙여 자바코드에 의한 빈 설정 기능을 사용했다. 이제 BeanSP만 빈으로 등록해주면 @Bean이 붙은 databaseProperty() 메소드에 의해서 PropertyPlaceholderConfigurer 타입의 빈이 등록될 것이고 그러면 당연히 username의 값도 변경될 것이라고 기대할 수 있다.

 

그러나 테스트 해보면 ${database.username}은 바뀌지 않는다. 파일도 잘 만들었을 뿐더러 확인해보면PropertyPlaceholderConfigurer 타입의 빈도 정상적으로 등록되어있다. XML로 바꿔서 등록하면 당연히 잘 된다.

그렇다면 @Bean을 사용해서 정의하면 왜 안될까?

 

이를 알려면 PropertyPlaceholderConfigurer @Bean애노테이션이 붙은 클래스를 스프링이 어떻게 처리하는지에 대한 두가지 지식이 모두 필요하다.

이 두가지 기능은 모두 BeanFactoryPostProcessor(BFPP)에 의해서 처리된다. BFPP는 빈 정의가 모두 준비되었을 때 진행하는 빈 팩토리 후처리기이다. 준비된 빈 정의 자체를 어떤 식으로든 바꿔치기 할 수 있기 때문에 상당히 유용하다.

PropertyPlaceholderConfigurer는 프로퍼티 파일의 속성과 빈 정의에 나온 프로퍼티 값을 비교해서 일치하면 프로퍼티 값 정의 자체를 바꿔주는 것이다. 그래서 ${database.name}이 user1과 같은 값 설정으로 바뀌고 나중에 빈을 만들면 이 빈 정의에 따라서 바뀐 값으로 만들어지는 것이다.

@Bean에 의해서 빈이 등록되는 것은 ConfigurationClassPostProcessor(CCPP) BFPP에 의해서 진행된다. 이 BFPP는 한 술 더 떠서 아예 새로운 빈 정의를 추가해준다. BFPP는 빈 정의를 어떤 식으로든 조작할 수 있는 강력한 기능을 가지도록 정의된 것으로 스프링은 이를 이용해서 많은 기능을 확장해왔다. 그래서 이 BFPP가 동작하는 동안에 @Bean이 붙은 빈 정의를 발견하면 그 내용을 토대로 새로운 빈 정의를 추가해준다.

자.. 문제는 BFPP가 실행되는 시점이다.  순서를 잘 보자.

1. 먼저 위의 BeanSP가 빈으로 등록된다. XML이든 스캐닝이든, 직접 주입이든 암튼 컨텍스트의 빈 설정으로 등록된다. 이 때 아직 @Bean에 의해서 CCPP가 만들어지지 않았다.

2. 이미 ConfigurationClassPostProcessor는 등록되어있다. AnnotationConfigApplicationContext라면 초기화 과정에서 추가해줄 것이고, XML이라면 <context:annotation-config>에 의해서 추가되어있을 것이다.

3. 기본 빈 등록이 끝났으므로 등록된 빈 중에서 BFPP를 찾는다. 지금까지 빈으로 등록된 BFPP는 ConfigurationClassPostProcessor뿐이다.

4. 모든 BFPP를 실행한다. 따라서 CCPP가 후처리기로 동작한다. 이 때 @Bean 메소드를 발견하고 PropertyPlaceholderConfigurer 타입의 빈을 등록해준다.

5. 끝.

문제는 3과 4의 순서이다. @Bean에 의해서 등록되는 빈은 이미 3번 과정에서 BFPP를 모두 찾아서 BFPP에 대한 실행이 시작된 이후에 일어난다. 따라서 4번 과정에서 등록된 PropertyPlaceholderConfigurer은 다시 3번으로 돌아가서 컨텍스트가 실행할 BFPP의 후보로 추가될 수 없다.

따라서 PropertyPlaceholderConfigurer는 빈으로 등록되지만 BFPP로 실행되는 시점이 지난 후기 때문에 무용지물이다.

 

결론은 "BFPP 빈은 다른 BFPP(CCPP)에서 등록되게 만들면 안된다".

XML을 사용하지 않으면서 위의 문제를 풀려면 다음과 같이 아예 독립적으로 PropertyPlaceholderConfigurer를 만들어 초기부터 빈으로 바로 등록되게 하면 된다.

@Component

public class DatabasePropertyPlaceHolder extends PropertyPlaceholderConfigurer {

    public DatabasePropertyPlaceHolder() {

        this.setLocation(new ClassPathResource("database.properties", getClass()));

    }

}

비슷한 원리가 BeanPostProcessor에도 적용될 수 있다. BPP에서 빈을 등록할 일은 없겠지만 기존 프로퍼티 내용을 조작하려고 하려면 BPP의 순서를 잘 염두에 둬야 한다.

또, BFPP가 단지 시간순서의 문제라면 BFPP빈의 실행순서를 조정하는 것은 가능하다. priority나 order를 지정하면 우선적으로 실행될 BFPP를 지정할 수 있다.

AC가 빈 등록이후 초기화 하는 작업의 순서를 알고 싶다면 AbstractApplicationContext의 refresh() 메소드를 참고하면 된다. 구체적인 후처리 작업에 대해서는 BFPP와 BPP를 beanFactory에서 가져와 출력해보면 된다. 스프링 초보자가 아니라면 적어도 자신이 만든 설정에 의해서 어떤 BF, Bean 후처리기들이 동작하는지쯤은 알아야 한다.


    출처 - http://toby.epril.com/?cat=81&paged=8




    다른 대안으로

    org.jasypt.spring31.properties.EncryptablePropertyPlaceholderConfigurer.

    EncryptablePropertyPlaceholderConfigurer(StringEncryptor stringEncryptor) 

    를 사용할 수 있다.







    시대착오적인 설정 파일 *.properties를 버리자 프로그래밍


    Eclipse의 파일 Lock 문제를 찾으면서 처음에는 Properties Editor Plugin을 원망했다. 게다가 대체품으로 선택한 Eclipse-RBE는 다소 불편한 인터페이스에 자꾸 NullPointerException을 떨궈서 사람 짜증나게 만들기 까지.

    하지만 그러다가 곧 생각이 바뀌어 버렸다.

    진짜 문제는 우리가 설정 파일로 시대착오적인 *.properties를 사용하고 있다는 그 사실이다.

    자바의 *.proerties 파일은 텍스트 파일을 가장한 바이너리 파일이라고 보면 된다. 특수한 편집기나 변환기가 없으면 비영문권 사용자에게는 결코 텍스트 파일로써 다뤄질 수 없는 설정 파일 형식이다. 이런 것을 사용하면서 이를 편집하게 도와주는 플러그인을 탓한 것이다.

    지금은 XML이나 YML 같은 잘 정형화되어있고 어느 편집기에서나 열어서 볼 수 있는 훌륭한 설정 파일 포맷이 나온지 이미 몇 년이 지난 시점이다. 그런 와중에 properties를 사용하는 나 자신을 탓하지 않고 플러그인을 탓하다니. 습관이 무섭다.

    Java도 또한 이 문제점을 인지하고 Properties 객체를 XML 파일을 통해서 생성할 수 있게 만든지 이미 오래전이다. Properties XML은 형식도 매우 직관적이다. http://wiki.kwonnam.pe.kr/java/properties 를 참조하자.

    게다가 더욱 훌륭한 것은 Spring Framework를 사용할 경우 <context:property-placeholder/>, <util:properties/>, 그리고 메시지 Resource Bundle 등이 모두 다 이 XML 형식의 프라퍼티를 완벽하게 지원한다는 것이다.

    그래서 오늘은 특정 디렉토리의 모든 *.properties를 XML Properties로 변환해주는 간단한 툴을 만들고, Spring Bean 설정 파일에서 properties를 사용하는 부분들을 모두 XML 파일을 가리키도록 고쳐서 팀 프로젝트의 모든 *.properties 파일을 없애버렸다. 생각보다 오래걸리지도 않았다. 그리고 log4j.properties도 삭제해 버리고 log4j.xml로 전면 교체해 버렸다.

    다른 Java 개발자들에게도 꼭 권한다. 프로젝트에서 *.properties를 삭제해 버리자. 어렵고 복잡한 일도 아니면서 설정 파일 읽기와  편집 효율성이 훨씬 더 증대된다.

    그리고 하면서 느끼게 된 사실이 또 있는데, Spring의 <context:property-placeholder/> 사용하지 말자. 이일민씨의 블로그 글에도 보면 되도록 <util:properties/>와 SpEL을 사용하자는 것이 있는데. 이번에 수정하면서 약간은 다른 이유로 매우 동감하게 되었다.

    properties-placeholder는 부모 자식 관계에 있는 Application Context XML 설정파일들의 경우에도 동일한 properties 파일임에도 모든 Bean 설정 파일에 property-placeholder를 명시해줘야 하는 문제가 있다.

    하지만 <util:properties/>를 사용하게 되면 부모 컨텍스트에 딱 한번만 설정하고 그 외에는 SpEL로 명시해주면 된다. 그 외에도 SpEL을 쓰게 되면 어노테이션 기반 객체 주입시에도 사용이 가능하지만 properties-placeholder는 억지로 String 등의 Bean 객체로 생성하지 않는 이상 어노테이션에서 직접 읽을 방법이 없다(내가 아는한. 있다면 좀 댓글로 알려주세요).

    따라서 나는 <context:property-placeholder/>를 버리고 <util:properties/>와 SpEL 사용을 강력하게 권한다.


    출처 - http://kwon37xi.egloos.com/4665590








    스프링에서 스캔된 컴포넌트에 외부 Property 값 주입하기

    프로그래밍 이야기박성철 2009/05/05 05:09

    서설

    스프링 2.5에 새로 추가된 기능 중 예상 외로 인기 있는 기능이 컴포넌트 스캔이다. Classpath를 따라서 탐색 하다가 정해진 필터링 규칙에 맞는 스프링 빈을 찾아 컨테이너에 등록해주는 기능인데 처음 들었을 때 잘 못 쓰면 독이 되겠다 싶었다. Autowiring은 몰라도 최소한 어떤 빈이 사용되는지는 XML에 등록해 주는 것이 관리 하기 좋지 않겠냐는 생각이었다. 빈 등록까지 자동으로 해버리면 애플리케이션 구성을 추적하기 어려워 유지보수가 곤란할 것이기 때문이다. 결국 나는 서비스 계층 후면은 XML을 사용한 전통적인 빈 설정으로, Web Tier는 빈 스캐닝과 Autowiring을 이용하는 방식으로 용처를 정리해서 사용하고 있다. 

    하지만 내가 결정권이 없는 여러 프로젝트에서 빈 스캐닝 기능을 사용하는 일이 많은 것이 현실이다. 그리고 절대 저질러서는 안 되는 범죄도 아니고 대부분 그리 복잡한 애플리케이션도 아니니 잘못 되었다고 말할 것도 아니다. 결국 두어 번 이런 상황에서 일을 하게 되었다. (사실 XML로 설정을 한다고 해도 관리가 허술하면 복잡하기는 마찮가지다. 설정 관리자라는 롤이라도 두어야 하는 걸까?)

    이런 저런 것 떠나서 빈 스캔 기능을 사용하면 가장 곤란한 것이 특정 값을 빈에 설정할 수 없다는 것이다. 스프링은 단순히 빈 간의 종속성 문제만 해결해줄 뿐 아니라 빈의 초기 상태를 설정할 수도 있다. 그런데 XML이 아닌 방법으로 스프링에 의존하지 않고 POJO의 순수성을 유지한채 특정 값을 설정하는 방법이 마땅치 않다. (적어도 내 지식으로는 그렇다.)

    애노테이션 환경에서 Property 값 설정

    PropertyInjectionConfigurer는 스프링의 PropertyPlaceholderConfigurer나 PropertyOverrideConfigurer와 비슷한 기능을 한다. 즉, 빈 초기화 단계에서 지정된 Property 파일의 값을 특정 빈의 필드에 넣거나 특정 메소드를 이 값을 가지고 호출한다. 다만 XML 설정이 아닌 애노테이션 방식을 사용한다는 것이 다르다. @Property 애노테이션을 Property 값을 받기 원하는 필드나 메소드에는 달아 놓으면 PropertyInjectionConfigurer가 빈 초기화 단계에 이 애노테이션을 인식해서 원하는 값을 찾아 설정을 해준다.

    물론 지금 1.0 M4 상태인 Spring JavaConfig나 1.0 M3 상태인 스프링 3가 정식 발표 된다면 문제가 안 되는 부분일 수 있다. 그러니 스프링 2.5만을 사용하고 JavaConfig를 기다릴 수 없는 (또는 JavaConfig를 사용 안 할) 프로젝트에만 사용하면 될 듯 하다. 그리고 JavaConfig의 @ExternalValue가 맘에 안 드는 사람도 있을 듯 하다.

    파일 다운로드

    property_injection.tar.gz

    소스 파일을 이클립스에서 Java 프로젝트(Spring IDE plugins가 설치되어 있으면 spring project)로 Import해서 열면 바로 사용이 가능하다. 일단 처음에는 테스트가 있으니 작동 유무를 이를 통해서 확인 한다.

    기존에 프로젝트 코드 베이스에 포함되어 있던 클래스 하나만 분리해서 만든 프로젝트이니 굳이 jar로 만들어 쓰기 보다는 그냥 사용할 프로젝트의 코드 베이스에 복사해서 패키지를 적절하게 변경한 다음에 사용하는 것이 좋을 듯 하다.

    설정

    가장 먼저 PropertyInjectionConfigurer를 XML에 등록한다.

        <bean class="property_injection.PropertyInjectionConfigurer">
            <property name="location" value="property_injection/test.properties"/>
        </bean>

    location 속성에 설정 값이 등록되어 있는 propertis 파일의 경로를 지정한다. 만약 properties 파일이 여러개라면 이렇게 할 수도 있다.

        <bean class="property_injection.PropertyInjectionConfigurer">
            <property name="locations">
                <list>
                    <value>classpath:property_injection/test.properties</value>
                    <value>classpath:property_injection/another.properties</value>
                </list>
            </property>
        </bean>

    물론 간단히 직접 Properties 값을 주는 것도 가능하다.

        <bean class="property_injection.PropertyInjectionConfigurer">
            <property name="properties">
                <value>
                    mail.server=mail.domain.net
                    mail.sender=admin@domain.net
                </value>
           </property>
        </bean>

    설정 방법은 PropertyPlaceholderConfigurer와 같으니 레퍼런스를 참조하도록 한다.

    필드에 @Property 사용

    일단 PropertyInjectionConfigurer를 등록했으면 Property 값을 지정하고 싶은 필드나 메소드에 @Property 애노테이션을 표기해야 한다. 직접 필드에 값을 지정하도록 할 수 있다.

        @Property(name="mail.server")
        private String mailServer;

    메소드에 @Property 사용

    메서드에 @Property를 달아 놓으면 초기화 단계에 해당 메소드를 호출한다. 이 메소드는 매개 변수가 하나 이상이어야 한다.
        
        @Property(name="mail.server")
        public void setMailServer(String mailServer) {
            this.mailServer = mailServer;
        }

    필수가 아닌 Property

    만약 지정한 Property 값을 찾지 못하면 예외를 발생시키기 때문에 빈 생성을 하지 못하게 된다. Property 값이 있어도 되고 없어도 되는 값이라면 required 속성를 false 값으로 바꾸어 예외 발생 없이 넘어가도록 할 수 있다.

      @Property(name="property.name" required=false)

    required 속성을 사용할 때에는 기본값을 필드에 지정하는 것이 좋을 것이다.

      @Property(name="mail.server" required=false)
      private String mailServer = "default.mailserver.com";


    출처 - http://gyumee.egloos.com/m/2369765








    Posted by linuxism
    ,


    2.Spring Scheduling

    본 장에서는 TaskExecutor와 TaskScheduler에 대해 간단히 살펴볼 것이다. 또한 XML과 Annotation 기반에서 이들을 사용하는 방법에 대해 알아보도록 하자.

    2.1.TaskExecutor

    TaskExecutor는 java.util.concurrent.Executor를 extends하여 정의된 인터페이스로써 지정된 Task를 실행시키기 위한 execute(Runnable task) 메소드를 정의하고 있다. Spring에서는 다양한 TaskExecutor 구현체를 제공하고 있으며 각 구현체에 대해서는 Spring Documentation 내의 TaskExecutor types를 참조하도록 한다.

    Spring에서 제공하는 TaskExecutor를 사용하여 특정 Task를 실행시키는 TaskExecutor를 개발하기 위해서는 XML 기반으로 Spring TaskExecutor의 속성을 정의한 후, 구현 대상이 되는 TaskExecutor에서 정의된 Spring TaskExecutor를 Inject하여 사용하면 된다. 해당 TaskExecutor 내에서는 Inject한 Spring TaskExecutor의 execute() 메소드를 호출함으로써 Task를 실행할 수 있으며 Task Execution을 위한 Rule은 자체적으로 구현해야 한다. 한편 Thread 형태로 구현된 Task는 Spring TaskExecutor 구현체의 특성에 맞게 Thread Pool에 관리될 것이다.

    public class PrintTaskExecutor {
        private TaskExecutor executor;
    
        public PrintTaskExecutor(TaskExecutor taskExecutor) {
            this.executor = taskExecutor;
        }
    
        public void print() {
            for (int i = 0; i < 3; i++) {
                executor.execute(new Task(i));
            }
        }
    
        private class Task implements Runnable {
            private int no;
    
            public Task(int no) {
                this.no = no;
            }
       
            public void run() {
                System.out.println("execute a Task" + no + " at " + new Date()
                    + " with TaskExecutor");
            }
        }
    }

    위 코드는 print() 메소드 내에서 Spring TaskExecutor를 활용하여 Inner 클래스로 정의된 Task를 실행하는 PrintTaskExecutor의 일부이다. PringTaskExecutor의 print() 메소드를 호출하면 Thread 유형의 내부 Task에 구현된 run() 메소드가 3회 실행되는 것을 확인할 수 있을 것이다.

    다음은 위에서 언급한 PrintTaskExecutor에 대한 속성 정의 내용의 일부이다.

    <task:executor id="executor" pool-size="4" queue-capacity="4" rejection-policy="ABORT"/>
    	
    <bean id="task" class="anyframe.sample.scheduling.task.executor.PrintTaskExecutor">
        <constructor-arg ref="executor"/>
    </bean>

    위에서 언급한 PrintTaskExecutor 샘플 코드는 본 섹션 내의 다운로드 - anyframe.sample.scheduling을 통해 다운로드받을 수 있다.

    2.2.TaskScheduler

    다음은 Spring 3에서 새롭게 제공하고 있는 org.springframework.scheduling.TaskScheduler 클래스의 일부 내용이다.

    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);
    }

    TaskScheduler는 Execution 대상이 되는 Task를 특정 시점 이후에 한 번 실행하거나 fixedRate 또는 fixedDelay 정보를 기반으로 주기적으로 실행할 수 있는 메소드를 제공하고 있다.

    2.3.XML based Scheduling

    Spring 3에서는 앞서 언급한 TaskExecutor(<task:executor/>)나 TaskScheduler(<task:scheduler/>)에 대한 속성 정의를 위해 task라는 Namespace를 제공한다. 또한 이를 이용하면 간편하게 Task Scheduling(<task:scheduled-task/>)을 위한 속성을 정의할 수 있게 된다. task Namespace를 사용하기 위해서는 해당 XML 파일 내의 <beans> 정의시 spring-task.xsd를 선언해 주어야 한다.

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:task="http://www.springframework.org/schema/task"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/task 
            http://www.springframework.org/schema/task/spring-task.xsd">		
        ...
    </beans>

    다음은 <task:scheduler/>를 사용한 속성 정의의 일부이다. 다음과 같이 속성을 정의한 경우 정의된 Pool Size를 기반으로 ThreadPoolTaskScheduler 인스턴스가 생성될 것이다. 정의된 id는 Pool에 관리될 Task Thread의 Prefix로 사용된다.

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

    다음은 <task:executor/>를 사용한 속성 정의의 일부이다. 다음과 같이 속성을 정의한 경우 ThreadPoolTaskExecutor 인스턴스가 생성될 것이다. 또한 정의된 id는 Pool에 관리될 Task Thread의 Prefix로 사용된다.

    <task:executor id="executor" pool-size="4" queue-capacity="4" rejection-policy="ABORT"/>

    <task:executor/>는 <task:scheduler/>에 비해 다양한 속성 정의를 지원한다. 다음에서는 정의 가능한 속성들에 대해 자세히 살펴보도록 하자.

    AttributeDescription
    pool-sizeThread Pool의 Size를 결정한다. 단일값으로 정의하였을 경우 Pool Size가 정의된 크기로 고정된다. min-max 형태로 정의하였을 경우 Pool Size의 범위가 지정된다.
    queue-capacity

    현재 실행중인 Thread의 개수가 지정된 최소 Pool Size보다 작을 경우, TaskExecutor는 실행 대상 Task에 대해 Free Thread를 사용한다. 점점 실행 대상 Task가 증가하여 현재 실행중인 Thread의 개수가 지정된 최소 Pool Size와 같아지는 경우, 실행 대상 Task는 Queue에 추가된다. 이 때 추가 가능한 Task의 개수는 queue-capacity와 동일하다. 정의된 Queue Capacity를 모두 사용하게 된다면 TaskExecutor는 실행 대상 Task에 대해 New Thread를 생성하여 Pool에 추가하게 된다. 현재 실행중인 Thread의 개수가 지정된 최대 Pool Size를 초과하는 경우 비로소 TaskExecutor는 해당 Task 실행을 거부하게 된다. 이와 같이 pool-size는 queue-capacity와 같이 고려되어야 하는 속성 정보이며 pool-size와 queue-capacity의 상관 관계에 대한 자세한 정보는 ThreadPoolExecutor API를 참조하도록 한다.

    queue-capacity 값을 정의하지 않는 경우 한계값이 정해지지 않으나 이 경우 너무 많은 실행 대상 Task가 Queuing 됨으로 인해 OutOfMemoryErrors를 초래할 수 있음에 유의하도록 한다. 또한 Queue Capacity에 대한 최대값이 존재하지 않으므로 Queue가 Full이 되는 상태가 발생하지 않아 결국 최대 Pool Size 또한 의미가 없어지게 된다.

    keep-alive최소 Pool Size 초과로 생성된 Inactive Thread에 대해 keep-alive 값으로 지정한 시간이 지난 후에 timeout된다. 만일 TaskExecutor의 pool-size가 범위로 정의되어 있고, queue-capacity가 정의되어 있지 않는다면, Pool Size가 최소 크기를 넘지 않았을 경우라도 해당 Pool에 포함된 Inactive Thread에 대해서 timeout을 적용하게 된다. (초 단위로 지정 가능)
    rejection-policy기본적으로 Task 실행이 거부되었을 경우 TaskExecutor는 TaskRejectedException을 throw하게 된다. 그러나 rejection-policy 값을 다음과 같이 정의하는 경우 정의된 Policy에 의해 다른 결과를 보여줄 수 있다.
    • ABORT : AbortPolicy 적용. rejection-policy가 정의되지 않았을 경우 기본 적용되는 Policy로 Exception을 throw한다.

    • CALLER_RUNS : CallerRunsPolicy 적용. 해당 Application이 과부하 상태일 경우 TaskExecutor에 의해서가 아닌 Thread에서 직접 Task를 실행시킬 수 있게 한다.

    • DISCARD : DiscardPolicy 적용. 모든 Task가 반드시 실행되어야 한다라는 제약점이 없는 경우 적용 가능한 Policy로써 해당 Application이 과부하 상태일 경우 현재 Task의 실행을 Skip한다.

    • DISCARD_OLDEST : DiscardOldestPolicy 적용. 모든 Task가 반드시 실행되어야 한다라는 제약점이 없는 경우 적용 가능한 Policy로써 해당 Application이 과부하 상태일 경우 Queue의 Head에 있는 Task의 실행을 Skip한다.

    다음은 task Namespace의 가장 강력한 특징인 <task:scheduled-task/>를 사용한 속성 정의의 일부이다. <task:scheduled-task/>는 기본적으로 'scheduler'라는 속성을 가지고 있는데 이것은 내부에 정의된 Task를 Scheduling하기 위한 TaskScheduler Bean을 정의하기 위한 것이다. <task:scheduled-task/>는 하위에 다수의 <task:scheduled/>를 포함할 수 있으며 <task:scheduled/>의 'ref'와 'method'는 실행 대상이 되는 Bean과 해당 Bean 내에 포함된 실행 대상 메소드를 정의하기 위한 속성이다.

    <task:scheduled-tasks scheduler="scheduler">
        <task:scheduled ref="task" method="printWithFixedDelay" fixed-delay="5000"/>
        <task:scheduled ref="task" method="printWithFixedRate" fixed-rate="10000"/>
        <task:scheduled ref="task" method="printWithCron" cron="*/8 * * * * MON-FRI"/>
    </task:scheduled-tasks>
    
    <task:scheduler id="scheduler" pool-size="10"/>

    <task:scheduled/>는 'ref', 'method' 외에 Scheduling을 위해 필요한 속성을 가지는데 각각에 대해 알아보면 다음과 같다.

    AttributeDescription
    cron

    Cron Expression을 이용하여 Task 실행 주기 정의.

    Cron Expression은 6개의 Field로 구성되며 각 Field는 순서대로 second, minute, hour, day, month, weekday를 의미한다. 각 Field의 구분은 Space로 한다. 또한 month와 weekday는 영어로 된 단어의 처음 3개의 문자로 정의할 수 있다.

    • 0 0 * * * * : 매일 매시 시작 시점

    • */10 * * * * * : 10초 간격

    • 0 0 8-10 * * * : 매일 8,9,10시

    • 0 0/30 8-10 * * * : 매일 8:00, 8:30, 9:00, 9:30, 10:00

    • 0 0 9-17 * * MON-FRI : 주중 9시부터 17시까지

    • 0 0 0 25 12 ? : 매년 크리스마스 자정

    * org.springframework.scheduling.support.CronSequenceGenerator API 참조

    fixed-delay이전에 실행된 Task의 종료 시간으로부터의 fixed-delay로 정의한 시간만큼 소비한 이후 Task 실행. (Milliseconds 단위로 정의)
    fixed-rate이전에 실행된 Task의 시작 시간으로부터 fixed-rate로 정의한 시간만큼 소비한 이후 Task 실행. (Milliseconds 단위로 정의)

    위에서 언급한 PrintTaskExecutor 샘플 코드는 본 섹션 내의 다운로드 - anyframe.sample.scheduling을 통해 다운로드받을 수 있다.

    2.4.Annotation based Scheduling & Asynchronous Execution

    Spring 3에서는 Task Scheduling(@Scheduled)과 Aynchronous Task Execution(@Async)을 위한 Annotation을 제공한다. 이 Annotation들을 인식할 수 있도록 하기 위해서는 다음과 같은 속성 정의가 추가되어야 한다.

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

    다음에서는 @Scheduled와 @Async Annotation에 대해 살펴보기로 하자.

    2.4.1.Scheduling

    @Scheduled는 메소드 단위로 사용 가능하며 실행 주기 정의를 위한 'fixedDelay', 'fixedRate', 'cron'과 같은 속성들을 제공하고 있다. 각 속성의 의미는 XML based Scheduling에서 언급한 <task:scheduled/> 속성과 동일한 의미를 가진다. @Scheduled 메소드에 대한 실행은 TaskScheduler가 담당한다.

    @Scheduled(fixedDelay=5000)
    public void printWithFixedDelay() {
        System.out.println("execute printWithFixedDelay() of Annotated PrintTask at " 
            + new Date());
    }
    
    @Scheduled(fixedRate=10000)
    public void printWithFixedRate() {
        System.out.println("execute printWithFixedRate() of Annotated PrintTask at " 
            + new Date());
    }	
    
    @Scheduled(cron="*/8 * * * * MON-FRI")
    public void printWithCron() {
        System.out.println("execute printWithCron() of Annotated PrintTask at " 
            + new Date());
    }

    @Scheduled Annotation을 부여한 메소드는 입력 인자를 갖지 않고, Return 값이 없어야 함에 유의하도록 한다. 또한 메소드 로직 실행을 위해 다른 Bean을 참조로 해야 한다면 Dependency Injection에 의해 처리하도록 한다.

    2.4.2.Asynchronous Execution

    @Async는 메소드 단위로 사용 가능하며 비동기적으로 특정 메소드를 실행하고자 할 때 사용할 수 있다. @Async 메소드에 대한 실제적인 실행은 TaskExecutor에 의해 처리된다.

    @Async
    public void printWithAsync() throws Exception {
        System.out.println("execute printWithAsync() of AsyncPrintTask at "	
            + new Date());
        Thread.sleep(5000);
    }
    
    @Async
    public void printWithArg(int i) throws Exception {
        System.out.println("execute printWithArg(" + i + ") of AsyncPrintTask at " 
            + new Date());
        Thread.sleep(5000);
    }
    
    @Async
    public Future<String> returnVal(int i) throws Exception {
        System.out.println("execute returnVal() of AsyncPrintTask");
        Date current = new Date();
        Thread.sleep(5000);
        return new AsyncResult<String>(Integer.toString(i));
    }

    위 코드에서와 같이 @Async 메소드는 @Scheduled 메소드와 다르게 입력 인자나 리턴값을 가질 수 있다. @Scheduled의 경우에는 Spring Container에 의해 관리되는 반면에 @Async는 Caller에 의해 직접 호출되기 때문이다. 단, 리턴값의 경우 Future 타입 형태만 가능하며 Caller는 비동기적으로 실행 종료된 메소드의 결과를 Future 객체의 get() 메소드를 통해 알아낼 수 있다.

    위에서 언급한 PrintTaskExecutor 샘플 코드는 본 섹션 내의 다운로드 - anyframe.sample.scheduling을 통해 다운로드받을 수 있다.




    출처 - http://dev.anyframejava.org/docs/anyframe/plugin/scheduling/4.5.2/reference/html/ch02.html








    Spring 2.0은 실행자(Executor)를 다루기 위해 새로운 추상화를 소개한다. 실행자(Executor)는 쓰레드 풀링의 개념을 위한 Java 5의 이름이다. 뜻밖의(odd) 명명은 참조하는 구현물이 실제로 풀(pool)인것을 보증하지 않는 사실에 기반한다. 사실, 많은 경우, 실행자(executor)는 쓰레드 하나로 이루어진다. Spring의 추상화는 1.3, 1.4, 5와 Java EE환경간의 상세한 구현을 감추는 만큼 Java 1.3, 1.4환경에 쓰레드 풀링을 적용하도록 도와준다.

    1. TaskExecutor 인터페이스
      Spring의 TaskExecutor 인터페이스는 java.util.concurrent.Executor에 일치한다. 사실, 이것이 존재하는 가장 큰 이유는 쓰레드 풀을 사용할때 Java 5에 필요한 추상화이다. 인터페이스는 의미론적인 수행과 쓰레드 풀의 설정을 위한 작업을 받는 하나의 메소드인 execute(Runnable task)를 가진다.
    2. TaskExecutor를 사용하는 곳
      TaskExecutor는 필요한 쓰레드 풀링을 위한 추상화에 다른 Spring컴포넌트를 주기 위해 생성되었다. ApplicationEventMulticaster와 같은 컴포넌트인 JMS의 AbstractMessageListenerContainer와 Quartz통합은 모두 풀 쓰레드를 위해 TaskExecutor 추상화를 사용한다. 어쨌뜬, bean이 쓰레드 풀링이 필요하다면, 자체적인 필요를 위해 이 추상화를 사용하는 것이 가능하다.
    3. TaskExecutor 타입들
      Spring배포판에 포함된 TaskExecutor의 미리 빌드된 많은 구현물이 있다. 모든 가능성에서, 자체적으로 구현할 필요는 없을것이다.
      • SimpleAsyncTaskExecutor
        이 구현물은 어떤 쓰레드도 재사용하지 않는다. 각각의 호출에 새로운 쓰레드를 시작한다. 어쨌뜬, 슬롯이 자유로워질때까지 제한을 넘어선 호출은 블럭할 동시 제한을 지원한다. 진정한 풀링을 찾는다면, 페이지 아래로 스크롤하라.
      • SyncTaskExecutor
        이 구현물은 호출을 비동기적으로 수행하지 않는다. 대신, 각각의 호출은 호출 쓰레드로 대체된다. 간단한 테스트케이스와 같이 필요하지 않은 멀티쓰레드 상황에서 사용된다.
      • ConcurrentTaskExecutor
        이 구현물은 Java 5 java.util.concurrent.Executor를 위한 래퍼(wrapper)이다. 대안으로 bean프라퍼티로 Executor 설정 파라미터를 나타내는 ThreadPoolTaskExecutor가 있다. ConcurrentTaskExecutor를 사용할 필요는 드물지만 ThreadPoolTaskExecutor가 필요한 것만큼 충분히 견고하지 않다면, ConcurrentTaskExecutor이 대안이 된다.
      • SimpleThreadPoolTaskExecutor
        이 구현물은 실제로 Spring의 생명주기 콜백을 듣는 Quartz의 SimpleThreadPool의 하위클래스이다. 이것은 Quartz와 Quartz가 아닌 컴포넌트간에 공유될필요가 있는 쓰레드 풀을 가질때 사용된다.
      • ThreadPoolTaskExecutor
        이 구현물은 Java 5 환경에서 사용될수 있지만 그 환경에서 가장 공통적으로 사용되는 것이다. java.util.concurrent.ThreadPoolExecutor를 설정하고 TaskExecutor에 그것을 포장하기 위한 bean프라퍼티를 나타낸다. ScheduledThreadPoolExecutor처럼 향상된 어떤것이 필요하다면, 대신 ConcurrentTaskExecutor를 사용하도록 추천한다.
      • TimerTaskExecutor
        이 구현물은 지원 구현물로 하나의 TimerTask를 사용한다. 이것은 비록 쓰레드에서 동기적이더라도 메소드 호출이 개별 쓰레드에서 수행되어 SyncTaskExecutor와 다르다.
      • WorkManagerTaskExecutor
        이 구현물은 지원 구현물로 CommonJ WorkManager을 사용하고 Spring 컨텍스트에서 CommonJ WorkManager참조를 셋팅하기 위한 중심적이고 편리한 클래스이다. SimpleThreadPoolTaskExecutor와 유사하게, 이 클래스는 WorkManager인터페이스를 구현하고 WorkManager만큼 직접 사용될수 있다.
    4. TaskExecutor 사용하기
      Spring의 TaskExecutor 구현물은 간단한 JavaBean처럼 사용된다. 아래의 예제에서, 우리는 메시지의 세트를 비동기적으로 프린트하기 위한 ThreadPoolTaskExecutor을 사용하는 bean을 정의한다.
      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));
          }
        }
      
      }

      당신이 볼수 있는것처럼, 풀에서 쓰레드를 가져오고 스스로 수행하는 것보다, 큐에 Runnable를 추가하고 TaskExecutor는 작업이 수행될때 결정할 내부 규칙을 사용한다.

    TaskExecutor가 사용할 규칙을 설정하기 위해, 간단한 bean프라퍼티로 나타낸다.

    /WEB-INF/classes/applicationContext.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>

    TaskExecutor 사용 예제에서는 TaskExecutorExample 클래스안에 MessagePrinterTask 객체를 inner class로 구현하였지만, 본 개발에서는 UserSyncScheduler와 userSyncTaskExecutor라는 것을 분리하여 각각의 class로 TaskExecutor와 Scheduler역할을 하는 객체를 분리하여 구현한다. 자세한 내용은 다음의 환경세팅 파일을 보듯이 작성을 하고, 실제 구현한 소스 파일를 참조하기 바란다.

    Scheduler와 조합한 ThreadPoolTaskExecutor /WEB-INF/classes/applicationContext.xml
    <bean id="userSyncTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
      <property name="corePoolSize" value="5" />
      <property name="maxPoolSize" value="10" />
      <property name="queueCapacity" value="25" />
    </bean>
    
    <bean id="userSyncGetterJob" class="org.springframework.scheduling.quartz.JobDetailBean">
        <property name="jobClass" value="com.nate.sims.async.scheduling.UserSyncScheduler" />
        <property name="jobDataAsMap">
            <map>
                <entry key="service">
                    <ref bean="aSyncService"/><!-- aSyncService set 해준다. -->
                </entry>
                <entry key="executor">
                    <ref bean="userSyncTaskExecutor"/><!-- userSyncTaskExecutor set 해준다. -->
                </entry>
            </map>
        </property>
    </bean>
    
    <!-- 스케쥴타임설정 -->
    <bean id="cronUserSyncTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
        <property name="jobDetail" ref="userSyncGetterJob"/>
        <property name="cronExpression" value="0 * * * * ?" />
    </bean>
    
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="cronUserSyncTrigger"/>
            </list>
        </property>
    </bean>


    출처 - http://blog.naver.com/PostView.nhn?blogId=ez2sarang&logNo=40060102403



    Posted by linuxism
    ,

    java - Executor

    Development/Java 2012. 11. 16. 18:31


    자바 5부터 새롭게 추가된 Concurrency API 중에서 Executor, 리턴이 가능한 Callable 및 Future에 대해서 살펴본다.

    Executor를 이용한 쓰레드 관리

    웹 서버와 같이 동시에 다수의 요청을 처리해야 하는 어플리케이션을 개발해야 할 경우 코드는 다음과 같은 형태를 띌 것이다.

    while(true) {
        request = acceptRequest();
        Runnable requestHandler = new RequestHandler(request);
        new Thread(requestHandler).start();
    }

    위 코드가 논리적으로 문제점은 없지만, 다음과 같은 성능상의 문제점을 안고 있다.

    • 소규모의 많은 요청이 들어올 경우 쓰레드 생성 및 종료에 따른 오버헤드가 발생한다.
    • 생성되는 쓰레드 개수에 제한이 없기 때문에 OutOfMemoryError가 발생할 수 있다.
    • 많은 수의 쓰레드가 실행될 경우, 쓰레드 스케줄링에 따른 오버헤드가 발생한다.
    이런 문제점 때문에 동시에 다수의 요청을 처리해야 하는 어플리케이션에서는 쓰레드 풀을 사용하여 동시에 실행될 수 있는 쓰레드의 개수를 제한하는 것이 일반적이다.

    자바5부터 새롭게 추가된 Concurrency API는 작업을 실행하기 위한 Executor 인터페이스를 제공하고 있으며, 쓰레드 풀, 큐 등 다양한 Executor 구현체를 제공하고 있다. 따라서, 앞서 Thread를 직접 사용할 때의 문제점을 해소할 수 있게 되었다.

    Executor를 이용한 쓰레드 관리

    Executor와 관련된 주요 API는 다음과 같다.


    위 클래스 다이어그램에서 각 인터페이스는 다음과 같은 기능을 정의한다.

    • Executor 인터페이스:
      제공된 작업(Runnable 구현체)을 실행하는 객체가 구현해야 할 인터페이스. 이 인터페이스는 작업을 제공하는 코드와 작업을 실행하는 메커니즘의 사이의 커플링을 제거해준다.
    • ExecutorService 인터페이스:
      Executor의 라이프사이클을 관리할 수 있는 기능을 정의하고 있다. Runnable 뿐만 아니라 Callable을 작업으로 사용할 수 있는 메소드가 추가로 제공된다.
    • ScheduledExecutorService:
      지정한 스케쥴에 따라 작업을 수행할 수 있는 기능이 추가되었다.
    Executor 인터페이스

    java.util.concurrent.Executor 인터페이스는 작업을 실행하는 클래스가 구현해야 할 인터페이스로서, 다음과 같이 정의되어 있다.

    public interface Executor {
        void execute(Runnable command);
    }

    Executor 인터페이스의 구현체는 execute() 메소드로 전달받은 작업(Runnable 인스턴스)을 알맞게 실행하게 된다. 예를 들어, 쓰레드 풀을 구현한 Executor 구현체는 전달받은 작업을 큐에 넣은 뒤 가용한 쓰레드가 존재할 경우, 해당 쓰레드에 작업을 실행하도록 구현될 것이다.

    아래 코드는 앞서 Thread를 사용했던 코드를 Executor를 사용하는 코드로 변환한 것이다.

    Executor executor = …; // Executor 구현체 생성
    while(true) {
        request = acceptRequest();
        Runnable requestHandler = new RequestHandler(request);
        executor.execute(requestHandler);
    }

    위 코드는 작업을 생성만 할 뿐 실제로 작업을 실행하지는 않는다. 단지, Executor.execute() 메소드에 생성한 작업을 전달할 뿐이다. 작업을 실제로 실행하는 책임은 Executor에 있으며, 이제 작업을 생성하는 코드에서는 작업이 어떻게 실행되는 지의 여부는 알 필요가 없다. 즉, Executor를 사용함으로써 작업을 생성하는 코드와 작업을 실행하는 메커니즘 사이의 커플링을 없앤 것이다.

    ExecutorService 인터페이스

    ExecutorService 인터페이스는 다음과 같이 두 가지 종류의 메소드가 추가되었다.

    • Executor의 라이프 사이클을 관리
    • Callable을 작업으로 사용하기 위한 메소드
    먼저, 라이프 사이클과 관련된 메소드는 다음과 같다.

    • void shutdown():
      셧다운 한다. 이미 Executor에 제공된 작업은 실행되지만, 새로운 작업은 수용하지 않는다.
    • List<Runnable> shutdownNow():
      현재 실행중인 모든 작업을 중지시키고, 대기중인 작업을 멈추고, 현재 실행되기 위해 대기중인 작업 목록을 리턴한다.
    • boolean isShutdown():
      Executor가 셧다운 되었는 지의 여부를 확인한다.
    • boolean isTerminated():
      셧다운 실행 후 모든 작업이 종료되었는 지의 여부를 확인한다.
    • boolean awaitTermination(long timeout, TimeUnit unit):
      셧다운을 실행한 뒤, 지정한 시간 동안 모든 작업이 종료될 때 까지 대기한다. 지정한 시간 이내에서 실행중인 모든 작업이 종료되면 true를 리턴하고, 여전히 실행중인 작업이 남아 있다면 false를 리턴한다.
    예를 들어, 웹 서버를 종료하게 되면 더 이상 클라이언트의 요청을 받아서는 안 되고, 기존에 처리중이던 요청은 지정한 시간내에서 처리해야 할 것이다. 이런 경우 ExecutorService를 사용하면 다음과 같이 종료 과정을 코딩할 수 있다.

    executor.shutdown();
    try {
        if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
            System.out.println("아직 처리중인 작업 존재");
            System.out.println("작업 강제 종료 실행");
            executor.shutdownNow();
            if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
                System.out.println("여전히 종료하지 않은 작업 존재");
            }
        }
    } catch (InterruptedException e1) {
        executor.shutdownNow();
        Thread.currentThread().interrupt();
    }
    System.out.println("서버 셧다운 완료");

    ExecutorService 인터페이스는 작업 수행과 관련해서 추가적으로 메소드를 제공하고 있으며, 이들 메소드는 다음과 같다.

    • <T> Future<T> submit(Callable<T> task)
      결과값을 리턴하는 작업을 추가한다.
    • Future<?> submit(Runnable task)
      결과값이 없는 작업을 추가한다.
    • <T> Future<T> submit(Runnable task, T result)
      새로운 작업을 추가한다. result는 작업이 성공적으로 수행될 때 사용될 리턴 값을 의미한다.
    • <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
      주어진 작업을 모두 실행한다. 각 실행 결과값을 구할 수 있는 Future의 List를 리턴한다.
    • <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
      앞서 invokeAll()과 동일하다. 지정한 시간 동안 완료되지 못한 작업은 취소되는 차이점이 있다.
    • <T> T invokeAny(Collection<? extends Callable<T>> tasks)
      작업울 수행하고, 작업 결과 중 성공적으로 완료된 것의 결과를 리턴한다. 정상적으로 수행된 결과가 발생하거나 예외가 발생하는 경우 나머지 완료되지 않은 작업은 취소된다.
    • <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
      invokeAny()와 동일하다. 지정한 시간 동안만 대기한다는 차이점이 있다.
    Callable은 Runnable과 비슷한데, 차이점이 있다면 결과 값을 리턴할 수 있다는 점이다. Future는 Callable의 결과값을 구하는 데 사용된다. Callable과 Future에 대해서는 뒤에서 자세히 살펴보도록 하자.

    ScheduledThreadPoolExecutor 클래스

    ScheduledThreadPoolExecutor 클래스는 ScheduledExecutorService 인터페이스을 구현한 클래스로서 스케줄링 기능을 제공한다. ScheduledThreadPoolExecutor 클래스는 또한 쓰레드 풀을 구현하고 있는 ThreadPoolExecutor 클래스를 상속받고 있기 때문에 쓰레드 풀 기능도 함께 제공한다.

    ScheduledThreadPoolExecutor 클래스에 대한 자세한 내용은 아래 사이트를 참고하기 바란다.

    • http://java.sun.com/javase/6/docs/api/java/util/concurrent/ScheduledThreadPoolExecutor.html
    Executors 유틸리티 클래스

    java.util.concurrent.Executors 클래스는 자바 5가 기본적으로 제공하는 Executor 구현체를 구할 수 있는 메소드를 제공하는 유틸리티이다. 예를 들어, 쓰레드 풀을 구현한 Executor 구현체를 구하고 싶다면 다음과 같은 코드를 사용하면 된다.

    Executor executor = Executors.newFixedThreadPool(THREADCOUNT);
    while(true) {
        request = acceptRequest();
        Runnable requestHandler = new RequestHandler(request);
        executor.execute(requestHandler);
    }

    Executors 클래스는 newFixedThreadPool()과 더불어 다음과 같은 메소드를 이용하여 Executor 인스턴스를 구할 수 있도록 하고 있다.

    • ExecutorService newFixedThreadPool(int nThreads)
      최대 지정한 개수 만큼의 쓰레드를 가질 수 있는 쓰레드 풀을 생성한다. 실제 생성되는 객체는 ThreadPoolExecutor 객체이다.
    • ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
      지정한 개수만큼 쓰레드가 유지되는 스케줄 가능한 쓰레드 풀을 생성한다. 실제 생성되는 객체는 ScheduledThreadPoolExecutor 객체이다.
    • ExecutorService newSingleThreadExecutor()
      하나의 쓰레드만 사용하는 ExecutorService를 생성한다.
    • ScheduledExecutorService newSingleThreadScheduledExecutor()
      하나의 쓰레드만 사용하는 ScheduledExecutorService를 생성한다.
    • ExecutorService newCachedThreadPool()
      필요할 때 마다 쓰레드를 생성하는 쓰레드 풀을 생성한다. 이미 생성된 쓰레드의 경우 재사용된다. 실제 생성되는 객체는 ThreadPoolExecutor 객체이다.
    Callable과 Future

    Runnable 인터페이스는 다음과 같이 정의되어 있다.

    public interface Runnable {
        public void run();
    }

    위 코드에서 볼 수 있듯이 Runnable.run() 메소드는 결과 값을 리턴하지 않기 때문에, run() 메소드의 실행 결과를 구하기 위해서는 공용 메모리나 파이프와 같은 것들을 사용해서 결과 값을 받아야만 했다. 이런 Runnable 인터페이스의 단점을 없애기 위해 추가된 것이 바로 Callable 인터페이스이다.

    java.util.concurrent.Callable 인터페이스는 다음과 같이 정의되어 있다.

    public Interface Callable<V> {
        V call() throws Exception
    }

    Callable 인터페이스의 call() 메소드는 결과 값을 리턴하도록 되어 있다. 또한, 자바 5부터 추가된 generic을 사용하여 어떤 타입이든 리턴 값으로 사용할 수 있도록 하였다. 예를 들어, 아래 코드는 Callable 인터페이스를 구현한 간단한 클래스를 보여주고 있다.

    public class CallableImpl implements Callable<Integer> {
        public Integer call() throws Exception {
            // 작업 처리
            return result;
        }
    }

    ExecutorService.submit() 메소드를 사용하면 CallableImpl 클래스를 작업으로 사용할 수 있다. 아래 코드는 ExecutorService.submit() 메소드의 실행 예이다.

    ExecutorService executor = Executors.newFixedThreadPool(THREADCOUNT);

    Future<Integer> future = executor.submit(new CallableImpl());
    Integer result = future.get();

    ExecutorService.submit() 메소드는 전달받은 Callable 객체를 내부 메커니즘에 따라 지정한 때에 실행한다. 예를 들어, 위 경우 CallableImpl 객체는 큐에 저장되었다가 가용한 쓰레드가 생길 때 CallblaImpl.call() 메소드가 실행될 것이다.

    Callable.call() 메소드가 ExecutorService.submit() 메소드에 전달될 때 곧 바로 실행되는 것이 아니기 때문에 리턴값을 바로 구할 수 없다. 이 리턴값은 미래의 어느 시점에 구할 수 있는데, 이렇게 미래에 실행되는 Callable의 수행 결과 값을 구할 때 사용되는 것이 Future이다. Future.get() 메소드는 Callable.call() 메소드의 실행이 완료될 때 까지 블록킹되며, Callable.call() 메소드의 실행이 완료되면 그 결과값을 리턴한다.

    Future 인터페이스는 get() 메소드 외에도 다음과 같은 메소드를 제공하고 있다.

    • V get()
      Callable 등 작업의 실행이 완료될 때 까지 블록킹 되며, 완료되면 그 결과값을 리턴한다.
    • V get(long timeout, TimeUnit unit)
      지정한 시간 동안 작업의 실행 결과를 기다린다. 지정한 시간 내에 수행이 완료되면 그 결과값을 리턴한다. 대기 시간이 초과되면 TimeoutException을 발생시킨다.
    • boolean cancel(boolean mayInterruptIfRunning)
      작업을 취소한다.
    • boolean isCancelled()
      작업이 정상적으로 완료되기 이전에 취소되었을 경우 true를 리턴한다.
    • boolean isDone()
      작업이 완료되었다면 true를 리턴한다.
    관련링크:


    출처 - http://javacan.tistory.com/entry/134









    'Development > Java' 카테고리의 다른 글

    java - war 파일 생성 및 풀기  (0) 2012.12.06
    java.lang.NoSuchMethodError  (0) 2012.11.22
    java - CGLIB(Code Generator Library) 코드 생성 라이브러리  (0) 2012.11.12
    java - jar  (0) 2012.11.09
    java 로드맵  (0) 2012.11.07
    Posted by linuxism
    ,