Skip to content

Commit 241d9f1

Browse files
committed
BATCH-1701: Implementation of JobScope
* Updated Job and Step scopes to use common class introduced by jpraet * Updated Job and Step Synchronization Managers to use common class introduced by jpraet * Updated JobScope related code to not be concerned with Spring 2.5 (which is what batch was on when the PR was opened).
1 parent 2436d84 commit 241d9f1

File tree

20 files changed

+439
-592
lines changed

20 files changed

+439
-592
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -335,9 +335,9 @@ public final void execute(JobExecution execution) {
335335
if (execution.getStatus().isLessThanOrEqualTo(BatchStatus.STOPPED)
336336
&& execution.getStepExecutions().isEmpty()) {
337337
ExitStatus exitStatus = execution.getExitStatus();
338-
execution
339-
.setExitStatus(exitStatus.and(ExitStatus.NOOP
340-
.addExitDescription("All steps already completed or no steps configured for this job.")));
338+
ExitStatus newExitStatus =
339+
ExitStatus.NOOP.addExitDescription("All steps already completed or no steps configured for this job.");
340+
execution.setExitStatus(exitStatus.and(newExitStatus));
341341
}
342342

343343
execution.setEndTime(new Date());
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
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.scope;
17+
18+
import org.springframework.aop.scope.ScopedProxyUtils;
19+
import org.springframework.batch.core.scope.context.StepContext;
20+
import org.springframework.beans.BeansException;
21+
import org.springframework.beans.factory.config.BeanDefinition;
22+
import org.springframework.beans.factory.config.BeanDefinitionHolder;
23+
import org.springframework.beans.factory.config.BeanDefinitionVisitor;
24+
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
25+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
26+
import org.springframework.beans.factory.config.Scope;
27+
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
28+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
29+
import org.springframework.core.Ordered;
30+
import org.springframework.util.Assert;
31+
import org.springframework.util.StringValueResolver;
32+
33+
/**
34+
* ScopeSupport.
35+
*
36+
* @author Michael Minella
37+
* @since 3.0
38+
*/
39+
public abstract class BatchScopeSupport implements Scope, BeanFactoryPostProcessor, Ordered {
40+
41+
private boolean autoProxy = true;
42+
43+
private boolean proxyTargetClass = false;
44+
45+
private String name;
46+
47+
private int order = Ordered.LOWEST_PRECEDENCE;
48+
49+
/**
50+
* @param order the order value to set priority of callback execution for
51+
* the {@link BeanFactoryPostProcessor} part of this scope bean.
52+
*/
53+
public void setOrder(int order) {
54+
this.order = order;
55+
}
56+
57+
@Override
58+
public int getOrder() {
59+
return order;
60+
}
61+
62+
public String getName() {
63+
return this.name;
64+
}
65+
66+
/**
67+
* Public setter for the name property. This can then be used as a bean
68+
* definition attribute, e.g. scope="job".
69+
*
70+
* @param name the name to set for this scope.
71+
*/
72+
public void setName(String name) {
73+
this.name = name;
74+
}
75+
76+
/**
77+
* Flag to indicate that proxies should use dynamic subclassing. This allows
78+
* classes with no interface to be proxied. Defaults to false.
79+
*
80+
* @param proxyTargetClass set to true to have proxies created using dynamic
81+
* subclasses
82+
*/
83+
public void setProxyTargetClass(boolean proxyTargetClass) {
84+
this.proxyTargetClass = proxyTargetClass;
85+
}
86+
87+
/**
88+
* Flag to indicate that bean definitions need not be auto proxied. This gives control back to the declarer of the
89+
* bean definition (e.g. in an @Configuration class).
90+
*
91+
* @param autoProxy the flag value to set (default true)
92+
*/
93+
public void setAutoProxy(boolean autoProxy) {
94+
this.autoProxy = autoProxy;
95+
}
96+
97+
public abstract String getTargetNamePrefix();
98+
99+
/**
100+
* Register this scope with the enclosing BeanFactory.
101+
*
102+
* @see BeanFactoryPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory)
103+
*
104+
* @param beanFactory the BeanFactory to register with
105+
* @throws BeansException if there is a problem.
106+
*/
107+
@Override
108+
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
109+
110+
beanFactory.registerScope(name, this);
111+
112+
if(!autoProxy) {
113+
return;
114+
}
115+
116+
Assert.state(beanFactory instanceof BeanDefinitionRegistry,
117+
"BeanFactory was not a BeanDefinitionRegistry, so JobScope cannot be used.");
118+
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
119+
120+
for (String beanName : beanFactory.getBeanDefinitionNames()) {
121+
if (!beanName.startsWith(getTargetNamePrefix())) {
122+
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
123+
// Replace this or any of its inner beans with scoped proxy if it
124+
// has this scope
125+
boolean scoped = name.equals(definition.getScope());
126+
Scopifier scopifier = new Scopifier(registry, name, proxyTargetClass, scoped);
127+
scopifier.visitBeanDefinition(definition);
128+
129+
if (scoped && !definition.isAbstract()) {
130+
createScopedProxy(beanName, definition, registry, proxyTargetClass);
131+
}
132+
}
133+
}
134+
135+
}
136+
137+
/**
138+
* Wrap a target bean definition in a proxy that defers initialization until
139+
* after the {@link StepContext} is available. Amounts to adding
140+
* <aop-auto-proxy/> to a step scoped bean.
141+
*
142+
* @param beanName the bean name to replace
143+
* @param definition the bean definition to replace
144+
* @param registry the enclosing {@link BeanDefinitionRegistry}
145+
* @param proxyTargetClass true if we need to force use of dynamic
146+
* subclasses
147+
* @return a {@link BeanDefinitionHolder} for the new representation of the
148+
* target. Caller should register it if needed to be visible at top level in
149+
* bean factory.
150+
*/
151+
protected static BeanDefinitionHolder createScopedProxy(String beanName, BeanDefinition definition,
152+
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
153+
154+
BeanDefinitionHolder proxyHolder;
155+
156+
proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(definition, beanName), registry,
157+
proxyTargetClass);
158+
159+
registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition());
160+
161+
return proxyHolder;
162+
163+
}
164+
165+
/**
166+
* Helper class to scan a bean definition hierarchy and force the use of
167+
* auto-proxy for step scoped beans.
168+
*
169+
* @author Dave Syer
170+
*
171+
*/
172+
protected static class Scopifier extends BeanDefinitionVisitor {
173+
174+
private final boolean proxyTargetClass;
175+
176+
private final BeanDefinitionRegistry registry;
177+
178+
private final String scope;
179+
180+
private final boolean scoped;
181+
182+
public Scopifier(BeanDefinitionRegistry registry, String scope, boolean proxyTargetClass, boolean scoped) {
183+
super(new StringValueResolver() {
184+
@Override
185+
public String resolveStringValue(String value) {
186+
return value;
187+
}
188+
});
189+
this.registry = registry;
190+
this.proxyTargetClass = proxyTargetClass;
191+
this.scope = scope;
192+
this.scoped = scoped;
193+
}
194+
195+
@Override
196+
protected Object resolveValue(Object value) {
197+
198+
BeanDefinition definition = null;
199+
String beanName = null;
200+
if (value instanceof BeanDefinition) {
201+
definition = (BeanDefinition) value;
202+
beanName = BeanDefinitionReaderUtils.generateBeanName(definition, registry);
203+
}
204+
else if (value instanceof BeanDefinitionHolder) {
205+
BeanDefinitionHolder holder = (BeanDefinitionHolder) value;
206+
definition = holder.getBeanDefinition();
207+
beanName = holder.getBeanName();
208+
}
209+
210+
if (definition != null) {
211+
boolean nestedScoped = scope.equals(definition.getScope());
212+
boolean scopeChangeRequiresProxy = !scoped && nestedScoped;
213+
if (scopeChangeRequiresProxy) {
214+
// Exit here so that nested inner bean definitions are not
215+
// analysed
216+
return createScopedProxy(beanName, definition, registry, proxyTargetClass);
217+
}
218+
}
219+
220+
// Nested inner bean definitions are recursively analysed here
221+
value = super.resolveValue(value);
222+
return value;
223+
224+
}
225+
}
226+
}

