Skip to content

Commit 30b21a9

Browse files
committed
Make @configuration classes thread-safe
Refactor ConfigurationClassEnhancer so that BeanFactory instances are not held against CGLIB Callback objects. Enhanced @configuration classes now use the BeanFactoryAware interface in order to obtain a BeanFactory. This change has the additional benefit that a static final field can now be used to hold all Callback instances. Issue: SPR-10307
1 parent 8b90410 commit 30b21a9

File tree

3 files changed

+147
-98
lines changed

3 files changed

+147
-98
lines changed

spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java

Lines changed: 144 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,34 @@
1616

1717
package org.springframework.context.annotation;
1818

19+
import java.lang.reflect.Field;
1920
import java.lang.reflect.Method;
2021
import java.util.Arrays;
2122

2223
import org.apache.commons.logging.Log;
2324
import org.apache.commons.logging.LogFactory;
24-
2525
import org.springframework.aop.scope.ScopedProxyFactoryBean;
26+
import org.springframework.asm.Type;
2627
import org.springframework.beans.factory.BeanFactory;
28+
import org.springframework.beans.factory.BeanFactoryAware;
2729
import org.springframework.beans.factory.DisposableBean;
28-
import org.springframework.beans.factory.FactoryBean;
2930
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
3031
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
3132
import org.springframework.beans.factory.support.SimpleInstantiationStrategy;
33+
import org.springframework.cglib.core.ClassGenerator;
34+
import org.springframework.cglib.core.Constants;
35+
import org.springframework.cglib.core.DefaultGeneratorStrategy;
3236
import org.springframework.cglib.proxy.Callback;
3337
import org.springframework.cglib.proxy.CallbackFilter;
3438
import org.springframework.cglib.proxy.Enhancer;
3539
import org.springframework.cglib.proxy.MethodInterceptor;
3640
import org.springframework.cglib.proxy.MethodProxy;
3741
import org.springframework.cglib.proxy.NoOp;
42+
import org.springframework.cglib.transform.ClassEmitterTransformer;
43+
import org.springframework.cglib.transform.TransformingClassGenerator;
3844
import org.springframework.core.annotation.AnnotationUtils;
3945
import org.springframework.util.Assert;
46+
import org.springframework.util.ReflectionUtils;
4047

4148
/**
4249
* Enhances {@link Configuration} classes by generating a CGLIB subclass capable of
@@ -52,25 +59,18 @@ class ConfigurationClassEnhancer {
5259

5360
private static final Log logger = LogFactory.getLog(ConfigurationClassEnhancer.class);
5461

55-
private static final CallbackFilter CALLBACK_FILTER = new ConfigurationClassCallbackFilter();
56-
57-
private static final Callback DISPOSABLE_BEAN_METHOD_INTERCEPTOR = new DisposableBeanMethodInterceptor();
58-
59-
private static final Class<?>[] CALLBACK_TYPES =
60-
{BeanMethodInterceptor.class, DisposableBeanMethodInterceptor.class, NoOp.class};
62+
private static final Callback[] CALLBACKS = new Callback[] {
63+
new BeanMethodInterceptor(),
64+
new DisposableBeanMethodInterceptor(),
65+
new BeanFactoryAwareMethodInterceptor(),
66+
NoOp.INSTANCE
67+
};
6168

62-
private final Callback[] callbackInstances;
69+
private static final ConditionalCallbackFilter CALLBACK_FILTER =
70+
new ConditionalCallbackFilter(CALLBACKS);
6371

6472

65-
/**
66-
* Creates a new {@link ConfigurationClassEnhancer} instance.
67-
*/
68-
public ConfigurationClassEnhancer(ConfigurableBeanFactory beanFactory) {
69-
Assert.notNull(beanFactory, "BeanFactory must not be null");
70-
// Callback instances must be ordered in the same way as CALLBACK_TYPES and CALLBACK_FILTER
71-
this.callbackInstances = new Callback[]
72-
{new BeanMethodInterceptor(beanFactory), DISPOSABLE_BEAN_METHOD_INTERCEPTOR, NoOp.INSTANCE};
73-
}
73+
private static final String BEAN_FACTORY_FIELD = "$$beanFactory";
7474

