Deregister @Configuration CGLIB callbacks

CGLIB-enhanced @Configuration subclasses now implement DisposableBean
such that Enhancer.registerStaticCallbacks(subclass, null) is invoked
on container shutdown. This ensures that garbage collection can work
properly and avoids memory consumption issues for applications that
create and destroy many application contexts within the same JVM.

Issue: SPR-7901
This commit is contained in:
Chris Beams
2011-03-14 09:20:19 +00:00
parent 76ce418556
commit 43676bd660
2 changed files with 110 additions and 1 deletions

View File

@@ -31,6 +31,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.scope.ScopedProxyFactoryBean;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
@@ -64,6 +65,7 @@ class ConfigurationClassEnhancer {
Assert.notNull(beanFactory, "BeanFactory must not be null");
this.callbackInstances.add(new BeanMethodInterceptor(beanFactory));
this.callbackInstances.add(new DisposableBeanMethodInterceptor());
this.callbackInstances.add(NoOp.INSTANCE);
for (Callback callback : this.callbackInstances) {
@@ -74,7 +76,13 @@ class ConfigurationClassEnhancer {
// handling a @Bean-annotated method; otherwise, return index of the NoOp callback.
callbackFilter = new CallbackFilter() {
public int accept(Method candidateMethod) {
return (BeanAnnotationHelper.isBeanAnnotated(candidateMethod) ? 0 : 1);
if (BeanAnnotationHelper.isBeanAnnotated(candidateMethod)) {
return 0;
}
if (DisposableBeanMethodInterceptor.isDestroyMethod(candidateMethod)) {
return 1;
}
return 2;
}
};
}
@@ -104,6 +112,7 @@ class ConfigurationClassEnhancer {
// any performance problem.
enhancer.setUseCache(false);
enhancer.setSuperclass(superclass);
enhancer.setInterfaces(new Class[] {DisposableBean.class});
enhancer.setUseFactory(false);
enhancer.setCallbackFilter(this.callbackFilter);
enhancer.setCallbackTypes(this.callbackTypes.toArray(new Class[this.callbackTypes.size()]));
@@ -145,6 +154,29 @@ class ConfigurationClassEnhancer {
}
/**
* Intercepts the invocation of any {@link DisposableBean#destroy()} on @Configuration
* class instances for the purpose of de-registering CGLIB callbacks. This helps avoid
* garbage collection issues See SPR-7901.
*/
private static class DisposableBeanMethodInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Enhancer.registerStaticCallbacks(obj.getClass(), null);
if (DisposableBean.class.isAssignableFrom(obj.getClass().getSuperclass())) {
return proxy.invokeSuper(obj, args);
}
return null;
}
public static boolean isDestroyMethod(Method candidateMethod) {
return candidateMethod.getName().equals("destroy") &&
candidateMethod.getParameterTypes().length == 0 &&
DisposableBean.class.isAssignableFrom(candidateMethod.getDeclaringClass());
}
}
/**
* Intercepts the invocation of any {@link Bean}-annotated methods in order to ensure proper
* handling of bean semantics such as scoping and AOP proxying.