Enter my site for the best reading experience


During the development process, it is very routine to start the system locally to conduct some tests and joint tuning. However, as the project grows, the system relies on more and more middleware or services, such as Rocket MQ, Dubbo, scheduled tasks, and so on. During system startup, these components need to be initialized, resulting in a slow startup process.

Five minutes for system startup and three seconds for test run. And these components are often not needed when tested locally.

If you’re having the same problem, take advantage of Spring’s extension point, BeanFactoryPostProcessor, to ininvasibly castrate these slower-startup components and get your system off to a one-click start.

For example, Rocket MQ loads five consumers at startup.

public class RocketMQConfiguration {

    /** * SpringBoot loads all consumers */
    @PostConstruct
    public void initConsumer(a) {
        Map<String, AbstractRocketConsumer> consumers = applicationContext.getBeansOfType(AbstractRocketConsumer.class);
        if (CollectionUtils.isEmpty(consumers)) {
            log.info("no consumers");
        }
        for (String beanName : consumers.keySet()) {
            AbstractRocketConsumer consumer = consumers.get(beanName);
            consumer.init();
            createConsumer(consumer);
            log.info("init consumer: title {} , topics {} , tags {}", consumer.consumerTitle, consumer.topics, consumer.tags); }}}Copy the code

When you run the test class, the system starts up in 30 seconds, with the Bean initialization taking 28 seconds.

The method of calculating the Bean initialization time here is to use BeanPostProcessor’s extension point before and after initialization to collect the time points of all beans before and after initialization and calculate the time of the whole process by subtracting the maximum time from the minimum time.

Next we take advantage of the BeanFactoryPostProcessor extension point to shorten the initialization time.

Let’s start by reviewing Spring’s Bean initialization process, which can be roughly divided into parsing configuration, initializing BeanFactory, registering BeanDefinition, registering BeanPostProcessor, and then initializing all singleton beans. Finally, the callback method of BeanPostProcessor is executed.

If you are familiar with Spring’s source code, the logic in the org. Springframework. Context. Support. AbstractApplicationContext# refresh, scrutinize once during this process, Which will execute invokeBeanFactoryPostProcessors (the beanFactory) method, and save all the bean in the beanFactory definition, namely BeanDefinition. Spring then initializes the corresponding beans based on these BeanDefinitions.

So, we can customize a BeanFactoryPostProcessor that silently removes unwanted BeanDefinitions before the bean is initialized. Spring does not initialize these beans. You can also do some specific treatment according to the need and actual situation.

public class SpringTestLaunchControl implements BeanFactoryPostProcessor {

    private static final Set<String> EXCLUDE_BEAN_CLASSES = Sets.newHashSet(
            "co.lilpilot.springtestfield.mq.Consumer1"."co.lilpilot.springtestfield.mq.Consumer2"."co.lilpilot.springtestfield.mq.Consumer3"."co.lilpilot.springtestfield.mq.Consumer4"."co.lilpilot.springtestfield.mq.Consumer5"
            );

    private static final Set<String> ANNOTATION_BEAN_CLASSES = Sets.newHashSet();

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        DefaultListableBeanFactory defaultBeanFactory = (DefaultListableBeanFactory) beanFactory;

        ArrayList<String> definitionNames = Lists.newArrayList(defaultBeanFactory.getBeanDefinitionNames());

        for (String definitionName : definitionNames) {
            BeanDefinition beanDefinition = defaultBeanFactory.getBeanDefinition(definitionName);
            String beanClassName = beanDefinition.getBeanClassName();
            // Remove BeanDefinition based on the class name
            if (EXCLUDE_BEAN_CLASSES.contains(beanClassName)) {
                defaultBeanFactory.removeBeanDefinition(definitionName);
            }
            // Remove BeanDefinition by annotation
            if (beanDefinition instanceof ScannedGenericBeanDefinition) {
                ScannedGenericBeanDefinition scannedGenericBeanDefinition = (ScannedGenericBeanDefinition) beanDefinition;
                AnnotationMetadata metadata = scannedGenericBeanDefinition.getMetadata();
                for (String annotationBeanClass : ANNOTATION_BEAN_CLASSES) {
                    if (metadata.hasAnnotation(annotationBeanClass)) {
                        defaultBeanFactory.removeBeanDefinition(definitionName);
                    }
                }
            }
        }
    }

}
Copy the code

In BeanFactoryPostProcessor, get the BeanFactory and then customize some matching rules to remove the BeanDefinition from the Factory.

Finally, we configure this processor in the test class, which is effective only for the test and does not affect the normal startup of the system.

@SpringBootTest
class SpringTestFieldApplicationTests {

    @TestConfiguration
    static class MyConfig {
        @Bean
        public BeanFactoryPostProcessor beanFactoryPostProcessor(a) {
            return newSpringTestLaunchControl(); }}}Copy the code

Let’s rerun the test and see what happens.

Boy, the system started in 5 seconds, the Bean initialization was reduced to 3 seconds (indicating that the initialization of MQ consumers is really slow), and catapulted!

To sum up, in this paper, the extension point BeanFactoryPostProcessor is used to remove some beans that are not needed in the testing process, thus shortening the system startup time. Moreover, it is only effective for the test startup environment, and has no invasion to the normal system startup.

Hope it helps.