Browse Source

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
pull/464/head
Sam Brannen 11 years ago
parent
commit
8028eae786
  1. 179
      spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java
  2. 130
      spring-context/src/test/java/org/springframework/beans/factory/xml/XmlBeanFactoryTests.java

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

@ -22,22 +22,27 @@ import java.lang.reflect.Method; @@ -22,22 +22,27 @@ import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cglib.core.SpringNamingPolicy;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.CallbackFilter;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;
/**
* Default object instantiation strategy for use in BeanFactories.
* Uses CGLIB to generate subclasses dynamically if methods need to be
* overridden by the container, to implement Method Injection.
*
* <p>Uses CGLIB to generate subclasses dynamically if methods need to be
* overridden by the container to implement <em>Method Injection</em>.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Sam Brannen
* @since 1.1
*/
public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy {
@ -50,7 +55,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt @@ -50,7 +55,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
/**
* Index in the CGLIB callback array for a method that should
* be overridden to provide method lookup.
* be overridden to provide <em>method lookup</em>.
*/
private static final int LOOKUP_OVERRIDE = 1;
@ -62,18 +67,17 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt @@ -62,18 +67,17 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
@Override
protected Object instantiateWithMethodInjection(
RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) {
protected Object instantiateWithMethodInjection(RootBeanDefinition beanDefinition, String beanName,
BeanFactory owner) {
// Must generate CGLIB subclass.
return new CglibSubclassCreator(beanDefinition, owner).instantiate(null, null);
return instantiateWithMethodInjection(beanDefinition, beanName, owner, null, null);
}
@Override
protected Object instantiateWithMethodInjection(
RootBeanDefinition beanDefinition, String beanName, BeanFactory owner,
Constructor<?> ctor, Object[] args) {
protected Object instantiateWithMethodInjection(RootBeanDefinition beanDefinition, String beanName,
BeanFactory owner, Constructor<?> ctor, Object[] args) {
// Must generate CGLIB subclass.
return new CglibSubclassCreator(beanDefinition, owner).instantiate(ctor, args);
}
@ -84,13 +88,15 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt @@ -84,13 +88,15 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
*/
private static class CglibSubclassCreator {
private static final Log logger = LogFactory.getLog(CglibSubclassCreator.class);
private static final Class<?>[] CALLBACK_TYPES = new Class<?>[] { NoOp.class,
LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class };
private final RootBeanDefinition beanDefinition;
private final BeanFactory owner;
public CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) {
CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) {
this.beanDefinition = beanDefinition;
this.owner = owner;
}
@ -101,30 +107,64 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt @@ -101,30 +107,64 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
* @param ctor constructor to use. If this is {@code null}, use the
* no-arg constructor (no parameterization, or Setter Injection)
* @param args arguments to use for the constructor.
* Ignored if the ctor parameter is {@code null}.
* Ignored if the {@code ctor} parameter is {@code null}.
* @return new instance of the dynamically generated subclass
*/
public Object instantiate(Constructor<?> ctor, Object[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.beanDefinition.getBeanClass());
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setCallbackFilter(new CallbackFilterImpl());
enhancer.setCallbacks(new Callback[] {
NoOp.INSTANCE,
new LookupOverrideMethodInterceptor(),
new ReplaceOverrideMethodInterceptor()
});
Object instantiate(Constructor<?> ctor, Object[] args) {
Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
return (ctor != null ? enhancer.create(ctor.getParameterTypes(), args) : enhancer.create());
Object instance;
if (ctor == null) {
instance = BeanUtils.instantiate(subclass);
}
else {
try {
Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes());
instance = enhancedSubclassConstructor.newInstance(args);
}
catch (Exception e) {
throw new BeanInstantiationException(this.beanDefinition.getBeanClass(), String.format(
"Failed to invoke construcor for CGLIB enhanced subclass [%s]", subclass.getName()), e);
}
}
// SPR-10785: set callbacks directly on the instance instead of in the
// enhanced class (via the Enhancer) in order to avoid memory leaks.
Factory factory = (Factory) instance;
factory.setCallbacks(new Callback[] { NoOp.INSTANCE,//
new LookupOverrideMethodInterceptor(beanDefinition, owner),//
new ReplaceOverrideMethodInterceptor(beanDefinition, owner) });
return instance;
}
/**
* Create an enhanced subclass of the bean class for the provided bean
* definition, using CGLIB.
*/
private Class<?> createEnhancedSubclass(RootBeanDefinition beanDefinition) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(beanDefinition.getBeanClass());
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setCallbackFilter(new CallbackFilterImpl(beanDefinition));
enhancer.setCallbackTypes(CALLBACK_TYPES);
return enhancer.createClass();
}
}
/**
* Class providing hashCode and equals methods required by CGLIB to
* ensure that CGLIB doesn't generate a distinct class per bean.
* Identity is based on class and bean definition.
*/
private class CglibIdentitySupport {
private static class CglibIdentitySupport {
private final RootBeanDefinition beanDefinition;
CglibIdentitySupport(RootBeanDefinition beanDefinition) {
this.beanDefinition = beanDefinition;
}
/**
* Exposed for equals method to allow access to enclosing class field
@ -135,8 +175,8 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt @@ -135,8 +175,8 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
@Override
public boolean equals(Object other) {
return (other.getClass().equals(getClass()) &&
((CglibIdentitySupport) other).getBeanDefinition().equals(beanDefinition));
return (other.getClass().equals(getClass()) && ((CglibIdentitySupport) other).getBeanDefinition().equals(
beanDefinition));
}
@Override
@ -145,46 +185,21 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt @@ -145,46 +185,21 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
}
}
/**
* CGLIB MethodInterceptor to override methods, replacing them with an
* implementation that returns a bean looked up in the container.
* CGLIB object to filter method interception behavior.
*/
private class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
// Cast is safe, as CallbackFilter filters are used selectively.
LookupOverride lo = (LookupOverride) beanDefinition.getMethodOverrides().getOverride(method);
return owner.getBean(lo.getBeanName());
}
}
private static class CallbackFilterImpl extends CglibIdentitySupport implements CallbackFilter {
private static final Log logger = LogFactory.getLog(CallbackFilterImpl.class);
/**
* CGLIB MethodInterceptor to override methods, replacing them with a call
* to a generic MethodReplacer.
*/
private class ReplaceOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
ReplaceOverride ro = (ReplaceOverride) beanDefinition.getMethodOverrides().getOverride(method);
// TODO could cache if a singleton for minor performance optimization
MethodReplacer mr = (MethodReplacer) owner.getBean(ro.getMethodReplacerBeanName());
return mr.reimplement(obj, method, args);
}
CallbackFilterImpl(RootBeanDefinition beanDefinition) {
super(beanDefinition);
}
/**
* CGLIB object to filter method interception behavior.
*/
private class CallbackFilterImpl extends CglibIdentitySupport implements CallbackFilter {
@Override
public int accept(Method method) {
MethodOverride methodOverride = beanDefinition.getMethodOverrides().getOverride(method);
MethodOverride methodOverride = getBeanDefinition().getMethodOverrides().getOverride(method);
if (logger.isTraceEnabled()) {
logger.trace("Override for '" + method.getName() + "' is [" + methodOverride + "]");
}
@ -197,9 +212,53 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt @@ -197,9 +212,53 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
else if (methodOverride instanceof ReplaceOverride) {
return METHOD_REPLACER;
}
throw new UnsupportedOperationException(
"Unexpected MethodOverride subclass: " + methodOverride.getClass().getName());
throw new UnsupportedOperationException("Unexpected MethodOverride subclass: "
+ methodOverride.getClass().getName());
}
}
/**
* CGLIB MethodInterceptor to override methods, replacing them with an
* implementation that returns a bean looked up in the container.
*/
private static class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
private final BeanFactory owner;
LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
super(beanDefinition);
this.owner = owner;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
// Cast is safe, as CallbackFilter filters are used selectively.
LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
return this.owner.getBean(lo.getBeanName());
}
}
/**
* CGLIB MethodInterceptor to override methods, replacing them with a call
* to a generic MethodReplacer.
*/
private static class ReplaceOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
private final BeanFactory owner;
ReplaceOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
super(beanDefinition);
this.owner = owner;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
ReplaceOverride ro = (ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
// TODO could cache if a singleton for minor performance optimization
MethodReplacer mr = owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class);
return mr.reimplement(obj, method, args);
}
}