spring-batch-core/src/main/java/org/springframework/batch/core/scope/JobScope.java

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2007 the original author or authors.
2+
* Copyright 2006-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,8 +19,6 @@
1919
import org.apache.commons.logging.LogFactory;
2020
import org.springframework.batch.core.scope.context.JobContext;
2121
import org.springframework.batch.core.scope.context.JobSynchronizationManager;
22-
import org.springframework.batch.core.scope.util.ContextFactory;
23-
import org.springframework.batch.core.scope.util.JobContextFactory;
2422
import org.springframework.beans.BeanWrapper;
2523
import org.springframework.beans.BeanWrapperImpl;
2624
import org.springframework.beans.factory.ObjectFactory;
@@ -32,31 +30,34 @@
3230
* job. All objects in this scope are <aop:scoped-proxy/> (no need to
3331
* decorate the bean definitions).<br/>
3432
* <br/>
35-
*
33+
*
3634
* In addition, support is provided for late binding of references accessible
3735
* from the {@link JobContext} using #{..} placeholders. Using this feature,
3836
* bean properties can be pulled from the job or job execution context and the
3937
* job parameters. E.g.
40-
*
38+
*
4139
* <pre>
4240
* &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;job&quot;&gt;
4341
* &lt;property name=&quot;name&quot; value=&quot;#{jobParameters[input]}&quot; /&gt;
4442
* &lt;/bean&gt;
45-
*
43+
*
4644
* &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;job&quot;&gt;
4745
* &lt;property name=&quot;name&quot; value=&quot;#{jobExecutionContext['input.stem']}.txt&quot; /&gt;
4846
* &lt;/bean&gt;
4947
* </pre>
50-
*
48+
*
5149
* The {@link JobContext} is referenced using standard bean property paths (as
5250
* per {@link BeanWrapper}). The examples above all show the use of the Map
5351
* accessors provided as a convenience for job attributes.
54-
*
52+
*
5553
* @author Dave Syer
5654
* @author Jimmy Praet (create JobScope based on {@link StepScope})
57-
* @since 2.0
55+
* @author Michael Minella
56+
* @since 3.0
5857
*/
59-
public class JobScope extends ScopeSupport {
58+
public class JobScope extends BatchScopeSupport {
59+
60+
private static final String TARGET_NAME_PREFIX = "jobScopedTarget.";
6061

6162
private Log logger = LogFactory.getLog(getClass());
6263

@@ -67,20 +68,15 @@ public class JobScope extends ScopeSupport {
6768
*/
6869
public static final String ID_KEY = "JOB_IDENTIFIER";
6970

70-
/**
71-
* The ContextFactory.
72-
*/
73-
private static final ContextFactory CONTEXT_FACTORY = new JobContextFactory();
74-
7571
public JobScope() {
76-
super("job", CONTEXT_FACTORY);
72+
super();
73+
setName("job");
7774
}
7875

7976
/**
80-
* If Spring 3.0 is available, this will be used to resolve expressions in
81-
* job-scoped beans. This method is part of the Scope SPI in Spring 3.0,
82-
* but should just be ignored by earlier versions of Spring.
77+
* This will be used to resolve expressions in job-scoped beans.
8378
*/
79+
@Override
8480
public Object resolveContextualObject(String key) {
8581
JobContext context = getContext();
8682
// TODO: support for attributes as well maybe (setters not exposed yet
@@ -91,8 +87,9 @@ public Object resolveContextualObject(String key) {
9187
/**
9288
* @see Scope#get(String, ObjectFactory)
9389
*/
90+
@SuppressWarnings("rawtypes")
91+
@Override
9492
public Object get(String name, ObjectFactory objectFactory) {
95-
9693
JobContext context = getContext();
9794
Object scopedObject = context.getAttribute(name);
9895

@@ -118,6 +115,7 @@ public Object get(String name, ObjectFactory objectFactory) {
118115
/**
119116
* @see Scope#getConversationId()
120117
*/
118+
@Override
121119
public String getConversationId() {
122120
JobContext context = getContext();
123121
return context.getId();
@@ -126,6 +124,7 @@ public String getConversationId() {
126124
/**
127125
* @see Scope#registerDestructionCallback(String, Runnable)
128126
*/
127+
@Override
129128
public void registerDestructionCallback(String name, Runnable callback) {
130129
JobContext context = getContext();
131130
logger.debug(String.format("Registered destruction callback in scope=%s, name=%s", this.getName(), name));
@@ -135,6 +134,7 @@ public void registerDestructionCallback(String name, Runnable callback) {
135134
/**
136135
* @see Scope#remove(String)
137136
*/
137+
@Override
138138
public Object remove(String name) {
139139
JobContext context = getContext();
140140
logger.debug(String.format("Removing from scope=%s, name=%s", this.getName(), name));
@@ -144,7 +144,7 @@ public Object remove(String name) {
144144
/**
145145
* Get an attribute accessor in the form of a {@link JobContext} that can
146146
* be used to store scoped bean instances.
147-
*
147+
*
148148
* @return the current job context which we can use as a scope storage
149149
* medium
150150
*/
@@ -156,4 +156,8 @@ private JobContext getContext() {
156156
return context;
157157
}
158158

159-
}
159+
@Override
160+
public String getTargetNamePrefix() {
161+
return TARGET_NAME_PREFIX;
162+
}
163+
}

0 commit comments

Comments
 (0)