BATCH-1948: Add @StepScope and associated tests

This commit is contained in:
Dave Syer
2013-01-10 14:55:47 +00:00
committed by Michael Minella
parent aadc5ee39e
commit 43bfc0769e
6 changed files with 260 additions and 23 deletions

View File

@@ -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<BatchConfigurer> 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;
}

View File

@@ -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;
/**
* <p>
@@ -162,12 +154,6 @@ import org.springframework.transaction.PlatformTransactionManager;
@Import(BatchConfigurationSelector.class)
public @interface EnableBatchProcessing {
/**
* Indicate whether beans in <code>scope="step"</code> 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 &#64;Bean Job definitions in this context, but rather supply them in separate (child)

View File

@@ -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;
/**
* <p>
* 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 &#64;Bean that needs to inject &#64;Values from the step
* context, and any bean that needs to share a lifecycle with a step execution (e.g. an ItemStream). E.g.
* </p>
*
* <pre class="code">
* &#064;Bean
* &#064;StepScope
* protected Callable&lt;String&gt; value(@Value(&quot;#{stepExecution.stepName}&quot;)
* final String value) {
* return new SimpleCallable(value);
* }
* </pre>
*
* <p>Marking a &#64;Bean as &#64;StepScope is quivalent to marking it as <code>&#64;Scope(value="step", proxyMode=INTERFACES)</code></p>
*
* @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;
}

View File

@@ -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<String> 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<String> 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<String> 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<String> {
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 = "<WRONG>";
}
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<String> value(@Value("#{stepExecution.stepName}")
final String value) {
return new SimpleCallable(value);
}
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<bean class="org.springframework.batch.core.scope.StepScope" />
<bean class="org.springframework.batch.core.configuration.annotation.StepScopeConfigurationTests.SimpleCallable"
scope="step">
<constructor-arg value="#{stepExecution.stepName}" />
</bean>
</beans>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<bean class="org.springframework.batch.core.scope.StepScope">
<property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.springframework.batch.core.configuration.annotation.StepScopeConfigurationTests.SimpleHolder" scope="step">
<constructor-arg value="#{stepExecution.stepName}" />
</bean>
</beans>