diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AbstractBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AbstractBatchConfiguration.java
index 32d09dac2..bd51bef4f 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AbstractBatchConfiguration.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AbstractBatchConfiguration.java
@@ -20,7 +20,6 @@ import org.springframework.batch.core.configuration.support.MapJobRegistry;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
-import org.springframework.batch.core.scope.StepScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -31,6 +30,8 @@ import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
+import org.springframework.batch.core.scope.JobScope;
+import org.springframework.batch.core.scope.StepScope;
import javax.sql.DataSource;
import java.util.Collection;
@@ -44,7 +45,7 @@ import java.util.Collection;
* @see EnableBatchProcessing
*/
@Configuration
-@Import(StepScopeConfiguration.class)
+@Import(ScopeConfiguration.class)
public abstract class AbstractBatchConfiguration implements ImportAware {
@Autowired
@@ -129,14 +130,21 @@ public abstract class AbstractBatchConfiguration implements ImportAware {
*
*/
@Configuration
-class StepScopeConfiguration {
+class ScopeConfiguration {
private StepScope stepScope = new StepScope();
+ private JobScope jobScope = new JobScope();
+
@Bean
public StepScope stepScope() {
stepScope.setAutoProxy(false);
return stepScope;
}
+ @Bean
+ public JobScope jobScope() {
+ jobScope.setAutoProxy(false);
+ return jobScope;
+ }
}
\ No newline at end of file
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java
index cedf8cc83..5dea5f600 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java
@@ -89,7 +89,8 @@ import java.lang.annotation.Target;
*
* Note that only one of your configuration classes needs to have the @EnableBatchProcessing
* annotation. Once you have an @EnableBatchProcessing class in your configuration you will have an
- * instance of {@link StepScope} so your beans inside steps can have @Scope("step"). You will also be
+ * instance of {@link StepScope} and {@link org.springframework.batch.core.scope.JobScope} so your beans inside steps
+ * can have @Scope("step") and @Scope("job") respectively. You will also be
* able to @Autowired some useful stuff into your context:
*
*
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/JobScope.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/JobScope.java
new file mode 100644
index 000000000..3aaabc546
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/JobScope.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.core.configuration.annotation;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ *
+ * Convenient annotation for job scoped beans that defaults the proxy mode, so that it doesn't have to be specified
+ * explicitly on every bean definition. Use this on any @Bean that needs to inject @Values from the job
+ * context, and any bean that needs to share a lifecycle with a job execution (e.g. an JobExecutionListener). E.g.
+ *
+ *
+ *
+ * @Bean
+ * @JobScope
+ * protected Callable<String> value(@Value("#{jobExecution.jobInstance.jobName}")
+ * final String value) {
+ * return new SimpleCallable(value);
+ * }
+ *
+ *
+ * Marking a @Bean as @JobScope is equivalent to marking it as @Scope(value="job", proxyMode=TARGET_CLASS)
+ *
+ * @author Michael Minella
+ *
+ * @since 3.0.1
+ *
+ */
+@Scope(value = "job", proxyMode = ScopedProxyMode.TARGET_CLASS)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface JobScope {
+
+}
\ No newline at end of file
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTests.java
new file mode 100644
index 000000000..8164d792f
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTests.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2006-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.batch.core.configuration.annotation;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.JobInstance;
+import org.springframework.batch.core.scope.context.JobSynchronizationManager;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import java.util.concurrent.Callable;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Dave Syer
+ * @author Michael Minella
+ *
+ */
+public class JobScopeConfigurationTests {
+
+ private ConfigurableApplicationContext context;
+
+ private JobExecution jobExecution;
+
+ @Rule
+ public ExpectedException expected = ExpectedException.none();
+
+ @Test
+ public void testXmlJobScopeWithProxyTargetClass() throws Exception {
+ context = new ClassPathXmlApplicationContext(
+ "org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsProxyTargetClass-context.xml");
+ JobSynchronizationManager.register(jobExecution);
+ SimpleHolder value = context.getBean(SimpleHolder.class);
+ assertEquals("JOB", value.call());
+ }
+
+ @Test
+ public void testXmlJobScopeWithInterface() throws Exception {
+ context = new ClassPathXmlApplicationContext(
+ "org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsInterface-context.xml");
+ JobSynchronizationManager.register(jobExecution);
+ @SuppressWarnings("unchecked")
+ Callable value = context.getBean(Callable.class);
+ assertEquals("JOB", value.call());
+ }
+
+ @Test
+ public void testXmlJobScopeWithInheritence() throws Exception {
+ context = new ClassPathXmlApplicationContext(
+ "org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsInheritence-context.xml");
+ JobSynchronizationManager.register(jobExecution);
+ SimpleHolder value = (SimpleHolder) context.getBean("child");
+ assertEquals("JOB", value.call());
+ }
+
+ @Test
+ public void testJobScopeWithProxyTargetClass() throws Exception {
+ init(JobScopeConfigurationRequiringProxyTargetClass.class);
+ SimpleHolder value = context.getBean(SimpleHolder.class);
+ assertEquals("JOB", value.call());
+ }
+
+ @Test
+ public void testJobScopeWithProxyTargetClassInjected() throws Exception {
+ init(JobScopeConfigurationInjectingProxy.class);
+ SimpleHolder value = context.getBean(Wrapper.class).getValue();
+ assertEquals("JOB", value.call());
+ }
+
+ @Test
+ public void testIntentionallyBlowUpOnMissingContextWithProxyTargetClass() throws Exception {
+ init(JobScopeConfigurationRequiringProxyTargetClass.class);
+ JobSynchronizationManager.release();
+ expected.expect(BeanCreationException.class);
+ expected.expectMessage("job scope");
+ SimpleHolder value = context.getBean(SimpleHolder.class);
+ assertEquals("JOB", value.call());
+ }
+
+ @Test
+ public void testIntentionallyBlowupWithForcedInterface() throws Exception {
+ init(JobScopeConfigurationForcingInterfaceProxy.class);
+ JobSynchronizationManager.release();
+ expected.expect(BeanCreationException.class);
+ expected.expectMessage("job scope");
+ SimpleHolder value = context.getBean(SimpleHolder.class);
+ assertEquals("JOB", value.call());
+ }
+
+ @Test
+ public void testJobScopeWithDefaults() throws Exception {
+ init(JobScopeConfigurationWithDefaults.class);
+ @SuppressWarnings("unchecked")
+ Callable value = context.getBean(Callable.class);
+ assertEquals("JOB", value.call());
+ }
+
+ @Test
+ public void testIntentionallyBlowUpOnMissingContextWithInterface() throws Exception {
+ init(JobScopeConfigurationWithDefaults.class);
+ JobSynchronizationManager.release();
+ expected.expect(BeanCreationException.class);
+ expected.expectMessage("job scope");
+ @SuppressWarnings("unchecked")
+ Callable value = context.getBean(Callable.class);
+ assertEquals("JOB", value.call());
+ }
+
+ public void init(Class>... config) throws Exception {
+ Class>[] configs = new Class>[config.length + 1];
+ System.arraycopy(config, 0, configs, 1, config.length);
+ configs[0] = DataSourceConfiguration.class;
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.register(configs);
+ context.refresh();
+ this.context = context;
+ JobSynchronizationManager.register(jobExecution);
+ }
+
+ @Before
+ public void setup() {
+ JobSynchronizationManager.release();
+ jobExecution = new JobExecution(new JobInstance(5l, "JOB"), null, null);
+ }
+
+ @After
+ public void close() {
+ JobSynchronizationManager.release();
+ if (context != null) {
+ context.close();
+ }
+ }
+
+ public static class SimpleCallable implements Callable {
+ private final String value;
+
+ private SimpleCallable(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String call() throws Exception {
+ return value;
+ }
+ }
+
+ public static class SimpleHolder {
+ private final String value;
+
+ protected SimpleHolder() {
+ value = "";
+ }
+
+ public SimpleHolder(String value) {
+ this.value = value;
+ }
+
+ public String call() throws Exception {
+ return value;
+ }
+ }
+
+ public static class Wrapper {
+
+ private SimpleHolder value;
+
+ public Wrapper(SimpleHolder value) {
+ this.value = value;
+ }
+
+ public SimpleHolder getValue() {
+ return value;
+ }
+
+ }
+
+ @Configuration
+ @EnableBatchProcessing
+ public static class JobScopeConfigurationInjectingProxy {
+
+ @Bean
+ public Wrapper wrapper(SimpleHolder value) {
+ return new Wrapper(value);
+ }
+
+ @Bean
+ @Scope(value="job", proxyMode = ScopedProxyMode.TARGET_CLASS)
+ protected SimpleHolder value(@Value("#{jobName}")
+ final String value) {
+ return new SimpleHolder(value);
+ }
+
+ }
+
+ @Configuration
+ @EnableBatchProcessing
+ public static class JobScopeConfigurationRequiringProxyTargetClass {
+
+ @Bean
+ @Scope(value="job", proxyMode = ScopedProxyMode.TARGET_CLASS)
+ protected SimpleHolder value(@Value("#{jobName}")
+ final String value) {
+ return new SimpleHolder(value);
+ }
+
+ }
+
+ @Configuration
+ @EnableBatchProcessing
+ public static class JobScopeConfigurationWithDefaults {
+
+ @Bean
+ @JobScope
+ protected Callable value(@Value("#{jobName}")
+ final String value) {
+ return new SimpleCallable(value);
+ }
+
+ }
+
+ @Configuration
+ @EnableBatchProcessing
+ public static class JobScopeConfigurationForcingInterfaceProxy {
+
+ @Bean
+ @Scope(value="job", proxyMode = ScopedProxyMode.INTERFACES)
+ protected SimpleHolder value(@Value("#{jobName}")
+ final String value) {
+ return new SimpleHolder(value);
+ }
+
+ }
+
+}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AutoRegisteringStepScopeTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AutoRegisteringStepScopeTests.java
index f6e280e7c..d2cf04d46 100644
--- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AutoRegisteringStepScopeTests.java
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AutoRegisteringStepScopeTests.java
@@ -15,15 +15,15 @@
*/
package org.springframework.batch.core.configuration.xml;
-import static org.junit.Assert.assertTrue;
-
-import java.util.Map;
-
import org.junit.Test;
import org.springframework.batch.core.scope.StepScope;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
+import java.util.Map;
+
+import static org.junit.Assert.assertTrue;
+
/**
* @author Thomas Risberg
diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsInheritence-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsInheritence-context.xml
new file mode 100644
index 000000000..5df33bb64
--- /dev/null
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsInheritence-context.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsInterface-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsInterface-context.xml
new file mode 100644
index 000000000..0a781a3c9
--- /dev/null
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsInterface-context.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsProxyTargetClass-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsProxyTargetClass-context.xml
new file mode 100644
index 000000000..7f157bf21
--- /dev/null
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsProxyTargetClass-context.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file