BATCH-2253: Added automatic registraiton of Job Scope to java config and

added support for a @JobScope annotation.
This commit is contained in:
Michael Minella
2014-06-12 16:42:48 -05:00
parent 752fe55e88
commit 8f8600c14d
8 changed files with 371 additions and 8 deletions

View File

@@ -20,7 +20,6 @@ import org.springframework.batch.core.configuration.support.MapJobRegistry;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.scope.StepScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -31,6 +30,8 @@ import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
import org.springframework.batch.core.scope.JobScope;
import org.springframework.batch.core.scope.StepScope;
import javax.sql.DataSource;
import java.util.Collection;
@@ -44,7 +45,7 @@ import java.util.Collection;
* @see EnableBatchProcessing
*/
@Configuration
@Import(StepScopeConfiguration.class)
@Import(ScopeConfiguration.class)
public abstract class AbstractBatchConfiguration implements ImportAware {
@Autowired
@@ -129,14 +130,21 @@ public abstract class AbstractBatchConfiguration implements ImportAware {
*
*/
@Configuration
class StepScopeConfiguration {
class ScopeConfiguration {
private StepScope stepScope = new StepScope();
private JobScope jobScope = new JobScope();
@Bean
public StepScope stepScope() {
stepScope.setAutoProxy(false);
return stepScope;
}
@Bean
public JobScope jobScope() {
jobScope.setAutoProxy(false);
return jobScope;
}
}

View File

@@ -89,7 +89,8 @@ import java.lang.annotation.Target;
*
* Note that only one of your configuration classes needs to have the <code>&#064;EnableBatchProcessing</code>
* annotation. Once you have an <code>&#064;EnableBatchProcessing</code> class in your configuration you will have an
* instance of {@link StepScope} so your beans inside steps can have <code>&#064;Scope("step")</code>. You will also be
* instance of {@link StepScope} and {@link org.springframework.batch.core.scope.JobScope} so your beans inside steps
* can have <code>&#064;Scope("step")</code> and <code>&#064;Scope("job")</code> respectively. You will also be
* able to <code>&#064;Autowired</code> some useful stuff into your context:
*
* <ul>

View File

@@ -0,0 +1,53 @@
/*
* Copyright 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.
* 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 org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* <p>
* Convenient annotation for job 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 job
* context, and any bean that needs to share a lifecycle with a job execution (e.g. an JobExecutionListener). E.g.
* </p>
*
* <pre class="code">
* &#064;Bean
* &#064;JobScope
* protected Callable&lt;String&gt; value(@Value(&quot;#{jobExecution.jobInstance.jobName}&quot;)
* final String value) {
* return new SimpleCallable(value);
* }
* </pre>
*
* <p>Marking a &#64;Bean as &#64;JobScope is equivalent to marking it as <code>&#64;Scope(value="job", proxyMode=TARGET_CLASS)</code></p>
*
* @author Michael Minella
*
* @since 3.0.1
*
*/
@Scope(value = "job", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JobScope {
}

View File

@@ -0,0 +1,261 @@
/*
* 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.
* 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 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.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.scope.context.JobSynchronizationManager;
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.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.concurrent.Callable;
import static org.junit.Assert.assertEquals;
/**
* @author Dave Syer
* @author Michael Minella
*
*/
public class JobScopeConfigurationTests {
private ConfigurableApplicationContext context;
private JobExecution jobExecution;
@Rule
public ExpectedException expected = ExpectedException.none();
@Test
public void testXmlJobScopeWithProxyTargetClass() throws Exception {
context = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsProxyTargetClass-context.xml");
JobSynchronizationManager.register(jobExecution);
SimpleHolder value = context.getBean(SimpleHolder.class);
assertEquals("JOB", value.call());
}
@Test
public void testXmlJobScopeWithInterface() throws Exception {
context = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsInterface-context.xml");
JobSynchronizationManager.register(jobExecution);
@SuppressWarnings("unchecked")
Callable<String> value = context.getBean(Callable.class);
assertEquals("JOB", value.call());
}
@Test
public void testXmlJobScopeWithInheritence() throws Exception {
context = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsInheritence-context.xml");
JobSynchronizationManager.register(jobExecution);
SimpleHolder value = (SimpleHolder) context.getBean("child");
assertEquals("JOB", value.call());
}
@Test
public void testJobScopeWithProxyTargetClass() throws Exception {
init(JobScopeConfigurationRequiringProxyTargetClass.class);
SimpleHolder value = context.getBean(SimpleHolder.class);
assertEquals("JOB", value.call());
}
@Test
public void testJobScopeWithProxyTargetClassInjected() throws Exception {
init(JobScopeConfigurationInjectingProxy.class);
SimpleHolder value = context.getBean(Wrapper.class).getValue();
assertEquals("JOB", value.call());
}
@Test
public void testIntentionallyBlowUpOnMissingContextWithProxyTargetClass() throws Exception {
init(JobScopeConfigurationRequiringProxyTargetClass.class);
JobSynchronizationManager.release();
expected.expect(BeanCreationException.class);
expected.expectMessage("job scope");
SimpleHolder value = context.getBean(SimpleHolder.class);
assertEquals("JOB", value.call());
}
@Test
public void testIntentionallyBlowupWithForcedInterface() throws Exception {
init(JobScopeConfigurationForcingInterfaceProxy.class);
JobSynchronizationManager.release();
expected.expect(BeanCreationException.class);
expected.expectMessage("job scope");
SimpleHolder value = context.getBean(SimpleHolder.class);
assertEquals("JOB", value.call());
}
@Test
public void testJobScopeWithDefaults() throws Exception {
init(JobScopeConfigurationWithDefaults.class);
@SuppressWarnings("unchecked")
Callable<String> value = context.getBean(Callable.class);
assertEquals("JOB", value.call());
}
@Test
public void testIntentionallyBlowUpOnMissingContextWithInterface() throws Exception {
init(JobScopeConfigurationWithDefaults.class);
JobSynchronizationManager.release();
expected.expect(BeanCreationException.class);
expected.expectMessage("job scope");
@SuppressWarnings("unchecked")
Callable<String> value = context.getBean(Callable.class);
assertEquals("JOB", 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.register(configs);
context.refresh();
this.context = context;
JobSynchronizationManager.register(jobExecution);
}
@Before
public void setup() {
JobSynchronizationManager.release();
jobExecution = new JobExecution(new JobInstance(5l, "JOB"), null, null);
}
@After
public void close() {
JobSynchronizationManager.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;
}
}
public static class Wrapper {
private SimpleHolder value;
public Wrapper(SimpleHolder value) {
this.value = value;
}
public SimpleHolder getValue() {
return value;
}
}
@Configuration
@EnableBatchProcessing
public static class JobScopeConfigurationInjectingProxy {
@Bean
public Wrapper wrapper(SimpleHolder value) {
return new Wrapper(value);
}
@Bean
@Scope(value="job", proxyMode = ScopedProxyMode.TARGET_CLASS)
protected SimpleHolder value(@Value("#{jobName}")
final String value) {
return new SimpleHolder(value);
}
}
@Configuration
@EnableBatchProcessing
public static class JobScopeConfigurationRequiringProxyTargetClass {
@Bean
@Scope(value="job", proxyMode = ScopedProxyMode.TARGET_CLASS)
protected SimpleHolder value(@Value("#{jobName}")
final String value) {
return new SimpleHolder(value);
}
}
@Configuration
@EnableBatchProcessing
public static class JobScopeConfigurationWithDefaults {
@Bean
@JobScope
protected Callable<String> value(@Value("#{jobName}")
final String value) {
return new SimpleCallable(value);
}
}
@Configuration
@EnableBatchProcessing
public static class JobScopeConfigurationForcingInterfaceProxy {
@Bean
@Scope(value="job", proxyMode = ScopedProxyMode.INTERFACES)
protected SimpleHolder value(@Value("#{jobName}")
final String value) {
return new SimpleHolder(value);
}
}
}

View File

@@ -15,15 +15,15 @@
*/
package org.springframework.batch.core.configuration.xml;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import org.junit.Test;
import org.springframework.batch.core.scope.StepScope;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Map;
import static org.junit.Assert.assertTrue;
/**
* @author Thomas Risberg

View File

@@ -0,0 +1,15 @@
<?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.JobScope">
<property name="proxyTargetClass" value="true" />
</bean>
<bean id="parent" class="org.springframework.batch.core.configuration.annotation.JobScopeConfigurationTests$SimpleHolder" scope="job" abstract="true">
<constructor-arg value="#{jobName}" />
</bean>
<bean id="child" class="org.springframework.batch.core.configuration.annotation.JobScopeConfigurationTests$SimpleHolder" parent="parent"/>
</beans>

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.JobScope" />
<bean class="org.springframework.batch.core.configuration.annotation.JobScopeConfigurationTests$SimpleCallable"
scope="job">
<constructor-arg value="#{jobName}" />
</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.JobScope">
<property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.springframework.batch.core.configuration.annotation.JobScopeConfigurationTests$SimpleHolder" scope="job">
<constructor-arg value="#{jobName}" />
</bean>
</beans>