Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 578f7ac

Browse files
committed
BATCH-2253: Added automatic registraiton of Job Scope to java config and
added support for a @JobScope annotation.
1 parent 654010d commit 578f7ac

File tree

8 files changed

+371
-8
lines changed

8 files changed

+371
-8
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AbstractBatchConfiguration.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import org.springframework.batch.core.explore.JobExplorer;
2121
import org.springframework.batch.core.launch.JobLauncher;
2222
import org.springframework.batch.core.repository.JobRepository;
23-
import org.springframework.batch.core.scope.StepScope;
2423
import org.springframework.beans.factory.annotation.Autowired;
2524
import org.springframework.context.ApplicationContext;
2625
import org.springframework.context.annotation.Bean;
@@ -31,6 +30,8 @@
3130
import org.springframework.core.type.AnnotationMetadata;
3231
import org.springframework.transaction.PlatformTransactionManager;
3332
import org.springframework.util.Assert;
33+
import org.springframework.batch.core.scope.JobScope;
34+
import org.springframework.batch.core.scope.StepScope;
3435

3536
import javax.sql.DataSource;
3637
import java.util.Collection;
@@ -44,7 +45,7 @@
4445
* @see EnableBatchProcessing
4546
*/
4647
@Configuration
47-
@Import(StepScopeConfiguration.class)
48+
@Import(ScopeConfiguration.class)
4849
public abstract class AbstractBatchConfiguration implements ImportAware {
4950

5051
@Autowired
@@ -129,14 +130,21 @@ protected BatchConfigurer getConfigurer(Collection<BatchConfigurer> configurers)
129130
*
130131
*/
131132
@Configuration
132-
class StepScopeConfiguration {
133+
class ScopeConfiguration {
133134

134135
private StepScope stepScope = new StepScope();
135136

137+
private JobScope jobScope = new JobScope();
138+
136139
@Bean
137140
public StepScope stepScope() {
138141
stepScope.setAutoProxy(false);
139142
return stepScope;
140143
}
141144

145+
@Bean
146+
public JobScope jobScope() {
147+
jobScope.setAutoProxy(false);
148+
return jobScope;
149+
}
142150
}

spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@
8989
*
9090
* Note that only one of your configuration classes needs to have the <code>&#064;EnableBatchProcessing</code>
9191
* annotation. Once you have an <code>&#064;EnableBatchProcessing</code> class in your configuration you will have an
92-
* instance of {@link StepScope} so your beans inside steps can have <code>&#064;Scope("step")</code>. You will also be
92+
* instance of {@link StepScope} and {@link org.springframework.batch.core.scope.JobScope} so your beans inside steps
93+
* can have <code>&#064;Scope("step")</code> and <code>&#064;Scope("job")</code> respectively. You will also be
9394
* able to <code>&#064;Autowired</code> some useful stuff into your context:
9495
*
9596
* <ul>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.core.configuration.annotation;
17+
18+
import org.springframework.context.annotation.Scope;
19+
import org.springframework.context.annotation.ScopedProxyMode;
20+
21+
import java.lang.annotation.Documented;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
25+
/**
26+
* <p>
27+
* Convenient annotation for job scoped beans that defaults the proxy mode, so that it doesn't have to be specified
28+
* explicitly on every bean definition. Use this on any &#64;Bean that needs to inject &#64;Values from the job
29+
* context, and any bean that needs to share a lifecycle with a job execution (e.g. an JobExecutionListener). E.g.
30+
* </p>
31+
*
32+
* <pre class="code">
33+
* &#064;Bean
34+
* &#064;JobScope
35+
* protected Callable&lt;String&gt; value(@Value(&quot;#{jobExecution.jobInstance.jobName}&quot;)
36+
* final String value) {
37+
* return new SimpleCallable(value);
38+
* }
39+
* </pre>
40+
*
41+
* <p>Marking a &#64;Bean as &#64;JobScope is equivalent to marking it as <code>&#64;Scope(value="job", proxyMode=TARGET_CLASS)</code></p>
42+
*
43+
* @author Michael Minella
44+
*
45+
* @since 3.0.1
46+
*
47+
*/
48+
@Scope(value = "job", proxyMode = ScopedProxyMode.TARGET_CLASS)
49+
@Retention(RetentionPolicy.RUNTIME)
50+
@Documented
51+
public @interface JobScope {
52+
53+
}
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/*
2+
* Copyright 2006-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.batch.core.configuration.annotation;
18+
19+
import org.junit.After;
20+
import org.junit.Before;
21+
import org.junit.Rule;
22+
import org.junit.Test;
23+
import org.junit.rules.ExpectedException;
24+
import org.springframework.batch.core.JobExecution;
25+
import org.springframework.batch.core.JobInstance;
26+
import org.springframework.batch.core.scope.context.JobSynchronizationManager;
27+
import org.springframework.beans.factory.BeanCreationException;
28+
import org.springframework.beans.factory.annotation.Value;
29+
import org.springframework.context.ConfigurableApplicationContext;
30+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
31+
import org.springframework.context.annotation.Bean;
32+
import org.springframework.context.annotation.Configuration;
33+
import org.springframework.context.annotation.Scope;
34+
import org.springframework.context.annotation.ScopedProxyMode;
35+
import org.springframework.context.support.ClassPathXmlApplicationContext;
36+
37+
import java.util.concurrent.Callable;
38+
39+
import static org.junit.Assert.assertEquals;
40+
41+
/**
42+
* @author Dave Syer
43+
* @author Michael Minella
44+
*
45+
*/
46+
public class JobScopeConfigurationTests {
47+
48+
private ConfigurableApplicationContext context;
49+
50+
private JobExecution jobExecution;
51+
52+
@Rule
53+
public ExpectedException expected = ExpectedException.none();
54+
55+
@Test
56+
public void testXmlJobScopeWithProxyTargetClass() throws Exception {
57+
context = new ClassPathXmlApplicationContext(
58+
"org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsProxyTargetClass-context.xml");
59+
JobSynchronizationManager.register(jobExecution);
60+
SimpleHolder value = context.getBean(SimpleHolder.class);
61+
assertEquals("JOB", value.call());
62+
}
63+
64+
@Test
65+
public void testXmlJobScopeWithInterface() throws Exception {
66+
context = new ClassPathXmlApplicationContext(
67+
"org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsInterface-context.xml");
68+
JobSynchronizationManager.register(jobExecution);
69+
@SuppressWarnings("unchecked")
70+
Callable<String> value = context.getBean(Callable.class);
71+
assertEquals("JOB", value.call());
72+
}
73+
74+
@Test
75+
public void testXmlJobScopeWithInheritence() throws Exception {
76+
context = new ClassPathXmlApplicationContext(
77+
"org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsInheritence-context.xml");
78+
JobSynchronizationManager.register(jobExecution);
79+
SimpleHolder value = (SimpleHolder) context.getBean("child");
80+
assertEquals("JOB", value.call());
81+
}
82+
83+
@Test
84+
public void testJobScopeWithProxyTargetClass() throws Exception {
85+
init(JobScopeConfigurationRequiringProxyTargetClass.class);
86+
SimpleHolder value = context.getBean(SimpleHolder.class);
87+
assertEquals("JOB", value.call());
88+
}
89+
90+
@Test
91+
public void testJobScopeWithProxyTargetClassInjected() throws Exception {
92+
init(JobScopeConfigurationInjectingProxy.class);
93+
SimpleHolder value = context.getBean(Wrapper.class).getValue();
94+
assertEquals("JOB", value.call());
95+
}
96+
97+
@Test
98+
public void testIntentionallyBlowUpOnMissingContextWithProxyTargetClass() throws Exception {
99+
init(JobScopeConfigurationRequiringProxyTargetClass.class);
100+
JobSynchronizationManager.release();
101+
expected.expect(BeanCreationException.class);
102+
expected.expectMessage("job scope");
103+
SimpleHolder value = context.getBean(SimpleHolder.class);
104+
assertEquals("JOB", value.call());
105+
}
106+
107+
@Test
108+
public void testIntentionallyBlowupWithForcedInterface() throws Exception {
109+
init(JobScopeConfigurationForcingInterfaceProxy.class);
110+
JobSynchronizationManager.release();
111+
expected.expect(BeanCreationException.class);
112+
expected.expectMessage("job scope");
113+
SimpleHolder value = context.getBean(SimpleHolder.class);
114+
assertEquals("JOB", value.call());
115+
}
116+
117+
@Test
118+
public void testJobScopeWithDefaults() throws Exception {
119+
init(JobScopeConfigurationWithDefaults.class);
120+
@SuppressWarnings("unchecked")
121+
Callable<String> value = context.getBean(Callable.class);
122+
assertEquals("JOB", value.call());
123+
}
124+
125+
@Test
126+
public void testIntentionallyBlowUpOnMissingContextWithInterface() throws Exception {
127+
init(JobScopeConfigurationWithDefaults.class);
128+
JobSynchronizationManager.release();
129+
expected.expect(BeanCreationException.class);
130+
expected.expectMessage("job scope");
131+
@SuppressWarnings("unchecked")
132+
Callable<String> value = context.getBean(Callable.class);
133+
assertEquals("JOB", value.call());
134+
}
135+
136+
public void init(Class<?>... config) throws Exception {
137+
Class<?>[] configs = new Class<?>[config.length + 1];
138+
System.arraycopy(config, 0, configs, 1, config.length);
139+
configs[0] = DataSourceConfiguration.class;
140+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
141+
context.register(configs);
142+
context.refresh();
143+
this.context = context;
144+
JobSynchronizationManager.register(jobExecution);
145+
}
146+
147+
@Before
148+
public void setup() {
149+
JobSynchronizationManager.release();
150+
jobExecution = new JobExecution(new JobInstance(5l, "JOB"), null, null);
151+
}
152+
153+
@After
154+
public void close() {
155+
JobSynchronizationManager.release();
156+
if (context != null) {
157+
context.close();
158+
}
159+
}
160+
161+
public static class SimpleCallable implements Callable<String> {
162+
private final String value;
163+
164+
private SimpleCallable(String value) {
165+
this.value = value;
166+
}
167+
168+
@Override
169+
public String call() throws Exception {
170+
return value;
171+
}
172+
}
173+
174+
public static class SimpleHolder {
175+
private final String value;
176+
177+
protected SimpleHolder() {
178+
value = "<WRONG>";
179+
}
180+
181+
public SimpleHolder(String value) {
182+
this.value = value;
183+
}
184+
185+
public String call() throws Exception {
186+
return value;
187+
}
188+
}
189+
190+
public static class Wrapper {
191+
192+
private SimpleHolder value;
193+
194+
public Wrapper(SimpleHolder value) {
195+
this.value = value;
196+
}
197+
198+
public SimpleHolder getValue() {
199+
return value;
200+
}
201+
202+
}
203+
204+
@Configuration
205+
@EnableBatchProcessing
206+
public static class JobScopeConfigurationInjectingProxy {
207+
208+
@Bean
209+
public Wrapper wrapper(SimpleHolder value) {
210+
return new Wrapper(value);
211+
}
212+
213+
@Bean
214+
@Scope(value="job", proxyMode = ScopedProxyMode.TARGET_CLASS)
215+
protected SimpleHolder value(@Value("#{jobName}")
216+
final String value) {
217+
return new SimpleHolder(value);
218+
}
219+
220+
}
221+
222+
@Configuration
223+
@EnableBatchProcessing
224+
public static class JobScopeConfigurationRequiringProxyTargetClass {
225+
226+
@Bean
227+
@Scope(value="job", proxyMode = ScopedProxyMode.TARGET_CLASS)
228+
protected SimpleHolder value(@Value("#{jobName}")
229+
final String value) {
230+
return new SimpleHolder(value);
231+
}
232+
233+
}
234+
235+
@Configuration
236+
@EnableBatchProcessing
237+
public static class JobScopeConfigurationWithDefaults {
238+
239+
@Bean
240+
@JobScope
241+
protected Callable<String> value(@Value("#{jobName}")
242+
final String value) {
243+
return new SimpleCallable(value);
244+
}
245+
246+
}
247+
248+
@Configuration
249+
@EnableBatchProcessing
250+
public static class JobScopeConfigurationForcingInterfaceProxy {
251+
252+
@Bean
253+
@Scope(value="job", proxyMode = ScopedProxyMode.INTERFACES)
254+
protected SimpleHolder value(@Value("#{jobName}")
255+
final String value) {
256+
return new SimpleHolder(value);
257+
}
258+
259+
}
260+
261+
}

spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AutoRegisteringStepScopeTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
*/
1616
package org.springframework.batch.core.configuration.xml;
1717

18-
import static org.junit.Assert.assertTrue;
19-
20-
import java.util.Map;
21-
2218
import org.junit.Test;
2319
import org.springframework.batch.core.scope.StepScope;
2420
import org.springframework.context.ConfigurableApplicationContext;
2521
import org.springframework.context.support.ClassPathXmlApplicationContext;
2622

23+
import java.util.Map;
24+
25+
import static org.junit.Assert.assertTrue;
26+
2727

2828
/**
2929
* @author Thomas Risberg

0 commit comments

Comments
 (0)