7575
/**
7676
* Loads the specified class and generates a CGLIB subclass of it equipped with
@@ -106,7 +106,21 @@ private Enhancer newEnhancer(Class<?> superclass) {
106106
enhancer.setInterfaces(new Class[] {EnhancedConfiguration.class});
107107
enhancer.setUseFactory(false);
108108
enhancer.setCallbackFilter(CALLBACK_FILTER);
109-
enhancer.setCallbackTypes(CALLBACK_TYPES);
109+
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
110+
enhancer.setStrategy(new DefaultGeneratorStrategy() {
111+
@Override
112+
protected ClassGenerator transform(ClassGenerator cg) throws Exception {
113+
ClassEmitterTransformer transformer = new ClassEmitterTransformer() {
114+
@Override
115+
public void end_class() {
116+
declare_field(Constants.ACC_PUBLIC, BEAN_FACTORY_FIELD,
117+
Type.getType(BeanFactory.class), null);
118+
super.end_class();
119+
}
120+
};
121+
return new TransformingClassGenerator(cg, transformer);
122+
}
123+
});
110124
return enhancer;
111125
}
112126

@@ -117,7 +131,7 @@ private Enhancer newEnhancer(Class<?> superclass) {
117131
private Class<?> createClass(Enhancer enhancer) {
118132
Class<?> subclass = enhancer.createClass();
119133
// registering callbacks statically (as opposed to threadlocal) is critical for usage in an OSGi env (SPR-5932)
120-
Enhancer.registerStaticCallbacks(subclass, this.callbackInstances);
134+
Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
121135
return subclass;
122136
}
123137

@@ -128,67 +142,75 @@ private Class<?> createClass(Enhancer enhancer) {
128142
* Facilitates idempotent behavior for {@link ConfigurationClassEnhancer#enhance(Class)}
129143
* through checking to see if candidate classes are already assignable to it, e.g.
130144
* have already been enhanced.
131-
* <p>Also extends {@link DisposableBean}, as all enhanced
132-
* {@code @Configuration} classes must de-register static CGLIB callbacks on
133-
* destruction, which is handled by the (private) {@code DisposableBeanMethodInterceptor}.
145+
* <p>Also extends {@link DisposableBean} and {@link BeanFactoryAware}, as all
146+
* enhanced {@code @Configuration} classes require access to the {@link BeanFactory}
147+
* that created them and must de-register static CGLIB callbacks on destruction,
148+
* which is handled by the (private) {@code DisposableBeanMethodInterceptor}.
134149
* <p>Note that this interface is intended for framework-internal use only, however
135150
* must remain public in order to allow access to subclasses generated from other
136151
* packages (i.e. user code).
137152
*/
138-
public interface EnhancedConfiguration extends DisposableBean {
153+
public interface EnhancedConfiguration extends DisposableBean, BeanFactoryAware {
139154
}
140155

141156

142157
/**
143-
* CGLIB CallbackFilter implementation that points to BeanMethodInterceptor and
144-
* DisposableBeanMethodInterceptor.
158+
* Conditional {@link Callback}.
159+
* @see ConditionalCallbackFilter
145160
*/
146-
private static class ConfigurationClassCallbackFilter implements CallbackFilter {
147-
148-
public int accept(Method candidateMethod) {
149-
// Set up the callback filter to return the index of the BeanMethodInterceptor when
150-
// handling a @Bean-annotated method; otherwise, return index of the NoOp callback.
151-
if (BeanAnnotationHelper.isBeanAnnotated(candidateMethod)) {
152-
return 0;
153-
}
154-
if (DisposableBeanMethodInterceptor.isDestroyMethod(candidateMethod)) {
155-
return 1;
156-
}
157-
return 2;
158-
}
161+
private static interface ConditionalCallback extends Callback {
162+
boolean isMatch(Method candidateMethod);
159163
}
160164

161-
162165
/**
163-
* Intercepts calls to {@link FactoryBean#getObject()}, delegating to calling
164-
* {@link BeanFactory#getBean(String)} in order to respect caching / scoping.
165-
* @see BeanMethodInterceptor#intercept(Object, Method, Object[], MethodProxy)
166-
* @see BeanMethodInterceptor#enhanceFactoryBean(Class, String)
166+
* A {@link CallbackFilter} that works by interrogating {@link Callback}s in the order
167+
* that they are defined via {@link ConditionalCallback}.
167168
*/
168-
private static class GetObjectMethodInterceptor implements MethodInterceptor {
169+
private static class ConditionalCallbackFilter implements CallbackFilter {
169170

170-
private final ConfigurableBeanFactory beanFactory;
171+
private final Callback[] callbacks;
171172

172-
private final String beanName;
173+
private final Class<?>[] callbackTypes;
173174

174-
public GetObjectMethodInterceptor(ConfigurableBeanFactory beanFactory, String beanName) {
175-
this.beanFactory = beanFactory;
176-
this.beanName = beanName;
175+
public ConditionalCallbackFilter(Callback[] callbacks) {
176+
this.callbacks = callbacks;
177+
this.callbackTypes = new Class<?>[callbacks.length];
178+
for (int i = 0; i < callbacks.length; i++) {
179+
callbackTypes[i] = callbacks[i].getClass();
180+
}
177181
}
178182

179-
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
180-
return this.beanFactory.getBean(this.beanName);
183+
@Override
184+
public int accept(Method method) {
185+
for (int i = 0; i < callbacks.length; i++) {
186+
if (!(callbacks[i] instanceof ConditionalCallback) ||
187+
((ConditionalCallback) callbacks[i]).isMatch(method)) {
188+
return i;
189+
}
190+
}
191+
throw new IllegalStateException("No callback available for method "
192+
+ method.getName());
181193
}
182-
}
183194

195+
public Class<?>[] getCallbackTypes() {
196+
return callbackTypes;
197+
}
198+
}
184199

185200
/**
186201
* Intercepts the invocation of any {@link DisposableBean#destroy()} on @Configuration
187202
* class instances for the purpose of de-registering CGLIB callbacks. This helps avoid
188203
* garbage collection issues. See SPR-7901.
189204
* @see EnhancedConfiguration
190205
*/
191-
private static class DisposableBeanMethodInterceptor implements MethodInterceptor {
206+
private static class DisposableBeanMethodInterceptor implements MethodInterceptor,
207+
ConditionalCallback {
208+
209+
public boolean isMatch(Method candidateMethod) {
210+
return candidateMethod.getName().equals("destroy")
211+
&& candidateMethod.getParameterTypes().length == 0
212+
&& DisposableBean.class.isAssignableFrom(candidateMethod.getDeclaringClass());
213+
}
192214

193215
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
194216
Enhancer.registerStaticCallbacks(obj.getClass(), null);
@@ -200,10 +222,38 @@ public Object intercept(Object obj, Method method, Object[] args, MethodProxy pr
200222
return null;
201223
}
202224

203-
public static boolean isDestroyMethod(Method candidateMethod) {
204-
return candidateMethod.getName().equals("destroy") &&
205-
candidateMethod.getParameterTypes().length == 0 &&
206-
DisposableBean.class.isAssignableFrom(candidateMethod.getDeclaringClass());
225+
}
226+
227+
228+
/**
229+
* Intercepts the invocation of any
230+
* {@link BeanFactoryAware#setBeanFactory(BeanFactory)} on {@code @Configuration}
231+
* class instances for the purpose of recording the {@link BeanFactory}.
232+
*
233+
* @see EnhancedConfiguration
234+
*/
235+
private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptor,
236+
ConditionalCallback {
237+
238+
public boolean isMatch(Method candidateMethod) {
239+
return candidateMethod.getName().equals("setBeanFactory")
240+
&& candidateMethod.getParameterTypes().length == 1
241+
&& candidateMethod.getParameterTypes()[0].equals(BeanFactory.class)
242+
&& BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass());
243+
}
244+
245+
public Object intercept(Object obj, Method method, Object[] args,
246+
MethodProxy proxy) throws Throwable {
247+
Field field = obj.getClass().getDeclaredField(BEAN_FACTORY_FIELD);
248+
Assert.state(field != null, "Unable to find generated bean factory field");
249+
field.set(obj, args[0]);
250+
251+
// does the actual (non-CGLIB) superclass actually implement BeanFactoryAware?
252+
// if so, call its setBeanFactory() method. If not, just exit.
253+
if (BeanFactoryAware.class.isAssignableFrom(obj.getClass().getSuperclass())) {
254+
return proxy.invokeSuper(obj, args);
255+
}
256+
return null;
207257
}
208258
}
209259

@@ -214,20 +264,10 @@ public static boolean isDestroyMethod(Method candidateMethod) {
214264
* @see Bean
215265
* @see ConfigurationClassEnhancer
216266
*/
217-
private static class BeanMethodInterceptor implements MethodInterceptor {
218-
219-
private static final Class<?>[] CALLBACK_TYPES = {GetObjectMethodInterceptor.class, NoOp.class};
220-
221-
private static final CallbackFilter CALLBACK_FILTER = new CallbackFilter() {
222-
public int accept(Method method) {
223-
return (method.getName().equals("getObject") ? 0 : 1);
224-
}
225-
};
226-
227-
private final ConfigurableBeanFactory beanFactory;
267+
private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {
228268

229-
public BeanMethodInterceptor(ConfigurableBeanFactory beanFactory) {
230-
this.beanFactory = beanFactory;
269+
public boolean isMatch(Method candidateMethod) {
270+
return BeanAnnotationHelper.isBeanAnnotated(candidateMethod);
231271
}
232272

233273
/**
@@ -240,13 +280,14 @@ public BeanMethodInterceptor(ConfigurableBeanFactory beanFactory) {
240280
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
241281
MethodProxy cglibMethodProxy) throws Throwable {
242282

283+
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
243284
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
244285

245286
// determine whether this bean is a scoped-proxy
246287
Scope scope = AnnotationUtils.findAnnotation(beanMethod, Scope.class);
247288
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
248289
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
249-
if (this.beanFactory.isCurrentlyInCreation(scopedBeanName)) {
290+
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
250291
beanName = scopedBeanName;
251292
}
252293
}
@@ -258,19 +299,20 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object
258299
// proxy that intercepts calls to getObject() and returns any cached bean instance.
259300
// this ensures that the semantics of calling a FactoryBean from within @Bean methods
260301
// is the same as that of referring to a FactoryBean within XML. See SPR-6602.
261-
if (factoryContainsBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName) && factoryContainsBean(beanName)) {
262-
Object factoryBean = this.beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
302+
if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
303+
factoryContainsBean(beanFactory, beanName)) {
304+
Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
263305
if (factoryBean instanceof ScopedProxyFactoryBean) {
264306
// pass through - scoped proxy factory beans are a special case and should not
265307
// be further proxied
266308
}
267309
else {
268310
// it is a candidate FactoryBean - go ahead with enhancement
269-
return enhanceFactoryBean(factoryBean.getClass(), beanName);
311+
return enhanceFactoryBean(factoryBean.getClass(), beanFactory, beanName);
270312
}
271313
}
272314

273-
if (isCurrentlyInvokedFactoryMethod(beanMethod) && !this.beanFactory.containsSingleton(beanName)) {
315+
if (isCurrentlyInvokedFactoryMethod(beanMethod) && !beanFactory.containsSingleton(beanName)) {
274316
// the factory is calling the bean method in order to instantiate and register the bean
275317
// (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
276318
// create the bean instance.
@@ -290,20 +332,19 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object
290332
// call to the bean method, direct or indirect. The bean may have already been
291333
// marked as 'in creation' in certain autowiring scenarios; if so, temporarily
292334
// set the in-creation status to false in order to avoid an exception.
293-
boolean alreadyInCreation = this.beanFactory.isCurrentlyInCreation(beanName);
335+
boolean alreadyInCreation = beanFactory.isCurrentlyInCreation(beanName);
294336
try {
295337
if (alreadyInCreation) {
296-
this.beanFactory.setCurrentlyInCreation(beanName, false);
338+
beanFactory.setCurrentlyInCreation(beanName, false);
297339
}
298-
return this.beanFactory.getBean(beanName);
340+
return beanFactory.getBean(beanName);
299341
}
300342
finally {
301343
if (alreadyInCreation) {
302-
this.beanFactory.setCurrentlyInCreation(beanName, true);
344+
beanFactory.setCurrentlyInCreation(beanName, true);
303345
}
304346
}
305347
}
306-
307348
}
308349

309350
/**
@@ -319,8 +360,8 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object
319360
* @param beanName name of bean to check for
320361
* @return whether <var>beanName</var> already exists in the factory
321362
*/
322-
private boolean factoryContainsBean(String beanName) {
323-
return (this.beanFactory.containsBean(beanName) && !this.beanFactory.isCurrentlyInCreation(beanName));
363+
private boolean factoryContainsBean(ConfigurableBeanFactory beanFactory, String beanName) {
364+
return (beanFactory.containsBean(beanName) && !beanFactory.isCurrentlyInCreation(beanName));
324365
}
325366

326367
/**
@@ -342,20 +383,29 @@ private boolean isCurrentlyInvokedFactoryMethod(Method method) {
342383
* instance directly. If a FactoryBean instance is fetched through the container via &-dereferencing,
343384
* it will not be proxied. This too is aligned with the way XML configuration works.
344385
*/
345-
private Object enhanceFactoryBean(Class<?> fbClass, String beanName) throws InstantiationException, IllegalAccessException {
386+
private Object enhanceFactoryBean(Class<?> fbClass, final ConfigurableBeanFactory beanFactory,
387+
final String beanName) throws InstantiationException, IllegalAccessException {
346388
Enhancer enhancer = new Enhancer();
347389
enhancer.setSuperclass(fbClass);
348390
enhancer.setUseFactory(false);
349-
enhancer.setCallbackFilter(CALLBACK_FILTER);
350-
// Callback instances must be ordered in the same way as CALLBACK_TYPES and CALLBACK_FILTER
351-
Callback[] callbackInstances = new Callback[] {
352-
new GetObjectMethodInterceptor(this.beanFactory, beanName),
353-
NoOp.INSTANCE
354-
};
355-
enhancer.setCallbackTypes(CALLBACK_TYPES);
356-
Class<?> fbSubclass = enhancer.createClass();
357-
Enhancer.registerCallbacks(fbSubclass, callbackInstances);
358-
return fbSubclass.newInstance();
391+
enhancer.setCallback(new MethodInterceptor() {
392+
public Object intercept(Object obj, Method method, Object[] args,
393+
MethodProxy proxy) throws Throwable {
394+
if (method.getName().equals("getObject") && args.length == 0) {
395+
return beanFactory.getBean(beanName);
396+
}
397+
return proxy.invokeSuper(obj, args);
398+
}
399+
});
400+
return enhancer.create();
401+
}
402+
403+
private ConfigurableBeanFactory getBeanFactory(Object enhancedConfigInstance) {
404+
Field field = ReflectionUtils.findField(enhancedConfigInstance.getClass(), BEAN_FACTORY_FIELD);
405+
Assert.state(field != null, "Unable to find generated bean factory field");
406+
Object beanFactory = ReflectionUtils.getField(field, enhancedConfigInstance);
407+
Assert.isInstanceOf(ConfigurableBeanFactory.class, beanFactory);
408+
return (ConfigurableBeanFactory) beanFactory;
359409
}
360410
}
361411

0 commit comments

Comments
 (0)