Skip to content

Commit 8028eae

Browse files
committed
Fix CGLIB memory leak for method injection
This commit continues the work for fixing memory leaks resulting from CGLIB subclass generation for beans relying on method injection. - Set proxy callbacks on the CGLIB Factory (i.e., the instance) instead of in the generated subclass (i.e., via the Enhancer). - Convert private inner classes in CglibSubclassingInstantiationStrategy to private static classes in order to avoid unnecessary coupling to classes generated using CGLIB. - Tidy up XmlBeanFactoryTests. - Update logic in serializableMethodReplacerAndSuperclass() so that it finally aligns with the decision made for SPR-356. Issue: SPR-10785, SPR-356
1 parent bda8f2b commit 8028eae

File tree

2 files changed

+196
-197
lines changed

2 files changed

+196
-197
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java

Lines changed: 147 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,27 @@
2222
import org.apache.commons.logging.Log;
2323
import org.apache.commons.logging.LogFactory;
2424

25+
import org.springframework.beans.BeanInstantiationException;
26+
import org.springframework.beans.BeanUtils;
2527
import org.springframework.beans.factory.BeanFactory;
2628
import org.springframework.cglib.core.SpringNamingPolicy;
2729
import org.springframework.cglib.proxy.Callback;
2830
import org.springframework.cglib.proxy.CallbackFilter;
2931
import org.springframework.cglib.proxy.Enhancer;
32+
import org.springframework.cglib.proxy.Factory;
3033
import org.springframework.cglib.proxy.MethodInterceptor;
3134
import org.springframework.cglib.proxy.MethodProxy;
3235
import org.springframework.cglib.proxy.NoOp;
3336

