diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepScope.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepScope.java index 016b97c6e..75781f063 100644 --- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepScope.java +++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepScope.java @@ -30,11 +30,12 @@ import org.springframework.core.Ordered; * @author Dave Syer * */ -public class StepScope implements Scope, - BeanFactoryPostProcessor, Ordered { +public class StepScope implements Scope, BeanFactoryPostProcessor, Ordered { private int order = Ordered.LOWEST_PRECEDENCE; + private Object mutex = new Object(); + public void setOrder(int order) { this.order = order; } @@ -59,12 +60,16 @@ public class StepScope implements Scope, SimpleStepContext context = getContext(); Object scopedObject = context.getAttribute(name); if (scopedObject == null) { - scopedObject = objectFactory.getObject(); - context.setAttribute(name, scopedObject); - if (scopedObject instanceof StepContextAware) { - ((StepContextAware) scopedObject).setStepContext(context); + synchronized (mutex) { + scopedObject = context.getAttribute(name); + if (scopedObject == null) { + scopedObject = objectFactory.getObject(); + context.setAttribute(name, scopedObject); + if (scopedObject instanceof StepContextAware) { + ((StepContextAware) scopedObject).setStepContext(context); + } + } } - } return scopedObject; } @@ -132,7 +137,7 @@ public class StepScope implements Scope, /** * Public setter for the name property. This can then be used as a bean - * definition attribute, e.g. scope="step". Defaults to "step". + * definition attribute, e.g. scope="step". Defaults to "step". * * @param name * the name to set for this scope. diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepSynchronizationManager.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepSynchronizationManager.java index a46d59ab9..b7b53380f 100644 --- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepSynchronizationManager.java +++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/scope/StepSynchronizationManager.java @@ -21,13 +21,13 @@ package org.springframework.batch.execution.scope; */ public class StepSynchronizationManager { - private static final ThreadLocal contextHolder = new ThreadLocal(); + private static final ThreadLocal contextHolder = new InheritableThreadLocal(); /** * Getter for the current context.. * - * @return the current {@link SimpleStepContext} or null if there is none (if - * we are not in a step). + * @return the current {@link SimpleStepContext} or null if there is none + * (if we are not in a step). */ public static SimpleStepContext getContext() { return (SimpleStepContext) contextHolder.get(); @@ -56,7 +56,7 @@ public class StepSynchronizationManager { */ public static StepContext close() { SimpleStepContext oldSession = getContext(); - if (oldSession==null) { + if (oldSession == null) { return null; } oldSession.close(); diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/StepContextAwareStepScopeTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/StepContextAwareStepScopeTests.java index 34a6ab714..cc841721d 100644 --- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/StepContextAwareStepScopeTests.java +++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/scope/StepContextAwareStepScopeTests.java @@ -77,12 +77,84 @@ public class StepContextAwareStepScopeTests extends TestCase { assertEquals(1, list.size()); } + public void testScopedBeanWithProxy() throws Exception { + StepContext context = StepSynchronizationManager.open(); + ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("scope-tests.xml", getClass()); + TestBeanAware bean = (TestBeanAware) applicationContext.getBean("proxy"); + assertNotNull(bean); + // A scoped proxy is only accessible through public methods + assertEquals(null, bean.name); + assertEquals("spam", bean.getName()); + assertEquals(context, bean.getContext()); + } + + public void testScopedBeanWithProxyInThread() throws Exception { + StepSynchronizationManager.open(); + final ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("scope-tests.xml", getClass()); + new Thread(new Runnable() { + public void run() { + TestBeanAware bean = (TestBeanAware) applicationContext.getBean("proxy"); + list.add(bean.getName()); + } + }).start(); + int count = 0; + while(list.size()==0 && count++ <10) { + Thread.sleep(100); + } + if (list.size()==0) { + fail("Scoped proxy was not created in child thread - maybe we need to use InheritableThreadLocal?"); + } + String name = (String) list.get(0); + assertEquals("spam", name); + } + + public void testScopedBeanWithTwoProxiesInThreads() throws Exception { + StepSynchronizationManager.open(); + final ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("scope-tests.xml", getClass()); + new Thread(new Runnable() { + public void run() { + TestBeanAware bean = (TestBeanAware) applicationContext.getBean("proxy"); + int count = 0; + while(list.size()==0 && count++ <10) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + fail("Timeout waiting for other thread to add a bean to list."); + } + } + bean.getName(); + list.add(bean); + } + }).start(); + new Thread(new Runnable() { + public void run() { + TestBeanAware bean = (TestBeanAware) applicationContext.getBean("proxy"); + bean.getName(); + list.add(bean); + } + }).start(); + int count = 0; + while(list.size()<2 && count++ <10) { + Thread.sleep(100); + } + if (list.size()<2) { + fail("Scoped proxies were not created in child threads"); + } + TestBeanAware bean1 = (TestBeanAware) list.get(0); + TestBeanAware bean2 = (TestBeanAware) list.get(1); + assertEquals("spam", bean1.getName()); + assertSame(bean1.getLock(), bean2.getLock()); + } + public static class TestBean { String name; TestBean child; public void setName(String name) { this.name = name; } + public String getName() { + return name; + } public void setChild(TestBean child) { this.child = child; } @@ -93,8 +165,15 @@ public class StepContextAwareStepScopeTests extends TestCase { public static class TestBeanAware extends TestBean implements StepContextAware { AttributeAccessor context; + Object lock = new Object(); public void setStepContext(StepContext context) { this.context = context; } + public AttributeAccessor getContext() { + return context; + } + public Object getLock() { + return lock; + } } } diff --git a/spring-batch-execution/src/test/resources/org/springframework/batch/execution/scope/scope-tests.xml b/spring-batch-execution/src/test/resources/org/springframework/batch/execution/scope/scope-tests.xml index 4fbd6a7e1..b8f94f388 100644 --- a/spring-batch-execution/src/test/resources/org/springframework/batch/execution/scope/scope-tests.xml +++ b/spring-batch-execution/src/test/resources/org/springframework/batch/execution/scope/scope-tests.xml @@ -36,4 +36,11 @@ + + + + +