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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user