diff --git a/spring-batch-core/.springBeans b/spring-batch-core/.springBeans
index aa331bd36..5388934d3 100644
--- a/spring-batch-core/.springBeans
+++ b/spring-batch-core/.springBeans
@@ -21,6 +21,7 @@
src/test/resources/org/springframework/batch/core/configuration/xml/common-context.xml
src/test/resources/org/springframework/batch/core/configuration/xml/OneStepJobParserTests-context.xml
src/test/resources/org/springframework/batch/core/configuration/xml/TwoStepJobParserTests-context.xml
+ src/test/resources/org/springframework/batch/core/annotation/batch-component-context.xml
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BatchComponent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BatchComponent.java
new file mode 100644
index 000000000..d57934f8d
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BatchComponent.java
@@ -0,0 +1,30 @@
+/**
+ *
+ */
+package org.springframework.batch.core.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.batch.core.scope.StepScope;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * Marker interface for components that wish to participate
+ * in the batch lifecycle. By default, any class annotated
+ * with this annotation will also has a scope of 'Step'
+ *
+ * @author Lucas Ward
+ * @since 2.0
+ * @see StepScope
+ * @see Component
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Scope("step")
+public @interface BatchComponent {
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BatchComponentScopeMetaDataResolver.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BatchComponentScopeMetaDataResolver.java
new file mode 100644
index 000000000..ebfce0059
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BatchComponentScopeMetaDataResolver.java
@@ -0,0 +1,53 @@
+/**
+ *
+ */
+package org.springframework.batch.core.annotation;
+
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.AnnotationScopeMetadataResolver;
+import org.springframework.context.annotation.ScopeMetadata;
+import org.springframework.context.annotation.ScopeMetadataResolver;
+import org.springframework.context.annotation.ScopedProxyMode;
+
+/**
+ * Implementation of the ScopeMetaDataResolver that checks for the {@link BatchComponent}
+ * annotation on the BeanDefinition passed in, and if found sets the scope to step. The default
+ * resolver, AnnotationScopeMetadataResolver is used for any classes that don't contain the
+ * {@link BatchComponent} annotation.
+ *
+ * @author Lucas Ward
+ * @since 2.0
+ * @see BeanDefinition
+ * @see BatchComponent
+ */
+public class BatchComponentScopeMetaDataResolver implements
+ ScopeMetadataResolver {
+
+ ScopeMetadataResolver delegate = new AnnotationScopeMetadataResolver();
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.context.annotation.ScopeMetadataResolver#resolveScopeMetadata(org.springframework.beans.factory.config.BeanDefinition)
+ */
+ public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
+
+ if(definition instanceof AnnotatedBeanDefinition){
+ AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
+ Map attributes =
+ annDef.getMetadata().getAnnotationAttributes(BatchComponent.class.getName());
+ if(attributes != null){
+ ScopeMetadata metadata = new ScopeMetadata();
+ metadata.setScopeName("step");
+ metadata.setScopedProxyMode(ScopedProxyMode.INTERFACES);
+ return metadata;
+ }
+ }
+
+ return delegate.resolveScopeMetadata(definition);
+ }
+
+
+}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/annotation/BatchComponentIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/annotation/BatchComponentIntegrationTests.java
new file mode 100644
index 000000000..c73a4cf90
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/annotation/BatchComponentIntegrationTests.java
@@ -0,0 +1,102 @@
+/**
+ *
+ */
+package org.springframework.batch.core.annotation;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+/**
+ * @author Lucas Ward
+ *
+ */
+public class BatchComponentIntegrationTests {
+
+ @Test
+ public void testWithoutResolver(){
+ ApplicationContext context = new ClassPathXmlApplicationContext("org/springframework/batch/core/annotation/batch-component-context.xml");
+ String[] beanNames = context.getBeanNamesForType(TestComponent.class);
+ //There should be one instance of TextComponent in the Spring Container, because it's annotated with BatchComponent, and the filter
+ //is setup. However, the beanDefinition, should not have the scope on it, because there is no proxy set.
+ assertEquals(1, beanNames.length);
+ ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory)context.getAutowireCapableBeanFactory();
+ BeanDefinition beanDef = beanFactory.getBeanDefinition(beanNames[0]);
+ //Even though BatchComponent is annotated with StepScope, the scope will still be singleton without the resolver.
+ assertEquals(BeanDefinition.SCOPE_SINGLETON, beanDef.getScope());
+ }
+
+ @Test
+ public void testWithResolver(){
+
+ ApplicationContext context = new ClassPathXmlApplicationContext("org/springframework/batch/core/annotation/batch-component-context-with-resolver.xml");
+ String[] beanNames = context.getBeanNamesForType(TestComponent.class);
+ //There should be one instance of TextComponent in the Spring Container, because it's annotated with BatchComponent, and the filter
+ //is setup. However, the beanDefinition, should not have the scope on it, because there is no proxy set.
+ assertEquals(1, beanNames.length);
+ ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory)context.getAutowireCapableBeanFactory();
+ BeanDefinition beanDef = beanFactory.getBeanDefinition(beanNames[0]);
+ //Even though BatchComponent is annotated with StepScope, the scope will still be singleton without the resolver.
+ assertEquals("step", beanDef.getScope());
+ }
+
+ @Test
+ public void testWithResolverNonScanned(){
+
+ ApplicationContext context = new ClassPathXmlApplicationContext("org/springframework/batch/core/annotation/batch-component-context-with-resolver-nonscanned.xml");
+ String[] beanNames = context.getBeanNamesForType(TestComponent.class);
+ //There should be one instance of TextComponent in the Spring Container, because it's annotated with BatchComponent, and the filter
+ //is setup. However, the beanDefinition, should not have the scope on it, because there is no proxy set.
+ assertEquals(1, beanNames.length);
+ ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory)context.getAutowireCapableBeanFactory();
+ BeanDefinition beanDef = beanFactory.getBeanDefinition(beanNames[0]);
+ //Even though BatchComponent is annotated with StepScope, the scope will still be singleton without the resolver.
+ assertEquals("step", beanDef.getScope());
+ }
+
+ @Test
+ public void testWithScopedComponent(){
+ ApplicationContext context = new ClassPathXmlApplicationContext("org/springframework/batch/core/annotation/batch-component-context-scoped.xml");
+ String[] beanNames = context.getBeanNamesForType(TestScopedComponent.class);
+ //There should be one instance of TextComponent in the Spring Container, because it's annotated with BatchComponent, and the filter
+ //is setup. However, the beanDefinition, should not have the scope on it, because there is no proxy set.
+ assertEquals(1, beanNames.length);
+ ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory)context.getAutowireCapableBeanFactory();
+ BeanDefinition beanDef = beanFactory.getBeanDefinition(beanNames[0]);
+ //Even though BatchComponent is annotated with StepScope, the scope will still be singleton without the resolver.
+ assertEquals("step", beanDef.getScope());
+ }
+
+ @Test
+ public void testWithPostProcessor(){
+
+ ApplicationContext context = new ClassPathXmlApplicationContext("org/springframework/batch/core/annotation/batch-component-context-postprocessor.xml");
+ String[] beanNames = context.getBeanNamesForType(TestComponent.class);
+ //There should be one instance of TextComponent in the Spring Container, because it's annotated with BatchComponent, and the filter
+ //is setup. However, the beanDefinition, should not have the scope on it, because there is no proxy set.
+ assertEquals(1, beanNames.length);
+ ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory)context.getAutowireCapableBeanFactory();
+ BeanDefinition beanDef = beanFactory.getBeanDefinition(beanNames[0]);
+ //Even though BatchComponent is annotated with StepScope, the scope will still be singleton without the resolver.
+ assertEquals("step", beanDef.getScope());
+ }
+
+
+ @Test
+ public void testWithPostProcessorScanned(){
+
+ ApplicationContext context = new ClassPathXmlApplicationContext("org/springframework/batch/core/annotation/batch-component-context-postprocessor-scanned.xml");
+ String[] beanNames = context.getBeanNamesForType(TestComponent.class);
+ //There should be one instance of TextComponent in the Spring Container, because it's annotated with BatchComponent, and the filter
+ //is setup. However, the beanDefinition, should not have the scope on it, because there is no proxy set.
+ assertEquals(1, beanNames.length);
+ ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory)context.getAutowireCapableBeanFactory();
+ BeanDefinition beanDef = beanFactory.getBeanDefinition(beanNames[0]);
+ //Even though BatchComponent is annotated with StepScope, the scope will still be singleton without the resolver.
+ assertEquals("step", beanDef.getScope());
+ }
+}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/annotation/BatchComponentScopeMetaDataResolverTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/annotation/BatchComponentScopeMetaDataResolverTests.java
new file mode 100644
index 000000000..a80b28d7e
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/annotation/BatchComponentScopeMetaDataResolverTests.java
@@ -0,0 +1,51 @@
+/**
+ *
+ */
+package org.springframework.batch.core.annotation;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
+import org.springframework.context.annotation.Scope;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.core.type.StandardAnnotationMetadata;
+
+/**
+ * @author Lucas Ward
+ *
+ */
+public class BatchComponentScopeMetaDataResolverTests {
+
+ AnnotatedBeanDefinition annBeanDef;
+ AnnotationMetadata metaData;
+ BatchComponentScopeMetaDataResolver resolver = new BatchComponentScopeMetaDataResolver();
+
+ @Before
+ public void init(){
+ annBeanDef = createMock(AnnotatedBeanDefinition.class);
+ }
+
+ @Test
+ public void testNormalCase(){
+ expect(annBeanDef.getMetadata()).andReturn(new StandardAnnotationMetadata(StubScopedClass.class));
+ expect(annBeanDef.getMetadata()).andReturn(new StandardAnnotationMetadata(StubScopedClass.class));
+ replay(annBeanDef);
+ assertEquals("step",resolver.resolveScopeMetadata(annBeanDef).getScopeName());
+ verify(annBeanDef);
+ }
+
+ @Test
+ public void testBatchComponent(){
+
+ expect(annBeanDef.getMetadata()).andReturn(new StandardAnnotationMetadata(TestComponent.class));
+ replay(annBeanDef);
+ assertEquals("step",resolver.resolveScopeMetadata(annBeanDef).getScopeName());
+ verify(annBeanDef);
+ }
+
+ @Scope("step")
+ private class StubScopedClass{}
+}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/annotation/TestComponent.java b/spring-batch-core/src/test/java/org/springframework/batch/core/annotation/TestComponent.java
new file mode 100644
index 000000000..e1d687654
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/annotation/TestComponent.java
@@ -0,0 +1,13 @@
+/**
+ *
+ */
+package org.springframework.batch.core.annotation;
+
+/**
+ * @author Lucas Ward
+ *
+ */
+@BatchComponent
+public class TestComponent {
+
+}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/annotation/TestScopedComponent.java b/spring-batch-core/src/test/java/org/springframework/batch/core/annotation/TestScopedComponent.java
new file mode 100644
index 000000000..1d26988e5
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/annotation/TestScopedComponent.java
@@ -0,0 +1,8 @@
+package org.springframework.batch.core.annotation;
+
+import org.springframework.context.annotation.Scope;
+
+@Scope("step")
+public class TestScopedComponent {
+
+}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/TestStep.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/TestStep.java
index efe6daf30..494f03aca 100644
--- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/TestStep.java
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/TestStep.java
@@ -13,7 +13,7 @@ public class TestStep implements Step {
public void setCollaborator(Collaborator collaborator) {
this.collaborator = collaborator;
}
-
+
public static StepContext getContext() {
return context;
}
diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/annotation/batch-component-context-scoped.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/annotation/batch-component-context-scoped.xml
new file mode 100644
index 000000000..c513028b2
--- /dev/null
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/annotation/batch-component-context-scoped.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/annotation/batch-component-context-with-resolver-nonscanned.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/annotation/batch-component-context-with-resolver-nonscanned.xml
new file mode 100644
index 000000000..1d6622885
--- /dev/null
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/annotation/batch-component-context-with-resolver-nonscanned.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/annotation/batch-component-context-with-resolver.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/annotation/batch-component-context-with-resolver.xml
new file mode 100644
index 000000000..e603b6c97
--- /dev/null
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/annotation/batch-component-context-with-resolver.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/annotation/batch-component-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/annotation/batch-component-context.xml
new file mode 100644
index 000000000..816d70213
--- /dev/null
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/annotation/batch-component-context.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/StepScopeIntegrationTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/StepScopeIntegrationTests-context.xml
index 802f16568..17351db4c 100644
--- a/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/StepScopeIntegrationTests-context.xml
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/StepScopeIntegrationTests-context.xml
@@ -29,6 +29,9 @@
+
+
+