Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c806139

Browse files
philwebbcbeams
authored andcommittedOct 12, 2012
Prevent memory leaks with @configuration beans
Refactor ConfigurationClassEnhancer to allow cglib caching of generated classes. Prior to this commit each enhanced @configuration class would consume permgen space when created. The CallbackFilter and Callback Types are now defined as static final members so that they can be shared by all enhancers. Only the callbackInstances remain specific to a @configuration class and these are not used by cglib as part of the cache key. Issue: SPR-9851
1 parent 365a42d commit c806139

File tree

1 file changed

+51
-54
lines changed

1 file changed

+51
-54
lines changed
 

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

Lines changed: 51 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package org.springframework.context.annotation;
1818

1919
import java.lang.reflect.Method;
20-
import java.util.ArrayList;
21-
import java.util.List;
2220

2321
import org.apache.commons.logging.Log;
2422
import org.apache.commons.logging.LogFactory;
@@ -54,40 +52,40 @@ class ConfigurationClassEnhancer {
5452

5553
private static final Log logger = LogFactory.getLog(ConfigurationClassEnhancer.class);
5654

57-
private final List<Callback> callbackInstances = new ArrayList<Callback>();
55+
private static final Class<?>[] CALLBACK_TYPES = { BeanMethodInterceptor.class,
56+
DisposableBeanMethodInterceptor.class, NoOp.class };
5857

59-
private final List<Class<? extends Callback>> callbackTypes = new ArrayList<Class<? extends Callback>>();
58+
private static final CallbackFilter CALLBACK_FILTER = new CallbackFilter() {
6059

61-
private final CallbackFilter callbackFilter;
60+
public int accept(Method candidateMethod) {
61+
// Set up the callback filter to return the index of the BeanMethodInterceptor when
62+
// handling a @Bean-annotated method; otherwise, return index of the NoOp callback.
63+
if (BeanAnnotationHelper.isBeanAnnotated(candidateMethod)) {
64+
return 0;
65+
}
66+
if (DisposableBeanMethodInterceptor.isDestroyMethod(candidateMethod)) {
67+
return 1;
68+
}
69+
return 2;
70+
}
71+
};
72+
73+
private static final Callback DISPOSABLE_BEAN_METHOD_INTERCEPTOR = new DisposableBeanMethodInterceptor();
74+
75+
76+
private final Callback[] callbackInstances;
6277

6378

6479
/**
6580
* Creates a new {@link ConfigurationClassEnhancer} instance.
6681
*/
6782
public ConfigurationClassEnhancer(ConfigurableBeanFactory beanFactory) {
6883
Assert.notNull(beanFactory, "BeanFactory must not be null");
69-
70-
this.callbackInstances.add(new BeanMethodInterceptor(beanFactory));
71-
this.callbackInstances.add(new DisposableBeanMethodInterceptor());
72-
this.callbackInstances.add(NoOp.INSTANCE);
73-
74-
for (Callback callback : this.callbackInstances) {
75-
this.callbackTypes.add(callback.getClass());
76-
}
77-
78-
// Set up the callback filter to return the index of the BeanMethodInterceptor when
79-
// handling a @Bean-annotated method; otherwise, return index of the NoOp callback.
80-
callbackFilter = new CallbackFilter() {
81-
public int accept(Method candidateMethod) {
82-
if (BeanAnnotationHelper.isBeanAnnotated(candidateMethod)) {
83-
return 0;
84-
}
85-
if (DisposableBeanMethodInterceptor.isDestroyMethod(candidateMethod)) {
86-
return 1;
87-
}
88-
return 2;
89-
}
90-
};
84+
// Callback instances must be ordered in the same way as CALLBACK_TYPES and CALLBACK_FILTER
85+
this.callbackInstances = new Callback[] {
86+
new BeanMethodInterceptor(beanFactory),
87+
DISPOSABLE_BEAN_METHOD_INTERCEPTOR,
88+
NoOp.INSTANCE };
9189
}
9290

9391
/**
@@ -135,15 +133,11 @@ public interface EnhancedConfiguration extends DisposableBean {
135133
*/
136134
private Enhancer newEnhancer(Class<?> superclass) {
137135
Enhancer enhancer = new Enhancer();
138-
// Because callbackFilter and callbackTypes are dynamically populated
139-
// there's no opportunity for caching. This does not appear to be causing
140-
// any performance problem.
141-
enhancer.setUseCache(false);
142136
enhancer.setSuperclass(superclass);
143137
enhancer.setInterfaces(new Class[] {EnhancedConfiguration.class});
144138
enhancer.setUseFactory(false);
145-
enhancer.setCallbackFilter(this.callbackFilter);
146-
enhancer.setCallbackTypes(this.callbackTypes.toArray(new Class[this.callbackTypes.size()]));
139+
enhancer.setCallbackFilter(CALLBACK_FILTER);
140+
enhancer.setCallbackTypes(CALLBACK_TYPES);
147141
return enhancer;
148142
}
149143

@@ -154,7 +148,7 @@ private Enhancer newEnhancer(Class<?> superclass) {
154148
private Class<?> createClass(Enhancer enhancer) {
155149
Class<?> subclass = enhancer.createClass();
156150
// registering callbacks statically (as opposed to threadlocal) is critical for usage in an OSGi env (SPR-5932)
157-
Enhancer.registerStaticCallbacks(subclass, this.callbackInstances.toArray(new Callback[this.callbackInstances.size()]));
151+
Enhancer.registerStaticCallbacks(subclass, this.callbackInstances);
158152
return subclass;
159153
}
160154

@@ -166,7 +160,7 @@ private Class<?> createClass(Enhancer enhancer) {
166160
* @see BeanMethodInterceptor#enhanceFactoryBean(Class, String)
167161
*/
168162
private static class GetObjectMethodInterceptor implements MethodInterceptor {
169-
163+
170164
private final ConfigurableBeanFactory beanFactory;
171165
private final String beanName;
172166

@@ -178,7 +172,7 @@ public GetObjectMethodInterceptor(ConfigurableBeanFactory beanFactory, String be
178172
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
179173
return beanFactory.getBean(beanName);
180174
}
181-
175+
182176
}
183177

184178

@@ -216,8 +210,19 @@ public static boolean isDestroyMethod(Method candidateMethod) {
216210
*/
217211
private static class BeanMethodInterceptor implements MethodInterceptor {
218212

213+
private static final Class<?>[] CALLBACK_TYPES = {
214+
GetObjectMethodInterceptor.class, NoOp.class };
215+
216+
private static final CallbackFilter CALLBACK_FITLER = new CallbackFilter() {
217+
public int accept(Method method) {
218+
return method.getName().equals("getObject") ? 0 : 1;
219+
}
220+
};
221+
222+
219223
private final ConfigurableBeanFactory beanFactory;
220224

225+
221226
public BeanMethodInterceptor(ConfigurableBeanFactory beanFactory) {
222227
this.beanFactory = beanFactory;
223228
}
@@ -246,7 +251,7 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object
246251

247252
// to handle the case of an inter-bean method reference, we must explicitly check the
248253
// container for already cached instances
249-
254+
250255
// first, check to see if the requested bean is a FactoryBean. If so, create a subclass
251256
// proxy that intercepts calls to getObject() and returns any cached bean instance.
252257
// this ensures that the semantics of calling a FactoryBean from within @Bean methods
@@ -328,26 +333,18 @@ private boolean factoryContainsBean(String beanName) {
328333
*/
329334
private Object enhanceFactoryBean(Class<?> fbClass, String beanName) throws InstantiationException, IllegalAccessException {
330335
Enhancer enhancer = new Enhancer();
331-
enhancer.setUseCache(false);
332336
enhancer.setSuperclass(fbClass);
333337
enhancer.setUseFactory(false);
334-
enhancer.setCallbackFilter(new CallbackFilter() {
335-
public int accept(Method method) {
336-
return method.getName().equals("getObject") ? 0 : 1;
337-
}
338-
});
339-
List<Callback> callbackInstances = new ArrayList<Callback>();
340-
callbackInstances.add(new GetObjectMethodInterceptor(this.beanFactory, beanName));
341-
callbackInstances.add(NoOp.INSTANCE);
342-
343-
List<Class<? extends Callback>> callbackTypes = new ArrayList<Class<? extends Callback>>();
344-
for (Callback callback : callbackInstances) {
345-
callbackTypes.add(callback.getClass());
346-
}
347-
348-
enhancer.setCallbackTypes(callbackTypes.toArray(new Class[callbackTypes.size()]));
338+
enhancer.setCallbackFilter(CALLBACK_FITLER);
339+
// Callback instances must be ordered in the same way as CALLBACK_TYPES and CALLBACK_FILTER
340+
Callback[] callbackInstances = new Callback[] {
341+
new GetObjectMethodInterceptor(this.beanFactory, beanName),
342+
NoOp.INSTANCE
343+
};
344+
345+
enhancer.setCallbackTypes(CALLBACK_TYPES);
349346
Class<?> fbSubclass = enhancer.createClass();
350-
Enhancer.registerCallbacks(fbSubclass, callbackInstances.toArray(new Callback[callbackInstances.size()]));
347+
Enhancer.registerCallbacks(fbSubclass, callbackInstances);
351348
return fbSubclass.newInstance();
352349
}
353350

0 commit comments

Comments
 (0)
Please sign in to comment.