Description
Peter Ertl opened SPR-9478 and commented
Background
In the TestContext framework, the DependencyInjectionTestExecutionListener
(DITEL) is primarily responsible for performing dependency injection into the current test instance, but DITEL also initializes the test instance using AutowireCapableBeanFactory.initializeBean()
.
Javadoc for initializeBean()
:
Initialize the given raw bean, applying factory callbacks such as
setBeanName
andsetBeanFactory
, also applying all bean post processors (including ones which might wrap the given raw bean).Note that no bean definition of the given name has to exist in the bean factory. The passed-in bean name will simply be used for callbacks but not checked against the registered bean definitions.
The fact that all registered bean post processors are applied to the test instance as if it were a bean in the ApplicationContext leads to unexpected side effects. For example, a CGLIB enhanced version of the test class may be created in order to proxy a transactional test class that does not implement any interfaces. The generated proxy is never actually used by the TestContext framework, and as such its creation is unnecessary and in fact unintentional.
Case Study Involving Transactional Tests and CGLIB
With a test class like the following:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( ... )
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
@Transactional
public class TransactionalSpringTest {
// test methods
}
... Spring proxies the test class since it discovers the @Transactional
annotation. This is inappropriate since @Transactional
is already handled by TransactionalTestExecutionListener
via reflection. For every test method execution DependencyInjectionTestExecutionListener
indirectly uses CGLIB to enhance the test class. This results in a potentially large number of instrumented CGLIB classes -- the total = number of transactional tests methods per test * number of tests -- each holding a reference to the data inside the test class which are not released but bound to the current class loader.
In our current project we have several classes (around 50-100) marked transactional which in our case results in a huge memory leak (1GB and more). We worked around this issue by letting the test case implement org.springframework.aop.Advisor
, which is very dirty but blocks Spring from instrumenting the test class and works fine.
Side Note regarding Dynamic Proxies
Note, however, that if the test class implements any interface then a JDK dynamic proxy will be used instead of CGLIB enhancement. This may occur, for example, if the test class implements ApplicationContextAware
like AbstractTransactionalJUnit4SpringContextTests
and AbstractTransactionalTestNGSpringContextTests
.
Deliverables
- Investigate an alternative for bean initialization of test instances that results in neither CGLIB enhancement of the test instances nor creation of dynamic proxies for test instances.
Affects: 3.0 GA
Issue Links:
- Load dedicated child ApplicationContext for test instance in the TestContext framework [SPR-4632] #9309 Load dedicated child ApplicationContext for test instance in the TestContext framework
- Provide mechanism for disabling automatic annotation-driven autowiring in tests [SPR-6050] #10719 Provide mechanism for disabling automatic annotation-driven autowiring in tests
- Java 10: "Illegal method name" when test functions in Kotlin contain spaces in name [SPR-17137] #21674 Java 10: "Illegal method name" when test functions in Kotlin contain spaces in name
- Ignore container callback and marker interfaces for auto-proxy decisions [SPR-11416] #16043 Ignore container callback and marker interfaces for auto-proxy decisions
3 votes, 4 watchers