From 43bfc0769e9c1d1d7f3d7c2362fd46cf128eb4b5 Mon Sep 17 00:00:00 2001
From: Dave Syer
Date: Thu, 10 Jan 2013 14:55:47 +0000
Subject: [PATCH 1/2] BATCH-1948: Add @StepScope and associated tests
---
.../AbstractBatchConfiguration.java | 12 +-
.../annotation/EnableBatchProcessing.java | 14 --
.../configuration/annotation/StepScope.java | 47 +++++
.../StepScopeConfigurationTests.java | 185 ++++++++++++++++++
...opeConfigurationTestsInterface-context.xml | 12 ++
...igurationTestsProxyTargetClass-context.xml | 13 ++
6 files changed, 260 insertions(+), 23 deletions(-)
create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepScope.java
create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTests.java
create mode 100644 spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTestsInterface-context.xml
create mode 100644 spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTestsProxyTargetClass-context.xml
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 f6c2b96fa..0967c2e8b 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
@@ -37,8 +37,7 @@ import org.springframework.util.Assert;
/**
* Base {@code Configuration} class providing common structure for enabling and using Spring Batch. Customization is
- * available by implementing the {@link BatchConfigurer} interface.
- * {@link BatchConfigurer}.
+ * available by implementing the {@link BatchConfigurer} interface. {@link BatchConfigurer}.
*
* @author Dave Syer
* @since 2.2
@@ -48,9 +47,6 @@ import org.springframework.util.Assert;
@Import(StepScopeConfiguration.class)
public abstract class AbstractBatchConfiguration implements ImportAware {
- @Autowired
- private StepScope stepScope;
-
@Autowired
private ApplicationContext context;
@@ -89,13 +85,10 @@ public abstract class AbstractBatchConfiguration implements ImportAware {
EnableBatchProcessing.class.getName(), false));
Assert.notNull(enabled,
"@EnableBatchProcessing is not present on importing class " + importMetadata.getClassName());
- if (enabled.getBoolean("proxyTargetClass")) {
- stepScope.setProxyTargetClass(true);
- }
}
protected BatchConfigurer getConfigurer(Collection configurers) throws Exception {
- if (this.configurer!=null) {
+ if (this.configurer != null) {
return this.configurer;
}
if (configurers == null || configurers.isEmpty()) {
@@ -134,6 +127,7 @@ class StepScopeConfiguration {
@Bean
public StepScope stepScope() {
+ stepScope.setAutoProxy(false);
return stepScope;
}
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 9b34683d2..d8545cc3b 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
@@ -21,15 +21,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import javax.sql.DataSource;
-
-import org.springframework.batch.core.configuration.support.ApplicationContextFactory;
-import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar;
-import org.springframework.batch.core.launch.JobLauncher;
-import org.springframework.batch.core.repository.JobRepository;
-import org.springframework.batch.core.scope.StepScope;
import org.springframework.context.annotation.Import;
-import org.springframework.transaction.PlatformTransactionManager;
/**
*
@@ -162,12 +154,6 @@ import org.springframework.transaction.PlatformTransactionManager;
@Import(BatchConfigurationSelector.class)
public @interface EnableBatchProcessing {
- /**
- * Indicate whether beans in scope="step" should use subclass-based (CGLIB) proxies are to be created
- * as opposed to standard Java interface-based proxies. The default is {@code false}.
- */
- boolean proxyTargetClass() default false;
-
/**
* Indicate whether the configuration is going to be modularized into multiple application contexts. If true then
* you should not create any @Bean Job definitions in this context, but rather supply them in separate (child)
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepScope.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepScope.java
new file mode 100644
index 000000000..20e348258
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepScope.java
@@ -0,0 +1,47 @@
+package org.springframework.batch.core.configuration.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+
+/**
+ *
+ * Convenient annotation for step 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 step
+ * context, and any bean that needs to share a lifecycle with a step execution (e.g. an ItemStream). E.g.
+ *
+ *
+ *
+ * @Bean
+ * @StepScope
+ * protected Callable<String> value(@Value("#{stepExecution.stepName}")
+ * final String value) {
+ * return new SimpleCallable(value);
+ * }
+ *
+ *
+ * Marking a @Bean as @StepScope is quivalent to marking it as @Scope(value="step", proxyMode=INTERFACES)
+ *
+ * @author Dave Syer
+ *
+ * @Since 2.2
+ *
+ */
+@Scope(value = "step")
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface StepScope {
+
+ /**
+ * Set the proxy mode to use (defaults to INTERFACES).
+ *
+ * @see Scope#proxyMode()
+ *
+ * @return the proxy mode to use
+ */
+ ScopedProxyMode proxyMode() default ScopedProxyMode.INTERFACES;
+
+}
\ No newline at end of file
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTests.java
new file mode 100644
index 000000000..e5b0a8f0a
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTests.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2006-2011 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 static org.junit.Assert.assertEquals;
+
+import java.util.concurrent.Callable;
+
+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.StepExecution;
+import org.springframework.batch.core.scope.context.StepSynchronizationManager;
+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.ScopedProxyMode;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class StepScopeConfigurationTests {
+
+ private ConfigurableApplicationContext context;
+
+ private StepExecution stepExecution;
+
+ @Rule
+ public ExpectedException expected = ExpectedException.none();
+
+ @Test
+ public void testXmlStepScopeWithProxyTargetClass() throws Exception {
+ context = new ClassPathXmlApplicationContext(
+ "org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTestsProxyTargetClass-context.xml");
+ SimpleHolder value = context.getBean(SimpleHolder.class);
+ assertEquals("STEP", value.call());
+ }
+
+ @Test
+ public void testXmlStepScopeWithInterface() throws Exception {
+ context = new ClassPathXmlApplicationContext(
+ "org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTestsInterface-context.xml");
+ @SuppressWarnings("unchecked")
+ Callable value = context.getBean(Callable.class);
+ assertEquals("STEP", value.call());
+ }
+
+ @Test
+ public void testStepScopeWithProxyTargetClass() throws Exception {
+ init(StepScopeConfigurationRequiringProxyTargetClass.class);
+ SimpleHolder value = context.getBean(SimpleHolder.class);
+ assertEquals("STEP", value.call());
+ }
+
+ @Test
+ public void testIntentionallyBlowUpOnMissingContextWithProxyTargetClass() throws Exception {
+ init(StepScopeConfigurationRequiringProxyTargetClass.class);
+ StepSynchronizationManager.release();
+ expected.expect(BeanCreationException.class);
+ expected.expectMessage("step scope");
+ SimpleHolder value = context.getBean(SimpleHolder.class);
+ assertEquals("STEP", value.call());
+ }
+
+ @Test
+ public void testStepScopeWithInterface() throws Exception {
+ init(StepScopeConfigurationWithInterface.class);
+ @SuppressWarnings("unchecked")
+ Callable value = context.getBean(Callable.class);
+ assertEquals("STEP", value.call());
+ }
+
+ @Test
+ public void testIntentionallyBlowUpOnMissingContextWithInterface() throws Exception {
+ init(StepScopeConfigurationWithInterface.class);
+ StepSynchronizationManager.release();
+ expected.expect(BeanCreationException.class);
+ expected.expectMessage("step scope");
+ @SuppressWarnings("unchecked")
+ Callable value = context.getBean(Callable.class);
+ assertEquals("STEP", 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.setScopeMetadataResolver(new AnnotationScopeMetadataResolver(ScopedProxyMode.TARGET_CLASS));
+ context.register(configs);
+ context.refresh();
+ this.context = context;
+ }
+
+ @Before
+ public void setup() {
+ stepExecution = new StepExecution("STEP", null);
+ StepSynchronizationManager.register(stepExecution);
+ }
+
+ @After
+ public void close() {
+ StepSynchronizationManager.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;
+ }
+ }
+
+ @Configuration
+ @EnableBatchProcessing
+ public static class StepScopeConfigurationRequiringProxyTargetClass {
+
+ @Bean
+ @StepScope(proxyMode = ScopedProxyMode.TARGET_CLASS)
+ protected SimpleHolder value(@Value("#{stepExecution.stepName}")
+ final String value) {
+ return new SimpleHolder(value);
+ }
+
+ }
+
+ @Configuration
+ @EnableBatchProcessing
+ public static class StepScopeConfigurationWithInterface {
+
+ @Bean
+ @StepScope
+ protected Callable value(@Value("#{stepExecution.stepName}")
+ final String value) {
+ return new SimpleCallable(value);
+ }
+
+ }
+
+}
diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTestsInterface-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTestsInterface-context.xml
new file mode 100644
index 000000000..35a08a7e2
--- /dev/null
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTestsInterface-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/StepScopeConfigurationTestsProxyTargetClass-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTestsProxyTargetClass-context.xml
new file mode 100644
index 000000000..4d615c5fe
--- /dev/null
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTestsProxyTargetClass-context.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
From 014e5bf523466d0755a593677bf9a3986a86adc3 Mon Sep 17 00:00:00 2001
From: Michael Minella
Date: Fri, 11 Jan 2013 16:02:01 -0600
Subject: [PATCH 2/2] BATCH-1948: Formatting and javadoc fixes
---
.../annotation/EnableBatchProcessing.java | 56 ++++++++++---------
.../configuration/annotation/StepScope.java | 16 +++---
.../StepScopeConfigurationTests.java | 9 ++-
3 files changed, 43 insertions(+), 38 deletions(-)
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 d8545cc3b..aaf15b2dc 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
@@ -21,67 +21,73 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import org.springframework.batch.core.configuration.JobRegistry;
+import org.springframework.batch.core.configuration.support.ApplicationContextFactory;
+import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar;
+import org.springframework.batch.core.launch.JobLauncher;
+import org.springframework.batch.core.repository.JobRepository;
import org.springframework.context.annotation.Import;
+import org.springframework.transaction.PlatformTransactionManager;
/**
*
* Enable Spring Batch features and provide a base configuration for setting up batch jobs in an @Configuration
* class, roughly equivalent to using the {@code } XML namespace.
- *
+ *
*
* @Configuration
* @EnableBatchProcessing
* @Import(DataSourceCnfiguration.class)
* public class AppConfig {
- *
+ *
* @Autowired
* private JobBuilderFactory jobs;
- *
+ *
* @Bean
* public Job job() {
* return jobs.get("myJob").start(step1()).next(step2()).build();
* }
- *
+ *
* @Bean
* protected Step step1() {
* ...
* }
- *
+ *
* @Bean
* protected Step step2() {
* ...
* }
* }
*
- *
+ *
* The user has to provide a {@link DataSource} as a bean in the context, or else implement {@link BatchConfigurer} in
* the configuration class itself, e.g.
- *
+ *
*
* @Configuration
* @EnableBatchProcessing
* public class AppConfig extends DefaultBatchConfigurer {
- *
+ *
* @Bean
* public Job job() {
* ...
* }
- *
+ *
* @Override
* protected JobRepository createJobRepository() {
* ...
* }
- *
+ *
* ...
- *
+ *
* }
*
- *
+ *
* 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
* able to @Autowired some useful stuff into your context:
- *
+ *
*
* - a {@link JobRepository} (bean name "jobRepository")
* - a {@link JobLauncher} (bean name "jobLauncher")
@@ -92,43 +98,43 @@ import org.springframework.context.annotation.Import;
* - a {@link StepBuilderFactory} (bean name "stepBuilders") as a convenience to prevent you from having to inject the
* job repository and transaction manager into every step
*
- *
+ *
* If the configuration is specified as modular=true then the context will also contain an
* {@link AutomaticJobRegistrar}. The job registrar is useful for modularizing your configuration if there are multiple
* jobs. It works by creating separate child application contexts containing job configurations and registering those
* jobs. The jobs can then create steps and other dependent components without needing to worry about bean definition
* name clashes. Beans of type {@link ApplicationContextFactory} will be registered automatically with the job
* registrar. Example:
- *
+ *
*
* @Configuration
* @EnableBatchProcessing(modular=true)
* public class AppConfig {
- *
+ *
* @Bean
* public ApplicationContextFactory someJobs() {
* return new GenericApplicationContextFactory(SomeJobConfiguration.class);
* }
- *
+ *
* @Bean
* public ApplicationContextFactory moreJobs() {
* return new GenericApplicationContextFactory(MoreJobConfiguration.class);
* }
- *
+ *
* ...
- *
+ *
* }
*
- *
+ *
* Note that a modular parent context in general should not itself contain @Bean definitions for job,
* especially if a {@link BatchConfigurer} is provided, because cyclic configuration dependencies are otherwise likely
* to develop.
- *
+ *
*
- *
+ *
*
* For reference, the first example above can be compared to the following Spring XML configuration:
- *
+ *
*
* {@code
*
@@ -144,9 +150,9 @@ import org.springframework.context.annotation.Import;
*
* }
*
- *
+ *
* @author Dave Syer
- *
+ *
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepScope.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepScope.java
index 20e348258..8ac7895b9 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepScope.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepScope.java
@@ -13,7 +13,7 @@ import org.springframework.context.annotation.ScopedProxyMode;
* explicitly on every bean definition. Use this on any @Bean that needs to inject @Values from the step
* context, and any bean that needs to share a lifecycle with a step execution (e.g. an ItemStream). E.g.
*
- *
+ *
*
* @Bean
* @StepScope
@@ -22,13 +22,13 @@ import org.springframework.context.annotation.ScopedProxyMode;
* return new SimpleCallable(value);
* }
*
- *
- * Marking a @Bean as @StepScope is quivalent to marking it as @Scope(value="step", proxyMode=INTERFACES)
- *
+ *
+ * Marking a @Bean as @StepScope is equivalent to marking it as @Scope(value="step", proxyMode=INTERFACES)
+ *
* @author Dave Syer
- *
+ *
* @Since 2.2
- *
+ *
*/
@Scope(value = "step")
@Retention(RetentionPolicy.RUNTIME)
@@ -37,9 +37,9 @@ public @interface StepScope {
/**
* Set the proxy mode to use (defaults to INTERFACES).
- *
+ *
* @see Scope#proxyMode()
- *
+ *
* @return the proxy mode to use
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.INTERFACES;
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTests.java
index e5b0a8f0a..0d95a5672 100644
--- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTests.java
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2011 the original author or authors.
+ * 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.
@@ -38,7 +38,7 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author Dave Syer
- *
+ *
*/
public class StepScopeConfigurationTests {
@@ -107,7 +107,6 @@ public class StepScopeConfigurationTests {
System.arraycopy(config, 0, configs, 1, config.length);
configs[0] = DataSourceConfiguration.class;
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
- // context.setScopeMetadataResolver(new AnnotationScopeMetadataResolver(ScopedProxyMode.TARGET_CLASS));
context.register(configs);
context.refresh();
this.context = context;
@@ -126,7 +125,7 @@ public class StepScopeConfigurationTests {
context.close();
}
}
-
+
public static class SimpleCallable implements Callable {
private final String value;
@@ -142,7 +141,7 @@ public class StepScopeConfigurationTests {
public static class SimpleHolder {
private final String value;
-
+
protected SimpleHolder() {
value = "";
}