3437
/**
3538
* Default object instantiation strategy for use in BeanFactories.
36-
* Uses CGLIB to generate subclasses dynamically if methods need to be
37-
* overridden by the container, to implement Method Injection.
39+
*
40+
* <p>Uses CGLIB to generate subclasses dynamically if methods need to be
41+
* overridden by the container to implement <em>Method Injection</em>.
3842
*
3943
* @author Rod Johnson
4044
* @author Juergen Hoeller
45+
* @author Sam Brannen
4146
* @since 1.1
4247
*/
4348
public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy {
@@ -50,7 +55,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
5055

5156
/**
5257
* Index in the CGLIB callback array for a method that should
53-
* be overridden to provide method lookup.
58+
* be overridden to provide <em>method lookup</em>.
5459
*/
5560
private static final int LOOKUP_OVERRIDE = 1;
5661

@@ -62,18 +67,17 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
6267

6368

6469
@Override
65-
protected Object instantiateWithMethodInjection(
66-
RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) {
70+
protected Object instantiateWithMethodInjection(RootBeanDefinition beanDefinition, String beanName,
71+
BeanFactory owner) {
6772

68-
// Must generate CGLIB subclass.
69-
return new CglibSubclassCreator(beanDefinition, owner).instantiate(null, null);
73+
return instantiateWithMethodInjection(beanDefinition, beanName, owner, null, null);
7074
}
7175

7276
@Override
73-
protected Object instantiateWithMethodInjection(
74-
RootBeanDefinition beanDefinition, String beanName, BeanFactory owner,
75-
Constructor<?> ctor, Object[] args) {
77+
protected Object instantiateWithMethodInjection(RootBeanDefinition beanDefinition, String beanName,
78+
BeanFactory owner, Constructor<?> ctor, Object[] args) {
7679

80+
// Must generate CGLIB subclass.
7781
return new CglibSubclassCreator(beanDefinition, owner).instantiate(ctor, args);
7882
}
7983

@@ -84,13 +88,15 @@ protected Object instantiateWithMethodInjection(
8488
*/
8589
private static class CglibSubclassCreator {
8690

87-
private static final Log logger = LogFactory.getLog(CglibSubclassCreator.class);
91+
private static final Class<?>[] CALLBACK_TYPES = new Class<?>[] { NoOp.class,
92+
LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class };
8893

8994
private final RootBeanDefinition beanDefinition;
9095

9196
private final BeanFactory owner;
9297

93-
public CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) {
98+
99+
CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) {
94100
this.beanDefinition = beanDefinition;
95101
this.owner = owner;
96102
}
@@ -101,105 +107,158 @@ public CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner
101107
* @param ctor constructor to use. If this is {@code null}, use the
102108
* no-arg constructor (no parameterization, or Setter Injection)
103109
* @param args arguments to use for the constructor.
104-
* Ignored if the ctor parameter is {@code null}.
110+
* Ignored if the {@code ctor} parameter is {@code null}.
105111
* @return new instance of the dynamically generated subclass
106112
*/
107-
public Object instantiate(Constructor<?> ctor, Object[] args) {
113+
Object instantiate(Constructor<?> ctor, Object[] args) {
114+
Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
115+
116+
Object instance;
117+
if (ctor == null) {
118+
instance = BeanUtils.instantiate(subclass);
119+
}
120+
else {
121+
try {
122+
Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes());
123+
instance = enhancedSubclassConstructor.newInstance(args);
124+
}
125+
catch (Exception e) {
126+
throw new BeanInstantiationException(this.beanDefinition.getBeanClass(), String.format(
127+
"Failed to invoke construcor for CGLIB enhanced subclass [%s]", subclass.getName()), e);
128+
}
129+
}
130+
131+
// SPR-10785: set callbacks directly on the instance instead of in the
132+
// enhanced class (via the Enhancer) in order to avoid memory leaks.
133+
Factory factory = (Factory) instance;
134+
factory.setCallbacks(new Callback[] { NoOp.INSTANCE,//
135+
new LookupOverrideMethodInterceptor(beanDefinition, owner),//
136+
new ReplaceOverrideMethodInterceptor(beanDefinition, owner) });
137+
138+
return instance;
139+
}
140+
141+
/**
142+
* Create an enhanced subclass of the bean class for the provided bean
143+
* definition, using CGLIB.
144+
*/
145+
private Class<?> createEnhancedSubclass(RootBeanDefinition beanDefinition) {
108146
Enhancer enhancer = new Enhancer();
109-
enhancer.setSuperclass(this.beanDefinition.getBeanClass());
147+
enhancer.setSuperclass(beanDefinition.getBeanClass());
110148
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
111-
enhancer.setCallbackFilter(new CallbackFilterImpl());
112-
enhancer.setCallbacks(new Callback[] {
113-
NoOp.INSTANCE,
114-
new LookupOverrideMethodInterceptor(),
115-
new ReplaceOverrideMethodInterceptor()
116-
});
117-
118-
return (ctor != null ? enhancer.create(ctor.getParameterTypes(), args) : enhancer.create());
149+
enhancer.setCallbackFilter(new CallbackFilterImpl(beanDefinition));
150+
enhancer.setCallbackTypes(CALLBACK_TYPES);
151+
return enhancer.createClass();
119152
}
153+
}
154+
155+
/**
156+
* Class providing hashCode and equals methods required by CGLIB to
157+
* ensure that CGLIB doesn't generate a distinct class per bean.
158+
* Identity is based on class and bean definition.
159+
*/
160+
private static class CglibIdentitySupport {
120161

162+
private final RootBeanDefinition beanDefinition;
163+
164+
165+
CglibIdentitySupport(RootBeanDefinition beanDefinition) {
166+
this.beanDefinition = beanDefinition;
167+
}
121168

122169
/**
123-
* Class providing hashCode and equals methods required by CGLIB to
124-
* ensure that CGLIB doesn't generate a distinct class per bean.
125-
* Identity is based on class and bean definition.
170+
* Exposed for equals method to allow access to enclosing class field
126171
*/
127-
private class CglibIdentitySupport {
128-
129-
/**
130-
* Exposed for equals method to allow access to enclosing class field
131-
*/
132-
protected RootBeanDefinition getBeanDefinition() {
133-
return beanDefinition;
134-
}
172+
protected RootBeanDefinition getBeanDefinition() {
173+
return beanDefinition;
174+
}
135175

136-
@Override
137-
public boolean equals(Object other) {
138-
return (other.getClass().equals(getClass()) &&
139-
((CglibIdentitySupport) other).getBeanDefinition().equals(beanDefinition));
140-
}
176+
@Override
177+
public boolean equals(Object other) {
178+
return (other.getClass().equals(getClass()) && ((CglibIdentitySupport) other).getBeanDefinition().equals(
179+
beanDefinition));
180+
}
141181

142-
@Override
143-
public int hashCode() {
144-
return beanDefinition.hashCode();
145-
}
182+
@Override
183+
public int hashCode() {
184+
return beanDefinition.hashCode();
146185
}
186+
}
147187

188+
/**
189+
* CGLIB object to filter method interception behavior.
190+
*/
191+
private static class CallbackFilterImpl extends CglibIdentitySupport implements CallbackFilter {
192+
193+
private static final Log logger = LogFactory.getLog(CallbackFilterImpl.class);
148194

149-
/**
150-
* CGLIB MethodInterceptor to override methods, replacing them with an
151-
* implementation that returns a bean looked up in the container.
152-
*/
153-
private class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
154195

155-
@Override
156-
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
157-
// Cast is safe, as CallbackFilter filters are used selectively.
158-
LookupOverride lo = (LookupOverride) beanDefinition.getMethodOverrides().getOverride(method);
159-
return owner.getBean(lo.getBeanName());
196+
CallbackFilterImpl(RootBeanDefinition beanDefinition) {
197+
super(beanDefinition);
198+
}
199+
200+
@Override
201+
public int accept(Method method) {
202+
MethodOverride methodOverride = getBeanDefinition().getMethodOverrides().getOverride(method);
203+
if (logger.isTraceEnabled()) {
204+
logger.trace("Override for '" + method.getName() + "' is [" + methodOverride + "]");
205+
}
206+
if (methodOverride == null) {
207+
return PASSTHROUGH;
160208
}
209+
else if (methodOverride instanceof LookupOverride) {
210+
return LOOKUP_OVERRIDE;
211+
}
212+
else if (methodOverride instanceof ReplaceOverride) {
213+
return METHOD_REPLACER;
214+
}
215+
throw new UnsupportedOperationException("Unexpected MethodOverride subclass: "
216+
+ methodOverride.getClass().getName());
161217
}
218+
}
162219

220+
/**
221+
* CGLIB MethodInterceptor to override methods, replacing them with an
222+
* implementation that returns a bean looked up in the container.
223+
*/
224+
private static class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
163225

164-
/**
165-
* CGLIB MethodInterceptor to override methods, replacing them with a call
166-
* to a generic MethodReplacer.
167-
*/
168-
private class ReplaceOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
169-
170-
@Override
171-
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
172-
ReplaceOverride ro = (ReplaceOverride) beanDefinition.getMethodOverrides().getOverride(method);
173-
// TODO could cache if a singleton for minor performance optimization
174-
MethodReplacer mr = (MethodReplacer) owner.getBean(ro.getMethodReplacerBeanName());
175-
return mr.reimplement(obj, method, args);
176-
}
226+
private final BeanFactory owner;
227+
228+
229+
LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
230+
super(beanDefinition);
231+
this.owner = owner;
177232
}
178233

234+
@Override
235+
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
236+
// Cast is safe, as CallbackFilter filters are used selectively.
237+
LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
238+
return this.owner.getBean(lo.getBeanName());
239+
}
240+
}
179241

