Skip to content

Commit ced6bb4

Browse files
philwebbcbeams
authored andcommitted
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 Backport-Commit: c806139
1 parent 4525527 commit ced6bb4

File tree

1 file changed

+53
-55
lines changed

1 file changed

+53
-55
lines changed

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

Lines changed: 53 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -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 net.sf.cglib.proxy.Callback;
2422
import net.sf.cglib.proxy.CallbackFilter;
@@ -29,6 +27,7 @@
2927

3028
import org.apache.commons.logging.Log;
3129
import org.apache.commons.logging.LogFactory;
30+
3231
import org.springframework.aop.scope.ScopedProxyFactoryBean;
3332
import org.springframework.beans.factory.BeanFactory;
3433
import org.springframework.beans.factory.DisposableBean;
@@ -53,40 +52,40 @@ class ConfigurationClassEnhancer {
5352

5453
private static final Log logger = LogFactory.getLog(ConfigurationClassEnhancer.class);
5554

56-
private final List<Callback> callbackInstances = new ArrayList<Callback>();
55+
private static final Class<?>[] CALLBACK_TYPES = { BeanMethodInterceptor.class,
56+
DisposableBeanMethodInterceptor.class, NoOp.class };
57+
58+
private static final CallbackFilter CALLBACK_FILTER = new CallbackFilter() {
59+
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();
5774

58-
private final List<Class<? extends Callback>> callbackTypes = new ArrayList<Class<? extends Callback>>();
5975

60-
private final CallbackFilter callbackFilter;
76+
private final Callback[] callbackInstances;
6177

6278

6379
/**
6480
* Creates a new {@link ConfigurationClassEnhancer} instance.
6581
*/
6682
public ConfigurationClassEnhancer(ConfigurableBeanFactory beanFactory) {
6783
Assert.notNull(beanFactory, "BeanFactory must not be null");
68-
69-
this.callbackInstances.add(new BeanMethodInterceptor(beanFactory));
70-
this.callbackInstances.add(new DisposableBeanMethodInterceptor());
71-
this.callbackInstances.add(NoOp.INSTANCE);
72-
73-
for (Callback callback : this.callbackInstances) {
74-
this.callbackTypes.add(callback.getClass());
75-
}
76-
77-
// Set up the callback filter to return the index of the BeanMethodInterceptor when
78-
// handling a @Bean-annotated method; otherwise, return index of the NoOp callback.
79-
callbackFilter = new CallbackFilter() {
80-
public int accept(Method candidateMethod) {
81-
if (BeanAnnotationHelper.isBeanAnnotated(candidateMethod)) {
82-
return 0;
83-
}
84-
if (DisposableBeanMethodInterceptor.isDestroyMethod(candidateMethod)) {
85-
return 1;
86-
}
87-
return 2;
88-
}
89-
};
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 };
9089
}
9190

9291
/**
@@ -134,15 +133,11 @@ public interface EnhancedConfiguration extends DisposableBean {
134133
*/
135134
private Enhancer newEnhancer(Class<?> superclass) {
136135
Enhancer enhancer = new Enhancer();
137-
// Because callbackFilter and callbackTypes are dynamically populated
138-
// there's no opportunity for caching. This does not appear to be causing
139-
// any performance problem.
140-
enhancer.setUseCache(false);
141136
enhancer.setSuperclass(superclass);
142137
enhancer.setInterfaces(new Class[] {EnhancedConfiguration.class});
143138
enhancer.setUseFactory(false);
144-
enhancer.setCallbackFilter(this.callbackFilter);
145-
enhancer.setCallbackTypes(this.callbackTypes.toArray(new Class[this.callbackTypes.size()]));
139+
enhancer.setCallbackFilter(CALLBACK_FILTER);
140+
enhancer.setCallbackTypes(CALLBACK_TYPES);
146141
return enhancer;
147142
}
148143

@@ -153,7 +148,7 @@ private Enhancer newEnhancer(Class<?> superclass) {
153148
private Class<?> createClass(Enhancer enhancer) {
154149
Class<?> subclass = enhancer.createClass();
155150
// registering callbacks statically (as opposed to threadlocal) is critical for usage in an OSGi env (SPR-5932)
156-
Enhancer.registerStaticCallbacks(subclass, this.callbackInstances.toArray(new Callback[this.callbackInstances.size()]));
151+
Enhancer.registerStaticCallbacks(subclass, this.callbackInstances);
157152
return subclass;
158153
}
159154

@@ -165,7 +160,7 @@ private Class<?> createClass(Enhancer enhancer) {
165160
* @see BeanMethodInterceptor#enhanceFactoryBean(Class, String)
166161
*/
167162
private static class GetObjectMethodInterceptor implements MethodInterceptor {
168-
163+
169164
private final ConfigurableBeanFactory beanFactory;
170165
private final String beanName;
171166

@@ -177,7 +172,7 @@ public GetObjectMethodInterceptor(ConfigurableBeanFactory beanFactory, String be
177172
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
178173
return beanFactory.getBean(beanName);
179174
}
180-
175+
181176
}
182177

183178

@@ -215,8 +210,19 @@ public static boolean isDestroyMethod(Method candidateMethod) {
215210
*/
216211
private static class BeanMethodInterceptor implements MethodInterceptor {
217212

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+
218223
private final ConfigurableBeanFactory beanFactory;
219224

225+
220226
public BeanMethodInterceptor(ConfigurableBeanFactory beanFactory) {
221227
this.beanFactory = beanFactory;
222228
}
@@ -245,7 +251,7 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object
245251

246252
// to handle the case of an inter-bean method reference, we must explicitly check the
247253
// container for already cached instances
248-
254+
249255
// first, check to see if the requested bean is a FactoryBean. If so, create a subclass
250256
// proxy that intercepts calls to getObject() and returns any cached bean instance.
251257
// this ensures that the semantics of calling a FactoryBean from within @Bean methods
@@ -327,26 +333,18 @@ private boolean factoryContainsBean(String beanName) {
327333
*/
328334
private Object enhanceFactoryBean(Class<?> fbClass, String beanName) throws InstantiationException, IllegalAccessException {
329335
Enhancer enhancer = new Enhancer();
330-
enhancer.setUseCache(false);
331336
enhancer.setSuperclass(fbClass);
332337
enhancer.setUseFactory(false);
333-
enhancer.setCallbackFilter(new CallbackFilter() {
334-
public int accept(Method method) {
335-
return method.getName().equals("getObject") ? 0 : 1;
336-
}
337-
});
338-
List<Callback> callbackInstances = new ArrayList<Callback>();
339-
callbackInstances.add(new GetObjectMethodInterceptor(this.beanFactory, beanName));
340-
callbackInstances.add(NoOp.INSTANCE);
341-
342-
List<Class<? extends Callback>> callbackTypes = new ArrayList<Class<? extends Callback>>();
343-
for (Callback callback : callbackInstances) {
344-
callbackTypes.add(callback.getClass());
345-
}
346-
347-
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);
348346
Class<?> fbSubclass = enhancer.createClass();
349-
Enhancer.registerCallbacks(fbSubclass, callbackInstances.toArray(new Callback[callbackInstances.size()]));
347+
Enhancer.registerCallbacks(fbSubclass, callbackInstances);
350348
return fbSubclass.newInstance();
351349
}
352350

0 commit comments

Comments
 (0)