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 = ""; }