180-
/**
181-
* CGLIB object to filter method interception behavior.
182-
*/
183-
private class CallbackFilterImpl extends CglibIdentitySupport implements CallbackFilter {
242+
/**
243+
* CGLIB MethodInterceptor to override methods, replacing them with a call
244+
* to a generic MethodReplacer.
245+
*/
246+
private static class ReplaceOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
184247

185-
@Override
186-
public int accept(Method method) {
187-
MethodOverride methodOverride = beanDefinition.getMethodOverrides().getOverride(method);
188-
if (logger.isTraceEnabled()) {
189-
logger.trace("Override for '" + method.getName() + "' is [" + methodOverride + "]");
190-
}
191-
if (methodOverride == null) {
192-
return PASSTHROUGH;
193-
}
194-
else if (methodOverride instanceof LookupOverride) {
195-
return LOOKUP_OVERRIDE;
196-
}
197-
else if (methodOverride instanceof ReplaceOverride) {
198-
return METHOD_REPLACER;
199-
}
200-
throw new UnsupportedOperationException(
201-
"Unexpected MethodOverride subclass: " + methodOverride.getClass().getName());
202-
}
248+
private final BeanFactory owner;
249+
250+
251+
ReplaceOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
252+
super(beanDefinition);
253+
this.owner = owner;
254+
}
255+
256+
@Override
257+
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
258+
ReplaceOverride ro = (ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
259+
// TODO could cache if a singleton for minor performance optimization
260+
MethodReplacer mr = owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class);
261+
return mr.reimplement(obj, method, args);
203262
}
204263
}
205264

0 commit comments

Comments
 (0)