복붙노트

[SPRING] Spring 빈을 ExecutorService의 ThreadLocal 인스턴스처럼 동작하게 만들기

SPRING

Spring 빈을 ExecutorService의 ThreadLocal 인스턴스처럼 동작하게 만들기

내 웹 응용 프로그램에는 배경 서비스가 있습니다. 이 서비스는 Engine 클래스를 포함하는 Generator 클래스와 여러 스레드를 사용하도록 구성된 ExecutorService를 사용하며 GeneratorTasks를 허용합니다.

@Component
public class Generator {
    @Autowired
    private Engine heavyEngine;

    private ExecutorService exec = Executors.newFixedThreadPool(3);

    //I actually pass the singleton instance Generator class into the task.
    public void submitTask(TaskModel model, TaskCallback callback) {
        this.exec.submit(new GeneratorTask(model, this, callback));
    }
}

@Component
public class Engine {
    public Engine() {
        //time-consuming initialization code here
    }
}

public class GeneratorTask implements Callable<String> {
    public GeneratorTask(TaskModel m, Generator g, ReceiptCallback c) {
        this.m = m;
        this.generator = g;
        this.c = c;
    }

    public String call() throws Exception {
        //This actually calls the Engine class of the generator.
        //Maybe I should have passed the Engine itself?
        this.generator.runEngine(c);  
    }
}

Engine 클래스는 초기화하는 데 오랜 시간이 걸리므로 스레드 당 한 번만 초기화하는 것이 이상적입니다. 인스턴스를 여러 스레드에서 공유 할 수 없으므로이 인스턴스를 싱글 톤 인스턴스로 만들 수 없습니다 (순차 처리에 의존). 처리 작업이 완료된 후에도 인스턴스를 재사용해도 좋습니다.

개인 Engine heavyEngine 변수를 ThreadLocal 변수로 만들려고합니다. 그러나 Spring을 사용하는 ThreadLocal 변수를 삽입하는 또 다른 방법이 있는지 궁금합니다. 범위를 요청하기 위해 빈 범위를 조사했지만, 내 디자인이 주어진다면 어떻게해야하는지 잘 모르겠습니다.

내 디자인을 개선하는 방법에 대한 지침을 주시면 감사하겠습니다.

해결법

  1. ==============================

    1.우선 ThreadLocal을 포기합니다. - 그 클래스에는 무서운 것이 있습니다. 필요한 것은 객체 풀링뿐입니다. 그것은 잘 알려진 특징은 아니지만 Spring은 이것을 지원합니다 :

    우선 ThreadLocal을 포기합니다. - 그 클래스에는 무서운 것이 있습니다. 필요한 것은 객체 풀링뿐입니다. 그것은 잘 알려진 특징은 아니지만 Spring은 이것을 지원합니다 :

    <bean id="engineProto" class="Engine" scope="prototype" lazy-init="true"/>
    
    <bean id="engine" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="targetSource">
            <bean class="org.springframework.aop.target.CommonsPoolTargetSource">
                <property name="targetClass" value="Engine"/>
                <property name="targetBeanName" value="engineProto"/>
                <property name="maxSize" value="3"/>
                <property name="maxWait" value="5000"/>
            </bean>
        </property>
    </bean>
    

    이제 엔진을 삽입하면 풀에있는 무료 객체에 대한 모든 호출을 위임하는 프록시 객체 (엔진에는 인터페이스가 필요함)가 실제로 수신됩니다. 풀 크기는 구성 가능합니다. 물론 Commons Pool 대신 ThreadLocal을 사용하는 ThreadLocalTargetSource 사용을 방해하는 것은 없습니다. 두 가지 방법 모두 엔진에 대한 스레드 안전 액세스를 독점합니다.

    마지막으로 수동으로 풀링을 사용할 수 있지만 (위의 솔루션의 장점은 완전히 투명하다는 것입니다) 또는 정의에 따라 풀링 된 EJB로 전환하십시오.

  2. ==============================

    2.FYI, Spring 3.0 이상에는 스레드 기반 Scope 구현 인 SimpleThreadScope가 포함되어 있습니다.

    FYI, Spring 3.0 이상에는 스레드 기반 Scope 구현 인 SimpleThreadScope가 포함되어 있습니다.

    그것을 사용하려면 사용자 정의 범위를 등록해야합니다.

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

    그리고 나서 쓰레드 스코프 된 빈을 선언한다 :

    <bean id="myBean" class="com.foo.MyBean" scope="thread">
        ...
    </bean>
    
  3. ==============================

    3.Engine에 대한 팩토리를 생성하고 GeneratorTask 내에서 호출해야합니다. 이렇게하면 GeneratorTask에서 Generator 내의 heavyEngine 필드와 Generator 생성자 인수를 제거 할 수 있습니다. 그런 다음 엔진의 초기화 시간을 저장하려는 경우 여전히 싱글 톤으로 선언 할 수 있지만 thread가 아닌 메소드에서는 synchronized 키워드를 사용하십시오.

    Engine에 대한 팩토리를 생성하고 GeneratorTask 내에서 호출해야합니다. 이렇게하면 GeneratorTask에서 Generator 내의 heavyEngine 필드와 Generator 생성자 인수를 제거 할 수 있습니다. 그런 다음 엔진의 초기화 시간을 저장하려는 경우 여전히 싱글 톤으로 선언 할 수 있지만 thread가 아닌 메소드에서는 synchronized 키워드를 사용하십시오.

    public class Generator {    
        @Autowired private EngineFactory engineFactory;
        private ExecutorService exec = Executors.newFixedThreadPool(3);
    
        public void submitTask(TaskModel model, TaskCallback callback) {
            this.exec.submit(new GeneratorTask(engineFactory, model, callback));
        }
    }
    
    public class EngineFactory {
        @Autowired private Engine instance;
    
        public Engine getInstance() {
            return instance;
        }
    }
    
    public class Engine {
        public Engine() {
            //time-consuming initialization code here
        }
    
        public synchronized void runEngine() {
            // Do non thread safe stuf
        } 
    }
    
    public class GeneratorTask implements Callable<String> {
        public GeneratorTask(EngineFactory f, TaskModel m, ReceiptCallback c) {
            this.f = f;
            this.m = m;
            this.c = c;
        }
    
        public String call() throws Exception {
            Engine engine = f.getInstance();
            engine.runEngine();
            ... 
        }
    }
    

    아마도 Callable에 엔진을 전달하는 순수한 Spring 방법이 있지만,이 경우에는 공장이 제 의견으로는 충분합니다.

  4. from https://stackoverflow.com/questions/12613451/making-spring-beans-behave-like-threadlocal-instances-for-an-executorservice by cc-by-sa and MIT license