From 43bfc0769e9c1d1d7f3d7c2362fd46cf128eb4b5 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 10 Jan 2013 14:55:47 +0000 Subject: [PATCH] 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