Improve Startup of Spring Integration Test
See original GitHub issueIt would be really useful to provide a native mechanism to tell spring boot test to mark all beans in the context as “lazy”. This can substantially speed up running an individual integration test, as you only have to wait for the initialization of beans that have actually been injected into the test. Even if this were an “opt-in” strategy, I think a lot of people would find it useful.
We solved this problem, in our environment, by adding a BeanFactoryPostProcessor that marks all beans within the application context as “lazy”. We add this processor only in our testing context and we have seen anywhere between a 10 and 20 seconds improvement in startup of individual tests. This also improves overall runtime when tests use mock beans or dirty the context, a subsequent test will start more quickly when the new application context is initialized.
This strategy works in a majority of cases except when using one of Spring’s DSLs (like spring integration). The problem with these is that the flows are not directly referenced by any class, but the beans they create ARE. We had two work arounds for this use case: You can @Autowire
the flows within a test to insure your beans are created or you can add a list of class exclusions to the post processor.
I think there might be a more elegant way to do this but this is the post-processor we are using today:
/**
* This post processor will mark all beans in the context as "lazy", meaning they will be loaded "on-demand".
* This can substantially speed up running a single integration test, as it will ONLY initialize the beans references
* by that test. This strategy works in a majority of cases except when using one of Spring's DSLs (like spring integration).
* In those cases, those integration flows indirectly create additional beans that may be needed as dependencies in
* specific tests. This class can be initialized with a set of exclusion classes, such that beans that are are assignable
* to one of the classes in the exclusion list will NOT be lazy initialized.
*
* <pre>
* Example:
*
* {@code
* @Bean
* public BeanFactoryPostProcessor lazyBeanPostProcessor() {
* return new ServiceTestLazyBeanFactoryPostProcessor();
* }
*
* static private class ServiceTestLazyBeanFactoryPostProcessor extends LazyInitBeanFactoryPostProcessor {
* public ServiceTestLazyBeanFactoryPostProcessor() {
* super(new Class<?>[] {IntegrationFlow.class});
* }
* }
*
* </pre>
*/
public class LazyInitBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private Class<?>[] exclusionList;
public LazyInitBeanFactoryPostProcessor() {
}
public LazyInitBeanFactoryPostProcessor(Class<?>[] exclusionList) {
this.exclusionList = exclusionList;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//Iterate over all bean, mark them as lazy if they are not in the exclusion list.
for (String beanName : beanFactory.getBeanDefinitionNames()) {
if (isLazy(beanName, beanFactory)) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
definition.setLazyInit(true);
}
}
}
private boolean isLazy(String beanName, ConfigurableListableBeanFactory beanFactory) {
if (exclusionList == null || exclusionList.length == 0) {
return true;
}
for (Class<?> clazz : exclusionList) {
if (beanFactory.isTypeMatch(beanName,clazz)) {
return false;
}
}
return true;
}
}
Then within our Test:
@SpringBootTest(webEnvironment=WebEnvironment.NONE)
public abstract class BaseServiceTest extends BaseTest {
@TestConfiguration
protected static class ServiceTestConfiguration {
@Bean
public BeanFactoryPostProcessor lazyBeanPostProcessor() {
return new ServiceTestLazyBeanFactoryPostProcessor();
}
static private class ServiceTestLazyBeanFactoryPostProcessor extends LazyInitBeanFactoryPostProcessor {
public ServiceTestLazyBeanFactoryPostProcessor() {
super(new Class<?>[] {IntegrationFlow.class});
}
}
}
Issue Analytics
- State:
- Created 6 years ago
- Reactions:6
- Comments:6 (4 by maintainers)
Makes sense to me, at the very least others can use my example if they run into a similar issue. Thanks for looking into it.
I think this might be worth a second look. It’s a very simple idea, but Bill says it works well for his app. It shaves more than 20% off the startup time of Petclinic even (which is a pretty simple app). The tests in Petclinic use slices though, so you don’t gain much really in practice for that app. But for large, slow apps where integration tests are the norm, it could be quite a big deal.