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
This commit is contained in:
Sam Brannen
2014-02-12 02:03:24 +01:00
parent bda8f2b4cf
commit 8028eae786
2 changed files with 201 additions and 202 deletions

View File

@@ -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 {
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 {
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 {
assertEquals(5, xbf.getSingletonCount());
}
@Test
public void testNoSuchFactoryBeanMethod() {
try {
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(expected = BeanCreationException.class)
public void noSuchFactoryBeanMethod() {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(NO_SUCH_FACTORY_METHOD_CONTEXT);
assertNotNull(xbf.getBean("defaultTestBean"));
}
@Test
@@ -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) {
}
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(MISSING_CONTEXT);
}
@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) {
}
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(INVALID_CONTEXT);
}
@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) {
}
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(UNSATISFIED_OBJECT_DEP_CONTEXT);
xbf.getBean("a", DependenciesBean.class);
}
@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) {
}
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(UNSATISFIED_SIMPLE_DEP_CONTEXT);
xbf.getBean("a", DependenciesBean.class);
}
@Test
@@ -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) {
}
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(UNSATISFIED_ALL_DEP_CONTEXT);
xbf.getBean("a", DependenciesBean.class);
}
@Test
@@ -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) {
}
xbf.getBean("rod7", ConstructorDependenciesBean.class);
}
@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) {
}
xbf.getBean("rod8", ConstructorDependenciesBean.class);
}
@Test
@@ -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();
}
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(RECURSIVE_IMPORT_CONTEXT);
}
/**
@@ -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 {
}
@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 {
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 {
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 {
}
@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 {
}
}
/**
* 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 {
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