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 @@ + + +