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 @@
+
+
+
+
+