130
spring-context/src/test/java/org/springframework/beans/factory/xml/XmlBeanFactoryTests.java

@ -65,7 +65,6 @@ import org.springframework.util.ClassUtils; @@ -65,7 +65,6 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.SerializationTestUtils;
import org.springframework.util.StopWatch;
import org.xml.sax.InputSource;
import static org.hamcrest.CoreMatchers.*;
@ -390,11 +389,11 @@ public final class XmlBeanFactoryTests { @@ -390,11 +389,11 @@ public final class XmlBeanFactoryTests {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(xbf);
reader.loadBeanDefinitions(OVERRIDES_CONTEXT);
TestBean david = (TestBean)xbf.getBean("magicDavid");
TestBean david = (TestBean) xbf.getBean("magicDavid");
// the parent bean is autowiring
assertNotNull(david.getSpouse());
TestBean derivedDavid = (TestBean)xbf.getBean("magicDavidDerived");
TestBean derivedDavid = (TestBean) xbf.getBean("magicDavidDerived");
// this fails while it inherits from the child bean
assertNull("autowiring not propagated along child relationships", derivedDavid.getSpouse());
}
@ -492,7 +491,7 @@ public final class XmlBeanFactoryTests { @@ -492,7 +491,7 @@ public final class XmlBeanFactoryTests {
DefaultListableBeanFactory child = new DefaultListableBeanFactory(parent);
new XmlBeanDefinitionReader(child).loadBeanDefinitions(CHILD_CONTEXT);
TestBean inherits = (TestBean) child.getBean("singletonInheritsFromParentFactoryPrototype");
// Name property value is overriden
// Name property value is overridden
assertTrue(inherits.getName().equals("prototype-override"));
// Age property is inherited from bean in parent factory
assertTrue(inherits.getAge() == 2);
@ -643,17 +642,11 @@ public final class XmlBeanFactoryTests { @@ -643,17 +642,11 @@ public final class XmlBeanFactoryTests {
assertEquals(5, xbf.getSingletonCount());
}
@Test
public void testNoSuchFactoryBeanMethod() {
try {
@Test(expected = BeanCreationException.class)
public void noSuchFactoryBeanMethod() {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(NO_SUCH_FACTORY_METHOD_CONTEXT);
assertNotNull(xbf.getBean("defaultTestBean"));
fail("Should not get invalid bean");
}
catch (BeanCreationException ex) {
// Ok
}
}
@Test
@ -757,50 +750,30 @@ public final class XmlBeanFactoryTests { @@ -757,50 +750,30 @@ public final class XmlBeanFactoryTests {
}
}
@Test
public void testNoSuchXmlFile() throws Exception {
@Test(expected = BeanDefinitionStoreException.class)
public void noSuchXmlFile() throws Exception {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
try {
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(MISSING_CONTEXT);
fail("Must not create factory from missing XML");
}
catch (BeanDefinitionStoreException expected) {
}
}
@Test
public void testInvalidXmlFile() throws Exception {
@Test(expected = BeanDefinitionStoreException.class)
public void invalidXmlFile() throws Exception {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
try {
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(INVALID_CONTEXT);
fail("Must not create factory from invalid XML");
}
catch (BeanDefinitionStoreException expected) {
}
}
@Test
public void testUnsatisfiedObjectDependencyCheck() throws Exception {
@Test(expected = UnsatisfiedDependencyException.class)
public void unsatisfiedObjectDependencyCheck() throws Exception {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
try {
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(UNSATISFIED_OBJECT_DEP_CONTEXT);
xbf.getBean("a", DependenciesBean.class);
fail("Must have thrown an UnsatisfiedDependencyException");
}
catch (UnsatisfiedDependencyException ex) {
}
}
@Test
public void testUnsatisfiedSimpleDependencyCheck() throws Exception {
@Test(expected = UnsatisfiedDependencyException.class)
public void unsatisfiedSimpleDependencyCheck() throws Exception {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
try {
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(UNSATISFIED_SIMPLE_DEP_CONTEXT);
xbf.getBean("a", DependenciesBean.class);
fail("Must have thrown an UnsatisfiedDependencyException");
}
catch (UnsatisfiedDependencyException expected) {
}
}
@Test
@ -820,16 +793,11 @@ public final class XmlBeanFactoryTests { @@ -820,16 +793,11 @@ public final class XmlBeanFactoryTests {
assertEquals(a.getAge(), 33);
}
@Test
public void testUnsatisfiedAllDependencyCheck() throws Exception {
@Test(expected = UnsatisfiedDependencyException.class)
public void unsatisfiedAllDependencyCheck() throws Exception {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
try {
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(UNSATISFIED_ALL_DEP_CONTEXT);
xbf.getBean("a", DependenciesBean.class);
fail("Must have thrown an UnsatisfiedDependencyException");
}
catch (UnsatisfiedDependencyException expected) {
}
}
@Test
@ -1092,28 +1060,18 @@ public final class XmlBeanFactoryTests { @@ -1092,28 +1060,18 @@ public final class XmlBeanFactoryTests {
assertEquals(File.separator + "test", file.getPath());
}
@Test
public void testThrowsExceptionOnTooManyArguments() throws Exception {
@Test(expected = BeanCreationException.class)
public void throwsExceptionOnTooManyArguments() throws Exception {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(CONSTRUCTOR_ARG_CONTEXT);
try {
xbf.getBean("rod7", ConstructorDependenciesBean.class);
fail("Should have thrown BeanCreationException");
}
catch (BeanCreationException expected) {
}
}
@Test
public void testThrowsExceptionOnAmbiguousResolution() throws Exception {
@Test(expected = UnsatisfiedDependencyException.class)
public void throwsExceptionOnAmbiguousResolution() throws Exception {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(CONSTRUCTOR_ARG_CONTEXT);
try {
xbf.getBean("rod8", ConstructorDependenciesBean.class);
fail("Must have thrown UnsatisfiedDependencyException");
}
catch (UnsatisfiedDependencyException expected) {
}
}
@Test
@ -1274,17 +1232,10 @@ public final class XmlBeanFactoryTests { @@ -1274,17 +1232,10 @@ public final class XmlBeanFactoryTests {
xbf.getBean("resource2", ResourceTestBean.class);
}
@Test
public void testRecursiveImport() {
@Test(expected = BeanDefinitionStoreException.class)
public void recursiveImport() {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
try {
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(RECURSIVE_IMPORT_CONTEXT);
fail("Should have thrown BeanDefinitionStoreException");
}
catch (BeanDefinitionStoreException ex) {
// expected
ex.printStackTrace();
}
}
/**
@ -1296,7 +1247,7 @@ public final class XmlBeanFactoryTests { @@ -1296,7 +1247,7 @@ public final class XmlBeanFactoryTests {
public void methodInjectedBeanMustBeOfSameEnhancedCglibSubclassTypeAcrossBeanFactories() {
Class<?> firstClass = null;
for (int i = 1; i <= 10; i++) {
for (int i = 0; i < 10; i++) {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(OVERRIDES_CONTEXT);
@ -1314,15 +1265,15 @@ public final class XmlBeanFactoryTests { @@ -1314,15 +1265,15 @@ public final class XmlBeanFactoryTests {
}
@Test
public void testLookupOverrideMethodsWithSetterInjection() {
public void lookupOverrideMethodsWithSetterInjection() {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(xbf);
reader.loadBeanDefinitions(OVERRIDES_CONTEXT);
testLookupOverrideMethodsWithSetterInjection(xbf, "overrideOneMethod", true);
lookupOverrideMethodsWithSetterInjection(xbf, "overrideOneMethod", true);
// Should work identically on subclass definition, in which lookup
// methods are inherited
testLookupOverrideMethodsWithSetterInjection(xbf, "overrideInheritedMethod", true);
lookupOverrideMethodsWithSetterInjection(xbf, "overrideInheritedMethod", true);
// Check cost of repeated construction of beans with method overrides
// Will pick up misuse of CGLIB
@ -1330,10 +1281,10 @@ public final class XmlBeanFactoryTests { @@ -1330,10 +1281,10 @@ public final class XmlBeanFactoryTests {
StopWatch sw = new StopWatch();
sw.start("Look up " + howMany + " prototype bean instances with method overrides");
for (int i = 0; i < howMany; i++) {
testLookupOverrideMethodsWithSetterInjection(xbf, "overrideOnPrototype", false);
lookupOverrideMethodsWithSetterInjection(xbf, "overrideOnPrototype", false);
}
sw.stop();
System.out.println(sw);
// System.out.println(sw);
if (!LogFactory.getLog(DefaultListableBeanFactory.class).isDebugEnabled()) {
assertTrue(sw.getTotalTimeMillis() < 2000);
}
@ -1347,7 +1298,8 @@ public final class XmlBeanFactoryTests { @@ -1347,7 +1298,8 @@ public final class XmlBeanFactoryTests {
assertEquals("Jenny", tb.getName());
}
private void testLookupOverrideMethodsWithSetterInjection(BeanFactory xbf, String beanName, boolean singleton) {
private void lookupOverrideMethodsWithSetterInjection(BeanFactory xbf,
String beanName, boolean singleton) {
OverrideOneMethod oom = (OverrideOneMethod) xbf.getBean(beanName);
if (singleton) {
@ -1428,7 +1380,7 @@ public final class XmlBeanFactoryTests { @@ -1428,7 +1380,7 @@ public final class XmlBeanFactoryTests {
}
@Test
public void testLookupOverrideOneMethodWithConstructorInjection() {
public void lookupOverrideOneMethodWithConstructorInjection() {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(xbf);
reader.loadBeanDefinitions(CONSTRUCTOR_OVERRIDES_CONTEXT);
@ -1466,21 +1418,8 @@ public final class XmlBeanFactoryTests { @@ -1466,21 +1418,8 @@ public final class XmlBeanFactoryTests {
}
}
/**
* Assert the presence of this bug until we resolve it.
*/
@Test
public void testSerializabilityOfMethodReplacer() throws Exception {
try {
BUGtestSerializableMethodReplacerAndSuperclass();
fail();
}
catch (AssertionError ex) {
System.err.println("****** SPR-356: Objects with MethodReplace overrides are not serializable");
}
}
public void BUGtestSerializableMethodReplacerAndSuperclass() throws IOException, ClassNotFoundException {
public void serializableMethodReplacerAndSuperclass() throws Exception {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(xbf);
reader.loadBeanDefinitions(DELEGATION_OVERRIDES_CONTEXT);
@ -1488,9 +1427,10 @@ public final class XmlBeanFactoryTests { @@ -1488,9 +1427,10 @@ public final class XmlBeanFactoryTests {
String forwards = "this is forwards";
String backwards = new StringBuffer(forwards).reverse().toString();
assertEquals(backwards, s.replaceMe(forwards));
assertTrue(SerializationTestUtils.isSerializable(s));
s = (SerializableMethodReplacerCandidate) SerializationTestUtils.serializeAndDeserialize(s);
assertEquals("Method replace still works after serialization and deserialization", backwards, s.replaceMe(forwards));
// SPR-356: lookup methods & method replacers are not serializable.
assertFalse(
"Lookup methods and method replacers are not meant to be serializable.",
SerializationTestUtils.isSerializable(s));
}
@Test

Loading…
Cancel
Save